Skip to content
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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p

## [Unreleased]

### Added

- Adds new ability to search for a GitHub PR in the _Launchpad;_ closes [#3543](https://github.com/gitkraken/vscode-gitlens/issues/3543)

## [15.6.2] - 2024-10-17

### Fixed
Expand Down
2 changes: 1 addition & 1 deletion docs/telemetry-events.md
Original file line number Diff line number Diff line change
Expand Up @@ -1305,7 +1305,7 @@ void
```typescript
{
'timeout': number,
'operation': 'getMyPullRequests' | 'getCodeSuggestions' | 'getEnrichedItems' | 'getCodeSuggestionCounts',
'operation': 'getPullRequest' | 'searchPullRequests' | 'getMyPullRequests' | 'getCodeSuggestions' | 'getEnrichedItems' | 'getCodeSuggestionCounts',
'duration': number
}
```
Expand Down
8 changes: 7 additions & 1 deletion src/constants.telemetry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -294,7 +294,13 @@ export type TelemetryEvents = {
/** Sent when a launchpad operation is taking longer than a set timeout to complete */
'launchpad/operation/slow': {
timeout: number;
operation: 'getMyPullRequests' | 'getCodeSuggestions' | 'getEnrichedItems' | 'getCodeSuggestionCounts';
operation:
| 'getPullRequest'
| 'searchPullRequests'
| 'getMyPullRequests'
| 'getCodeSuggestions'
| 'getEnrichedItems'
| 'getCodeSuggestionCounts';
duration: number;
};

Expand Down
84 changes: 84 additions & 0 deletions src/git/models/__tests__/pullRequest.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import * as assert from 'assert';
import { suite, test } from 'mocha';
import { getPullRequestIdentityValuesFromSearch } from '../pullRequest';

suite('Test GitHub PR URL parsing to identity: getPullRequestIdentityValuesFromSearch()', () => {
function t(message: string, query: string, prNumber: string | undefined, ownerAndRepo?: string) {
assert.deepStrictEqual(
getPullRequestIdentityValuesFromSearch(query),
{
ownerAndRepo: ownerAndRepo,
prNumber: prNumber,
},
`${message} (${JSON.stringify(query)})`,
);
}

test('full URL or without protocol but with domain, should parse to ownerAndRepo and prNumber', () => {
t('full URL', 'https://github.com/eamodio/vscode-gitlens/pull/1', '1', 'eamodio/vscode-gitlens');
t(
'with suffix',
'https://github.com/eamodio/vscode-gitlens/pull/1/files?diff=unified#hello',
'1',
'eamodio/vscode-gitlens',
);
t(
'with query',
'https://github.com/eamodio/vscode-gitlens/pull/1?diff=unified#hello',
'1',
'eamodio/vscode-gitlens',
);

t('with anchor', 'https://github.com/eamodio/vscode-gitlens/pull/1#hello', '1', 'eamodio/vscode-gitlens');
t('a weird suffix', 'https://github.com/eamodio/vscode-gitlens/pull/1-files', '1', 'eamodio/vscode-gitlens');
t('numeric repo name', 'https://github.com/sergeibbb/1/pull/16', '16', 'sergeibbb/1');

t('no protocol with leading slash', '/github.com/sergeibbb/1/pull/16?diff=unified', '16', 'sergeibbb/1');
t('no protocol without leading slash', 'github.com/sergeibbb/1/pull/16/files', '16', 'sergeibbb/1');
});

test('no domain, should parse to ownerAndRepo and prNumber', () => {
t('with leading slash', '/sergeibbb/1/pull/16#hello', '16', 'sergeibbb/1');
t('words in repo name', 'eamodio/vscode-gitlens/pull/1?diff=unified#hello', '1', 'eamodio/vscode-gitlens');
t('numeric repo name', 'sergeibbb/1/pull/16/files', '16', 'sergeibbb/1');
});

test('domain vs. no domain', () => {
t(
'with anchor',
'https://github.com/eamodio/vscode-gitlens/pull/1#hello/sergeibbb/1/pull/16',
'1',
'eamodio/vscode-gitlens',
);
});

test('has "pull/" fragment', () => {
t('with leading slash', '/pull/16/files#hello', '16');
t('without leading slash', 'pull/16?diff=unified#hello', '16');
t('with numeric repo name', '1/pull/16?diff=unified#hello', '16');
t('with double slash', '1//pull/16?diff=unified#hello', '16');
});

test('has "/<num>" fragment', () => {
t('with leading slash', '/16/files#hello', '16');
});

test('is a number', () => {
t('just a number', '16', '16');
t('with a hash', '#16', '16');
});

test('does not match', () => {
t('without leading slash', '16?diff=unified#hello', undefined);
t('with leading hash', '/#16/files#hello', undefined);
t('number is a part of a word', 'hello16', undefined);
t('number is a part of a word', '16hello', undefined);

t('with a number', '1/16?diff=unified#hello', '16');
t('with a number and slash', '/1/16?diff=unified#hello', '1');
t('with a word', 'anything/16?diff=unified#hello', '16');

t('with a wrong character leading to pull', 'sergeibbb/1/-pull/16?diff=unified#hello', '1');
t('with a wrong character leading to pull', 'sergeibbb/1-pull/16?diff=unified#hello', '1');
});
});
58 changes: 58 additions & 0 deletions src/git/models/pullRequest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { Uri, window } from 'vscode';
import { Schemes } from '../../constants';
import { Container } from '../../container';
import type { RepositoryIdentityDescriptor } from '../../gk/models/repositoryIdentities';
import type { EnrichablePullRequest } from '../../plus/integrations/providers/models';
import { formatDate, fromNow } from '../../system/date';
import { memoize } from '../../system/decorators/memoize';
import type { LeftRightCommitCountResult } from '../gitProvider';
Expand Down Expand Up @@ -415,3 +416,60 @@ export async function getOpenedPullRequestRepo(
const repo = await getOrOpenPullRequestRepository(container, pr, { promptIfNeeded: true });
return repo;
}

export type PullRequestURLIdentity = {
ownerAndRepo?: string;
prNumber?: string;
};

export function getPullRequestIdentityValuesFromSearch(search: string): PullRequestURLIdentity {
let ownerAndRepo: string | undefined = undefined;
let prNumber: string | undefined = undefined;

let match = search.match(/([^/]+\/[^/]+)\/pull\/(\d+)/); // with org and rep name
if (match != null) {
ownerAndRepo = match[1];
prNumber = match[2];
}

if (prNumber == null) {
match = search.match(/(?:\/|^)pull\/(\d+)/); // without repo name
if (match != null) {
prNumber = match[1];
}
}

if (prNumber == null) {
match = search.match(/(?:\/)(\d+)/); // any number starting with "/"
if (match != null) {
prNumber = match[1];
}
}

if (prNumber == null) {
match = search.match(/^#?(\d+)$/); // just a number or with a leading "#"
if (match != null) {
prNumber = match[1];
}
}

return { ownerAndRepo: ownerAndRepo, prNumber: prNumber };
}

export function doesPullRequestSatisfyRepositoryURLIdentity(
pr: EnrichablePullRequest | undefined,
{ ownerAndRepo, prNumber }: PullRequestURLIdentity,
): boolean {
if (pr == null) {
return false;
}
const satisfiesPrNumber = prNumber != null && pr.number === parseInt(prNumber, 10);
if (!satisfiesPrNumber) {
return false;
}
const satisfiesOwnerAndRepo = ownerAndRepo != null && pr.repoIdentity.name === ownerAndRepo;
if (!satisfiesOwnerAndRepo) {
return false;
}
return true;
}
8 changes: 5 additions & 3 deletions src/plus/integrations/providers/github/github.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3040,15 +3040,17 @@ export class GitHubApi implements Disposable {
const scope = getLogScope();

interface SearchResult {
nodes: GitHubPullRequest[];
search: {
nodes: GitHubPullRequest[];
};
}

try {
const query = `query searchPullRequests(
$searchQuery: String!
$avatarSize: Int
) {
search(first: 100, query: $searchQuery, type: ISSUE) {
search(first: 10, query: $searchQuery, type: ISSUE) {
nodes {
...on PullRequest {
${gqlPullRequestFragment}
Expand Down Expand Up @@ -3082,7 +3084,7 @@ export class GitHubApi implements Disposable {
);
if (rsp == null) return [];

const results = rsp.nodes.map(pr => fromGitHubPullRequest(pr, provider));
const results = rsp.search.nodes.map(pr => fromGitHubPullRequest(pr, provider));
return results;
} catch (ex) {
throw this.handleException(ex, provider, scope);
Expand Down
Loading
Loading