Skip to content

Commit 2ab00fa

Browse files
Allow short search strings on new workspace page (#20415)
* Allow short search strings on new workspace page * Get rid of `isIdentity` * Only show nudges when `!onlyConfigurations` Co-authored-by: Gero Posmyk-Leinemann <[email protected]> * Improve GitLab search length help text Co-authored-by: Gero Posmyk-Leinemann <[email protected]> * Remove duplicate Bitbucket value check * 💄 --------- Co-authored-by: Gero Posmyk-Leinemann <[email protected]>
1 parent 0b3880e commit 2ab00fa

File tree

5 files changed

+121
-60
lines changed

5 files changed

+121
-60
lines changed

components/dashboard/src/components/RepositoryFinder.tsx

Lines changed: 66 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ import { AuthProviderType } from "@gitpod/public-api/lib/gitpod/v1/authprovider_
2222
import { SuggestedRepository } from "@gitpod/public-api/lib/gitpod/v1/scm_pb";
2323
import { PREDEFINED_REPOS } from "../data/git-providers/predefined-repos";
2424
import { useConfiguration, useListConfigurations } from "../data/configurations/configuration-queries";
25+
import { useUserLoader } from "../hooks/use-user-loader";
26+
import { conjunctScmProviders, getDeduplicatedScmProviders } from "../utils";
2527

2628
const isPredefined = (repo: SuggestedRepository): boolean => {
2729
return PREDEFINED_REPOS.some((predefined) => predefined.url === repo.url) && !repo.configurationId;
@@ -54,6 +56,8 @@ export default function RepositoryFinder({
5456
onChange,
5557
}: RepositoryFinderProps) {
5658
const [searchString, setSearchString] = useState("");
59+
60+
const { user } = useUserLoader();
5761
const {
5862
data: unifiedRepos,
5963
isLoading,
@@ -120,6 +124,12 @@ export default function RepositoryFinder({
120124

121125
const authProviders = useAuthProviderDescriptions();
122126

127+
const usedProviders = useMemo(() => {
128+
if (!user || !authProviders.data) return [];
129+
130+
return getDeduplicatedScmProviders(user, authProviders.data) ?? [];
131+
}, [user, authProviders]);
132+
123133
const handleSelectionChange = useCallback(
124134
(selectedID: string) => {
125135
const matchingSuggestion = repos?.find(
@@ -310,9 +320,9 @@ export default function RepositoryFinder({
310320
}
311321

312322
if (
313-
searchString.length >= 3 &&
314-
authProviders.data?.some((p) => p.type === AuthProviderType.BITBUCKET_SERVER) &&
315-
!onlyConfigurations
323+
!onlyConfigurations &&
324+
searchString.length > 0 &&
325+
authProviders.data?.some((p) => p.type === AuthProviderType.BITBUCKET_SERVER)
316326
) {
317327
// add an element that tells the user that the Bitbucket Server does only support prefix search
318328
result.push({
@@ -327,27 +337,62 @@ export default function RepositoryFinder({
327337
});
328338
}
329339

330-
if (searchString.length >= 3 && authProviders.data?.some((p) => p.type === AuthProviderType.AZURE_DEVOPS)) {
331-
// ENT-780
340+
if (
341+
!onlyConfigurations &&
342+
searchString.length > 0 &&
343+
searchString.length < 3 &&
344+
usedProviders.includes("GitLab")
345+
) {
346+
// add an element that tells the user that GitLab only does exact searches for short queries
332347
result.push({
333-
id: "azure-devops",
348+
id: "gitlab",
334349
element: (
335350
<div className="text-sm text-pk-content-tertiary flex items-center">
336351
<Exclamation2 className="w-4 h-4 mr-2" />
337-
<span>Azure DevOps doesn't support repository searching.</span>
352+
<span>
353+
Search text is &lt; 3 characters. GitLab will only show exact matches for short
354+
searches.
355+
</span>
338356
</div>
339357
),
340358
isSelectable: false,
341359
});
342360
}
343361

344-
if (searchString.length < 3) {
345-
// add an element that tells the user to type more
362+
const setupProvidersWithoutPathSearchSupport = usedProviders.filter((p) =>
363+
["Bitbucket", "GitLab"].includes(p),
364+
);
365+
if (
366+
!onlyConfigurations &&
367+
searchString.length > 1 &&
368+
setupProvidersWithoutPathSearchSupport.length > 0 &&
369+
searchString.includes("/")
370+
) {
346371
result.push({
347-
id: "not-searched",
372+
id: "whole-path-matching-unsupported",
348373
element: (
349-
<div className="text-sm text-pk-content-tertiary">
350-
Please type at least 3 characters to search.
374+
<div className="text-sm text-pk-content-tertiary flex items-center">
375+
<Exclamation2 className="w-4 h-4 mr-2" />
376+
<span>
377+
{usedProviders
378+
? conjunctScmProviders(setupProvidersWithoutPathSearchSupport)
379+
: "Some providers"}{" "}
380+
only support searching by repository name, not full paths.
381+
</span>
382+
</div>
383+
),
384+
isSelectable: false,
385+
});
386+
}
387+
388+
if (!onlyConfigurations && searchString.length > 0 && usedProviders.includes("Azure DevOps")) {
389+
// CLC-780
390+
result.push({
391+
id: "azure-devops",
392+
element: (
393+
<div className="text-sm text-pk-content-tertiary flex items-center">
394+
<Exclamation2 className="w-4 h-4 mr-2" />
395+
<span>Azure DevOps doesn't support repository searching.</span>
351396
</div>
352397
),
353398
isSelectable: false,
@@ -356,7 +401,15 @@ export default function RepositoryFinder({
356401

357402
return result;
358403
},
359-
[isShowingExamples, onlyConfigurations, repos, hasMore, authProviders.data, filteredPredefinedRepos],
404+
[
405+
isShowingExamples,
406+
onlyConfigurations,
407+
repos,
408+
hasMore,
409+
authProviders.data,
410+
filteredPredefinedRepos,
411+
usedProviders,
412+
],
360413
);
361414

362415
return (

components/dashboard/src/data/git-providers/search-repositories-query.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ export const useSearchRepositories = ({ searchString, limit }: { searchString: s
2424
return repositories;
2525
},
2626
{
27-
enabled: !!org && debouncedSearchString.length >= 3,
27+
enabled: !!org && debouncedSearchString.trim().length > 0,
2828
// Need this to keep previous results while we wait for a new search to complete since debouncedSearchString changes and updates the key
2929
keepPreviousData: true,
3030
// We intentionally don't want to trigger refetches here to avoid a loading state side effect of focusing

components/dashboard/src/data/git-providers/unified-repositories-search-query.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ export const useUnifiedRepositorySearch = ({
6262
}, [configurationSearch.data, excludeConfigurations]);
6363

6464
const filteredRepos = useMemo(() => {
65-
const repos = [suggestedQuery.data || [], searchQuery.data || [], flattenedConfigurations ?? []].flat();
65+
const repos = [suggestedQuery.data || [], flattenedConfigurations ?? [], searchQuery.data || []].flat();
6666
return deduplicateAndFilterRepositories(searchString, excludeConfigurations, onlyConfigurations, repos);
6767
}, [
6868
searchString,
@@ -113,7 +113,7 @@ export function deduplicateAndFilterRepositories(
113113
}
114114

115115
// filter out entries that don't match the search string
116-
if (!`${repo.url}${repo.configurationName || ""}`.toLowerCase().includes(searchString.trim().toLowerCase())) {
116+
if (!`${repo.url}${repo.configurationName ?? ""}`.toLowerCase().includes(searchString.trim().toLowerCase())) {
117117
continue;
118118
}
119119
// filter out duplicates

components/dashboard/src/utils.ts

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,10 @@
44
* See License.AGPL.txt in the project root for license information.
55
*/
66

7+
import { User } from "@gitpod/public-api/lib/gitpod/v1/user_pb";
8+
import { AuthProviderDescription, AuthProviderType } from "@gitpod/public-api/lib/gitpod/v1/authprovider_pb";
79
import EventEmitter from "events";
10+
import { uniq } from "lodash";
811

912
export interface PollOptions<T> {
1013
backoffFactor: number;
@@ -230,3 +233,50 @@ export function isTrustedUrlOrPath(urlOrPath: string) {
230233
}
231234
return isTrusted;
232235
}
236+
237+
type UnifiedAuthProvider = "Bitbucket" | "GitLab" | "GitHub" | "Azure DevOps";
238+
const unifyProviderType = (type: AuthProviderType): UnifiedAuthProvider | undefined => {
239+
switch (type) {
240+
case AuthProviderType.BITBUCKET:
241+
case AuthProviderType.BITBUCKET_SERVER:
242+
return "Bitbucket";
243+
case AuthProviderType.GITHUB:
244+
return "GitHub";
245+
case AuthProviderType.GITLAB:
246+
return "GitLab";
247+
case AuthProviderType.AZURE_DEVOPS:
248+
return "Azure DevOps";
249+
default:
250+
return undefined;
251+
}
252+
};
253+
254+
export const getDeduplicatedScmProviders = (
255+
user: User,
256+
descriptions: AuthProviderDescription[],
257+
): UnifiedAuthProvider[] => {
258+
const userIdentities = user.identities.map((identity) => identity.authProviderId);
259+
const userProviders = userIdentities
260+
.map((id) => descriptions?.find((provider) => provider.id === id))
261+
.filter((p) => !!p)
262+
.map((provider) => provider.type);
263+
264+
const unifiedProviders = userProviders
265+
.map((type) => unifyProviderType(type))
266+
.filter((t) => !!t)
267+
.sort();
268+
269+
return uniq(unifiedProviders);
270+
};
271+
272+
export const disjunctScmProviders = (providers: UnifiedAuthProvider[]): string => {
273+
const formatter = new Intl.ListFormat("en", { style: "long", type: "disjunction" });
274+
275+
return formatter.format(providers);
276+
};
277+
278+
export const conjunctScmProviders = (providers: UnifiedAuthProvider[]): string => {
279+
const formatter = new Intl.ListFormat("en", { style: "long", type: "conjunction" });
280+
281+
return formatter.format(providers);
282+
};

components/dashboard/src/workspaces/BrowserExtensionBanner.tsx

Lines changed: 2 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,6 @@
77
import { useCallback, useEffect, useMemo, useState } from "react";
88
import UAParser from "ua-parser-js";
99
import { useUserLoader } from "../hooks/use-user-loader";
10-
import { User } from "@gitpod/public-api/lib/gitpod/v1/user_pb";
11-
import { AuthProviderDescription, AuthProviderType } from "@gitpod/public-api/lib/gitpod/v1/authprovider_pb";
1210
import { useAuthProviderDescriptions } from "../data/auth-providers/auth-provider-descriptions-query";
1311
import { useFeatureFlag } from "../data/featureflag-query";
1412
import { trackEvent } from "../Analytics";
@@ -17,7 +15,7 @@ import bitbucketButton from "../images/browser-extension/bitbucket.webp";
1715
import githubButton from "../images/browser-extension/github.webp";
1816
import gitlabButton from "../images/browser-extension/gitlab.webp";
1917
import azuredevopsButton from "../images/browser-extension/azure-devops.webp";
20-
import uniq from "lodash/uniq";
18+
import { disjunctScmProviders, getDeduplicatedScmProviders } from "../utils";
2119

2220
const browserExtensionImages = {
2321
Bitbucket: bitbucketButton,
@@ -31,7 +29,6 @@ type BrowserOption = {
3129
aliases?: string[];
3230
url: string;
3331
};
34-
type UnifiedAuthProvider = "Bitbucket" | "GitLab" | "GitHub" | "Azure DevOps";
3532

3633
const installationOptions: BrowserOption[] = [
3734
{
@@ -46,45 +43,6 @@ const installationOptions: BrowserOption[] = [
4643
},
4744
];
4845

49-
const isIdentity = (identity?: AuthProviderDescription): identity is AuthProviderDescription => !!identity;
50-
const unifyProviderType = (type: AuthProviderType): UnifiedAuthProvider | undefined => {
51-
switch (type) {
52-
case AuthProviderType.BITBUCKET:
53-
case AuthProviderType.BITBUCKET_SERVER:
54-
return "Bitbucket";
55-
case AuthProviderType.GITHUB:
56-
return "GitHub";
57-
case AuthProviderType.GITLAB:
58-
return "GitLab";
59-
case AuthProviderType.AZURE_DEVOPS:
60-
return "Azure DevOps";
61-
default:
62-
return undefined;
63-
}
64-
};
65-
66-
const isAuthProviderType = (type?: UnifiedAuthProvider): type is UnifiedAuthProvider => !!type;
67-
const getDeduplicatedScmProviders = (user: User, descriptions: AuthProviderDescription[]): UnifiedAuthProvider[] => {
68-
const userIdentities = user.identities.map((identity) => identity.authProviderId);
69-
const userProviders = userIdentities
70-
.map((id) => descriptions?.find((provider) => provider.id === id))
71-
.filter(isIdentity)
72-
.map((provider) => provider.type);
73-
74-
const unifiedProviders = userProviders
75-
.map((type) => unifyProviderType(type))
76-
.filter(isAuthProviderType)
77-
.sort();
78-
79-
return uniq(unifiedProviders);
80-
};
81-
82-
const displayScmProviders = (providers: UnifiedAuthProvider[]): string => {
83-
const formatter = new Intl.ListFormat("en", { style: "long", type: "disjunction" });
84-
85-
return formatter.format(providers);
86-
};
87-
8846
/**
8947
* Determines whether the extension has been able to access the current site in the past month. If it hasn't, it's most likely not installed or misconfigured
9048
*/
@@ -108,7 +66,7 @@ export function BrowserExtensionBanner() {
10866
return getDeduplicatedScmProviders(user, authProviderDescriptions);
10967
}, [user, authProviderDescriptions]);
11068

111-
const scmProviderString = useMemo(() => usedProviders && displayScmProviders(usedProviders), [usedProviders]);
69+
const scmProviderString = useMemo(() => usedProviders && disjunctScmProviders(usedProviders), [usedProviders]);
11270

11371
const parser = useMemo(() => new UAParser(), []);
11472
const browserName = useMemo(() => parser.getBrowser().name?.toLowerCase(), [parser]);

0 commit comments

Comments
 (0)