Skip to content

Pre-populate Cmd+O modal and sort suggested context URLs by most-recently-used first #8380

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Feb 22, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 6 additions & 16 deletions components/dashboard/src/components/RepositoryFinder.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,14 @@

import { User } from "@gitpod/gitpod-protocol";
import React, { useContext, useEffect, useState } from "react";
import { Link } from "react-router-dom";
import { getGitpodService } from "../service/service";
import { UserContext } from "../user-context";

type SearchResult = string;
type SearchData = SearchResult[];

const LOCAL_STORAGE_KEY = 'open-in-gitpod-search-data';
const MAX_DISPLAYED_ITEMS = 15;
const MAX_DISPLAYED_ITEMS = 20;

export default function RepositoryFinder(props: { initialQuery?: string }) {
const { user } = useContext(UserContext);
Expand All @@ -38,6 +37,10 @@ export default function RepositoryFinder(props: { initialQuery?: string }) {
}
}

useEffect(() => {
search('');
}, []);

// Up/Down keyboard navigation between results
const onKeyDown = (event: React.KeyboardEvent) => {
if (!selectedSearchResult) {
Expand Down Expand Up @@ -81,18 +84,9 @@ export default function RepositoryFinder(props: { initialQuery?: string }) {
<div className="py-4">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 16 16" width="16" height="16"><path fill="#A8A29E" d="M6 2a4 4 0 100 8 4 4 0 000-8zM0 6a6 6 0 1110.89 3.477l4.817 4.816a1 1 0 01-1.414 1.414l-4.816-4.816A6 6 0 010 6z" /></svg>
</div>
<input type="search" className="flex-grow" placeholder="Search repositories and examples" autoFocus value={searchQuery} onChange={e => search(e.target.value)} onKeyDown={onKeyDown} />
<input type="search" className="flex-grow" placeholder="Paste repository URL or type to find suggestions" autoFocus value={searchQuery} onChange={e => search(e.target.value)} onKeyDown={onKeyDown} />
</div>
<div className="mt-3 -mx-5 px-5 flex flex-col space-y-2 h-64 overflow-y-auto">
{searchQuery === '' && searchResults.length === 0 &&
<div className="mt-12 mx-auto w-96 text-gray-500">
Paste a <a className="gp-link" href="https://www.gitpod.io/docs/context-urls">repository context URL</a>, or start typing to see suggestions from:
<ul className="list-disc mt-4 pl-7 flex flex-col space-y-1">
<li>Your recent repositories</li>
<li>Your repositories from <Link className="gp-link" to="/integrations">connected integrations</Link></li>
<li>Example repositories</li>
</ul>
</div>}
{searchResults.slice(0, MAX_DISPLAYED_ITEMS).map((result, index) =>
<a className={`px-4 py-3 rounded-xl` + (result === selectedSearchResult ? ' bg-gray-600 text-gray-50 dark:bg-gray-700' : '')} href={`/#${result}`} key={`search-result-${index}`} onMouseEnter={() => setSelectedSearchResult(result)}>
{searchQuery.length < 2
Expand Down Expand Up @@ -157,10 +151,6 @@ async function actuallyRefreshSearchData(query: string, user: User | undefined):
}

async function findResults(query: string, onResults: (results: string[]) => void) {
if (!query) {
onResults([]);
return;
}
const searchData = loadSearchData();
try {
// If the query is a URL, and it's not present in the proposed results, "artificially" add it here.
Expand Down
32 changes: 22 additions & 10 deletions components/server/src/workspace/gitpod-server-impl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1029,15 +1029,15 @@ export class GitpodServerImpl implements GitpodServerWithTracing, Disposable {

public async getSuggestedContextURLs(ctx: TraceContext): Promise<string[]> {
const user = this.checkUser("getSuggestedContextURLs");
const suggestions: string[] = [];
const suggestions: Array<{ url: string, lastUse?: string }> = [];
const logCtx: LogContext = { userId: user.id };

// Fetch all data sources in parallel for maximum speed (don't await in this scope before `Promise.allSettled(promises)` below!)
const promises = [];

// Example repositories
promises.push(this.getFeaturedRepositories(ctx).then(exampleRepos => {
exampleRepos.forEach(r => suggestions.push(r.url));
exampleRepos.forEach(r => suggestions.push({ url: r.url }));
}).catch(error => {
log.error(logCtx, 'Could not get example repositories', error);
}));
Expand All @@ -1046,7 +1046,7 @@ export class GitpodServerImpl implements GitpodServerWithTracing, Disposable {
promises.push(this.getAuthProviders(ctx).then(authProviders => Promise.all(authProviders.map(async (p) => {
try {
const userRepos = await this.getProviderRepositoriesForUser(ctx, { provider: p.host });
userRepos.forEach(r => suggestions.push(r.cloneUrl.replace(/\.git$/, '')));
userRepos.forEach(r => suggestions.push({ url: r.cloneUrl.replace(/\.git$/, '') }));
} catch (error) {
log.debug(logCtx, 'Could not get user repositories from App for ' + p.host, error);
}
Expand All @@ -1064,7 +1064,7 @@ export class GitpodServerImpl implements GitpodServerWithTracing, Disposable {
return;
}
const userRepos = await services.repositoryProvider.getUserRepos(user);
userRepos.forEach(r => suggestions.push(r.replace(/\.git$/, '')));
userRepos.forEach(r => suggestions.push({ url: r.replace(/\.git$/, '') }));
} catch (error) {
log.debug(logCtx, 'Could not get user repositories from host ' + p.host, error);
}
Expand All @@ -1077,7 +1077,8 @@ export class GitpodServerImpl implements GitpodServerWithTracing, Disposable {
workspaces.forEach(ws => {
const repoUrl = Workspace.getFullRepositoryUrl(ws.workspace);
if (repoUrl) {
suggestions.push(repoUrl);
const lastUse = WorkspaceInfo.lastActiveISODate(ws);
suggestions.push({ url: repoUrl, lastUse });
}
});
}).catch(error => {
Expand All @@ -1088,14 +1089,25 @@ export class GitpodServerImpl implements GitpodServerWithTracing, Disposable {

const uniqueURLs = new Set();
return suggestions
.sort((a, b) => a.toLowerCase() > b.toLowerCase() ? 1 : -1)
.filter(r => {
if (uniqueURLs.has(r)) {
.sort((a, b) => {
// Most recently used first
if (b.lastUse || a.lastUse) {
const la = a.lastUse || '';
const lb = b.lastUse || '';
return la < lb ? 1 : (la === lb ? 0 : -1);
}
// Otherwise, alphasort
const ua = a.url.toLowerCase();
const ub = b.url.toLowerCase();
return ua > ub ? 1 : (ua === ub ? 0 : -1);
})
.filter(s => {
if (uniqueURLs.has(s.url)) {
return false;
}
uniqueURLs.add(r);
uniqueURLs.add(s.url);
return true;
});
}).map(s => s.url);
}

public async setWorkspaceTimeout(ctx: TraceContext, workspaceId: string, duration: WorkspaceTimeoutDuration): Promise<SetWorkspaceTimeoutResult> {
Expand Down