Skip to content

Commit 4896f98

Browse files
committed
Allow short search strings on new workspace page
1 parent 26d2b1c commit 4896f98

File tree

5 files changed

+118
-58
lines changed

5 files changed

+118
-58
lines changed

components/dashboard/src/components/RepositoryFinder.tsx

Lines changed: 60 additions & 11 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,7 +320,7 @@ export default function RepositoryFinder({
310320
}
311321

312322
if (
313-
searchString.length >= 3 &&
323+
searchString.length > 0 &&
314324
authProviders.data?.some((p) => p.type === AuthProviderType.BITBUCKET_SERVER) &&
315325
!onlyConfigurations
316326
) {
@@ -327,27 +337,58 @@ 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+
searchString.length > 0 &&
342+
searchString.length < 3 &&
343+
authProviders.data?.some((p) => p.type === AuthProviderType.GITLAB) &&
344+
!onlyConfigurations
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>Search is too short for searching on GitLab.</span>
353+
</div>
354+
),
355+
isSelectable: false,
356+
});
357+
}
358+
359+
const setupProvidersWithoutPathSearchSupport = usedProviders.filter((p) =>
360+
["Bitbucket", "Bitbucket Server", "GitLab"].includes(p),
361+
);
362+
if (
363+
searchString.length > 1 &&
364+
setupProvidersWithoutPathSearchSupport.length > 0 &&
365+
searchString.includes("/")
366+
) {
367+
result.push({
368+
id: "whole-path-matching-unsupported",
369+
element: (
370+
<div className="text-sm text-pk-content-tertiary flex items-center">
371+
<Exclamation2 className="w-4 h-4 mr-2" />
372+
<span>
373+
{usedProviders
374+
? conjunctScmProviders(setupProvidersWithoutPathSearchSupport)
375+
: "Some providers"}{" "}
376+
only support searching by repository name, not full paths.
377+
</span>
338378
</div>
339379
),
340380
isSelectable: false,
341381
});
342382
}
343383

344-
if (searchString.length < 3) {
345-
// add an element that tells the user to type more
384+
if (searchString.length > 0 && authProviders.data?.some((p) => p.type === AuthProviderType.AZURE_DEVOPS)) {
385+
// CLC-780
346386
result.push({
347-
id: "not-searched",
387+
id: "azure-devops",
348388
element: (
349-
<div className="text-sm text-pk-content-tertiary">
350-
Please type at least 3 characters to search.
389+
<div className="text-sm text-pk-content-tertiary flex items-center">
390+
<Exclamation2 className="w-4 h-4 mr-2" />
391+
<span>Azure DevOps doesn't support repository searching.</span>
351392
</div>
352393
),
353394
isSelectable: false,
@@ -356,7 +397,15 @@ export default function RepositoryFinder({
356397

357398
return result;
358399
},
359-
[isShowingExamples, onlyConfigurations, repos, hasMore, authProviders.data, filteredPredefinedRepos],
400+
[
401+
isShowingExamples,
402+
onlyConfigurations,
403+
repos,
404+
hasMore,
405+
authProviders.data,
406+
filteredPredefinedRepos,
407+
usedProviders,
408+
],
360409
);
361410

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

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)