Skip to content

Commit ee5a2f2

Browse files
authored
feat: add organization results to global search bar (#2023)
Signed-off-by: Gašper Grom <gasper.grom@gmail.com>
1 parent a88bcb7 commit ee5a2f2

6 files changed

Lines changed: 92 additions & 11 deletions

File tree

frontend/app/components/modules/collection/services/collections.api.service.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -209,7 +209,7 @@ class CollectionsApiService {
209209
async searchProjectsAndRepositories(query: string): Promise<SearchResults> {
210210
const sanitizedQuery = sanitizeSearchQuery(query);
211211
if (!sanitizedQuery) {
212-
return { projects: [], repositories: [], collections: [] };
212+
return { projects: [], repositories: [], collections: [], organizations: [] };
213213
}
214214

215215
const res = await $fetch<SearchResults>('/api/search', {

frontend/app/components/shared/layout/search/search-modal.vue

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ SPDX-License-Identifier: MIT
1717
v-model="search"
1818
type="text"
1919
class="!outline-none !shadow-none flex-grow text-sm text-neutral-900 leading-5"
20-
placeholder="Search projects, repositories, or collections..."
20+
placeholder="Search projects, repositories, collections, or organizations..."
2121
@input="triggerSearch"
2222
/>
2323
<lfx-icon
@@ -49,6 +49,7 @@ SPDX-License-Identifier: MIT
4949
:projects="projects"
5050
:repositories="repositories"
5151
:collections="collections"
52+
:organizations="organizations"
5253
/>
5354
</div>
5455
</div>
@@ -58,7 +59,13 @@ SPDX-License-Identifier: MIT
5859
<script setup lang="ts">
5960
import { computed, onMounted, ref } from 'vue';
6061
import { debounce } from 'lodash-es';
61-
import type { SearchCollection, SearchProject, SearchRepository, SearchResults } from '~~/types/search';
62+
import type {
63+
SearchCollection,
64+
SearchOrganization,
65+
SearchProject,
66+
SearchRepository,
67+
SearchResults,
68+
} from '~~/types/search';
6269
import LfxModal from '~/components/uikit/modal/modal.vue';
6370
import LfxIcon from '~/components/uikit/icon/icon.vue';
6471
import LfxSearchResult from '~/components/shared/layout/search/search-result.vue';
@@ -79,6 +86,7 @@ const loading = ref<boolean>(false);
7986
const projects = ref<SearchProject[]>([]);
8087
const repositories = ref<SearchRepository[]>([]);
8188
const collections = ref<SearchCollection[]>([]);
89+
const organizations = ref<SearchOrganization[]>([]);
8290
8391
const fetchSearchResults = () => {
8492
searchQuery.value = search.value;
@@ -95,11 +103,13 @@ const fetchSearchResults = () => {
95103
projects.value = res.projects;
96104
repositories.value = res.repositories;
97105
collections.value = res.collections;
106+
organizations.value = res.organizations;
98107
})
99108
.catch(() => {
100109
projects.value = [];
101110
repositories.value = [];
102111
collections.value = [];
112+
organizations.value = [];
103113
})
104114
.finally(() => {
105115
loading.value = false;

frontend/app/components/shared/layout/search/search-result.vue

Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,47 @@ SPDX-License-Identifier: MIT
154154
</p>
155155
</section>
156156

157+
<!-- Organizations -->
158+
<div
159+
v-if="tab === 'all' && props.organizations.length > 0"
160+
class="pt-3 px-3 text-xs font-semibold leading-5 text-neutral-400"
161+
>
162+
Organizations
163+
</div>
164+
<section
165+
v-if="tab === 'all' || (tab === 'organizations' && props.organizations.length > 0)"
166+
class="flex flex-col gap-1"
167+
>
168+
<nuxt-link
169+
v-for="organization of props.organizations"
170+
:key="organization.slug"
171+
:to="{ name: LfxRoutes.ORGANIZATION, params: { orgSlug: organization.slug } }"
172+
class="px-3 py-2 rounded-md transition-all hover:bg-neutral-50 flex items-center gap-2 cursor-pointer text-sm text-neutral-900"
173+
>
174+
<lfx-avatar
175+
:src="organization.logo || ''"
176+
size="xsmall"
177+
type="organization"
178+
class="!rounded-sm !outline-1"
179+
:aria-label="organization.logo && organization.name"
180+
/>
181+
{{ organization.name }}
182+
</nuxt-link>
183+
</section>
184+
<section
185+
v-if="tab === 'organizations' && props.organizations.length === 0"
186+
class="px-3 py-12 flex flex-col items-center"
187+
>
188+
<lfx-icon
189+
name="building"
190+
:size="40"
191+
class="text-neutral-300"
192+
/>
193+
<p class="pt-5 text-sm leading-5 text-neutral-500 text-center">
194+
We couldn’t find any organizations with that term
195+
</p>
196+
</section>
197+
157198
<!-- No results -->
158199
<section
159200
v-if="tab === 'all' && noResult"
@@ -174,7 +215,7 @@ SPDX-License-Identifier: MIT
174215

175216
<script setup lang="ts">
176217
import { useRoute } from 'nuxt/app';
177-
import type { SearchCollection, SearchProject, SearchRepository } from '~~/types/search';
218+
import type { SearchCollection, SearchOrganization, SearchProject, SearchRepository } from '~~/types/search';
178219
import LfxTabs from '~/components/uikit/tabs/tabs.vue';
179220
import LfxAvatar from '~/components/uikit/avatar/avatar.vue';
180221
import LfxIcon from '~/components/uikit/icon/icon.vue';
@@ -186,13 +227,17 @@ const props = defineProps<{
186227
projects: SearchProject[];
187228
repositories: SearchRepository[];
188229
collections: SearchCollection[];
230+
organizations: SearchOrganization[];
189231
}>();
190232
191233
const route = useRoute();
192234
193235
const tab = ref('all');
194236
195-
const noResult = computed(() => !props.projects.length && !props.repositories.length && !props.collections.length);
237+
const noResult = computed(
238+
() =>
239+
!props.projects.length && !props.repositories.length && !props.collections.length && !props.organizations.length,
240+
);
196241
197242
const repositories = computed(() =>
198243
props.repositories.map((repository) => ({
@@ -223,6 +268,10 @@ const tabs = [
223268
value: 'collections',
224269
label: 'Collections',
225270
},
271+
{
272+
value: 'organizations',
273+
label: 'Organizations',
274+
},
226275
];
227276
</script>
228277

frontend/app/components/shared/layout/search/search.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ SPDX-License-Identifier: MIT
1313
class="text-neutral-400 font-normal"
1414
:size="14"
1515
/>
16-
<p class="text-body-1 text-neutral-400 truncate">Search projects, repositories, or collections</p>
16+
<p class="text-body-1 text-neutral-400 truncate">Search projects, repositories, collections, or organizations</p>
1717
<div class="flex-grow" />
1818
<lfx-chip
1919
v-if="!isMobile"

frontend/server/api/search.ts

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,17 @@
11
// Copyright (c) 2025 The Linux Foundation and each contributor.
22
// SPDX-License-Identifier: MIT
33
import { fetchFromTinybird } from '~~/server/data/tinybird/tinybird';
4-
import type { SearchCollection, SearchProject, SearchRepository } from '~~/types/search';
4+
import type {
5+
SearchCollection,
6+
SearchOrganization,
7+
SearchProject,
8+
SearchRepository,
9+
} from '~~/types/search';
510
import { getRepoNameFromUrl, getRepoSlugFromName } from '~~/server/helpers/repository.helpers';
611

712
export interface SearchResponse {
813
id: string;
9-
type: 'project' | 'repository' | 'collection';
14+
type: 'project' | 'repository' | 'collection' | 'organization';
1015
slug: string;
1116
logo: string | null;
1217
projectSlug: string | null;
@@ -20,19 +25,20 @@ export interface SearchResponse {
2025
/**
2126
* API Endpoint: /api/search
2227
* Method: GET
23-
* Description: Searches for collections, projects, and repositories based on the provided query.
28+
* Description: Searches for collections, projects, repositories, and organizations based on the provided query.
2429
*
2530
* Query Parameters:
26-
* - query (string, optional): The search term to query collections, projects, and repositories.
31+
* - query (string, optional): The search term to query collections, projects, repositories, and organizations.
2732
* - limit (number, optional): The maximum number of results to return (default: 10).
2833
*
2934
* Response:
3035
* - projects (Array<SearchResponse>): A list of search results of type "project".
3136
* - repositories (Array<SearchResponse>): A list of search results of type "repository".
3237
* - collections (Array<SearchResponse>): A list of search results of type "collection".
38+
* - organizations (Array<SearchResponse>): A list of search results of type "organization".
3339
*
3440
* Search Response Object (SearchResponse):
35-
* - type (string): The type identifier for the search result (e.g., "project", "repository", "collection").
41+
* - type (string): The type identifier for the search result (e.g., "project", "repository", "collection", "organization").
3642
* - slug (string): A unique slug identifier for the search result.
3743
* - logo (string | null): The logo URL associated with the search result, or null if unavailable.
3844
* - projectSlug (string | null): The project slug associated with the result, or null if absent.
@@ -48,6 +54,7 @@ export default defineEventHandler(async (event) => {
4854
const projects: SearchProject[] = [];
4955
const repositories: SearchRepository[] = [];
5056
const collections: SearchCollection[] = [];
57+
const organizations: SearchOrganization[] = [];
5158

5259
const limit: number = 10;
5360

@@ -86,6 +93,12 @@ export default defineEventHandler(async (event) => {
8693
slug: item.slug,
8794
name: item.name || '',
8895
});
96+
} else if (item.type === 'organization') {
97+
organizations.push({
98+
slug: item.slug,
99+
name: item.name || '',
100+
logo: item.logo,
101+
});
89102
}
90103
});
91104
}
@@ -94,13 +107,15 @@ export default defineEventHandler(async (event) => {
94107
projects,
95108
repositories,
96109
collections,
110+
organizations,
97111
};
98112
} catch (error) {
99113
console.error('Error fetching search results:', error);
100114
return {
101115
projects,
102116
repositories,
103117
collections,
118+
organizations,
104119
};
105120
}
106121
});

frontend/types/search.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,15 @@ export interface SearchRepository {
2222
url: string;
2323
}
2424

25+
export interface SearchOrganization {
26+
name: string;
27+
slug: string;
28+
logo: string | null;
29+
}
30+
2531
export interface SearchResults {
2632
projects: SearchProject[];
2733
repositories: SearchRepository[];
2834
collections: SearchCollection[];
35+
organizations: SearchOrganization[];
2936
}

0 commit comments

Comments
 (0)