blob: 97e9ae0e26a7d2d0eb7ae3cf7c913bf2985a6ea4 [file] [log] [blame]
/**
* @license
* Copyright 2020 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import {
BasePatchSetNum,
BranchName,
ChangeInfo,
DashboardId,
GroupId,
NumericChangeId,
PARENT,
RepoName,
RevisionPatchSetNum,
TopicName,
UrlEncodedCommentId,
} from '../types/common';
import {PatchRangeParams} from '../elements/core/gr-router/gr-router';
import {encodeURL, getBaseUrl} from './url-util';
import {assertNever} from './common-util';
import {GerritView} from '../services/router/router-model';
import {addQuotesWhen} from './string-util';
import {AttemptChoice} from '../models/checks/checks-util';
export interface DashboardSection {
name: string;
query: string;
suffixForDashboard?: string;
selfOnly?: boolean;
hideIfEmpty?: boolean;
results?: ChangeInfo[];
}
export enum GroupDetailView {
MEMBERS = 'members',
LOG = 'log',
}
export enum RepoDetailView {
GENERAL = 'general',
ACCESS = 'access',
BRANCHES = 'branches',
COMMANDS = 'commands',
DASHBOARDS = 'dashboards',
TAGS = 'tags',
}
export interface GenerateUrlSearchViewParameters {
view: GerritView.SEARCH;
query?: string;
offset?: number;
project?: RepoName;
branch?: BranchName;
topic?: TopicName;
// TODO(TS): Define more precise type (enum?)
statuses?: string[];
hashtag?: string;
owner?: string;
}
export interface GenerateUrlChangeViewParameters {
view: GerritView.CHANGE;
// TODO(TS): NumericChangeId - not sure about it, may be it can be removed
changeNum: NumericChangeId;
project: RepoName;
patchNum?: RevisionPatchSetNum;
basePatchNum?: BasePatchSetNum;
edit?: boolean;
messageHash?: string;
commentId?: UrlEncodedCommentId;
forceReload?: boolean;
openReplyDialog?: boolean;
tab?: string;
/** regular expression for filtering check runs */
filter?: string;
/** selected attempt for selected check runs */
attempt?: AttemptChoice;
usp?: string;
}
export interface GenerateUrlRepoViewParameters {
view: GerritView.REPO;
repoName: RepoName;
detail?: RepoDetailView;
}
export interface GenerateUrlDashboardViewParameters {
view: GerritView.DASHBOARD;
user?: string;
repo?: RepoName;
dashboard?: DashboardId;
// TODO(TS): properties bellow aren't set anywhere, try to remove
project?: RepoName;
sections?: DashboardSection[];
title?: string;
}
export interface GenerateUrlGroupViewParameters {
view: GerritView.GROUP;
groupId: GroupId;
detail?: GroupDetailView;
}
export interface GenerateUrlEditViewParameters {
view: GerritView.EDIT;
changeNum: NumericChangeId;
project: RepoName;
path: string;
patchNum: RevisionPatchSetNum;
lineNum?: number | string;
}
export interface GenerateUrlRootViewParameters {
view: GerritView.ROOT;
}
export interface GenerateUrlSettingsViewParameters {
view: GerritView.SETTINGS;
}
export interface GenerateUrlDiffViewParameters {
view: GerritView.DIFF;
changeNum: NumericChangeId;
project: RepoName;
path?: string;
patchNum?: RevisionPatchSetNum;
basePatchNum?: BasePatchSetNum;
lineNum?: number | string;
leftSide?: boolean;
commentId?: UrlEncodedCommentId;
// TODO(TS): remove - property is set but never used
commentLink?: boolean;
}
export type GenerateUrlParameters =
| GenerateUrlSearchViewParameters
| GenerateUrlChangeViewParameters
| GenerateUrlRepoViewParameters
| GenerateUrlDashboardViewParameters
| GenerateUrlGroupViewParameters
| GenerateUrlEditViewParameters
| GenerateUrlRootViewParameters
| GenerateUrlSettingsViewParameters
| GenerateUrlDiffViewParameters;
export function isGenerateUrlChangeViewParameters(
x: GenerateUrlParameters
): x is GenerateUrlChangeViewParameters {
return x.view === GerritView.CHANGE;
}
export function isGenerateUrlEditViewParameters(
x: GenerateUrlParameters
): x is GenerateUrlEditViewParameters {
return x.view === GerritView.EDIT;
}
export function isGenerateUrlDiffViewParameters(
x: GenerateUrlParameters
): x is GenerateUrlDiffViewParameters {
return x.view === GerritView.DIFF;
}
export const TEST_ONLY = {
getPatchRangeExpression,
};
export function generateUrl(params: GenerateUrlParameters) {
const base = getBaseUrl();
let url = '';
if (params.view === GerritView.SEARCH) {
url = generateSearchUrl(params);
} else if (params.view === GerritView.CHANGE) {
url = generateChangeUrl(params);
} else if (params.view === GerritView.DASHBOARD) {
url = generateDashboardUrl(params);
} else if (
params.view === GerritView.DIFF ||
params.view === GerritView.EDIT
) {
url = generateDiffOrEditUrl(params);
} else if (params.view === GerritView.GROUP) {
url = generateGroupUrl(params);
} else if (params.view === GerritView.REPO) {
url = generateRepoUrl(params);
} else if (params.view === GerritView.ROOT) {
url = '/';
} else if (params.view === GerritView.SETTINGS) {
url = generateSettingsUrl();
} else {
assertNever(params, "Can't generate");
}
return base + url;
}
/**
* Given an object of parameters, potentially including a `patchNum` or a
* `basePatchNum` or both, return a string representation of that range. If
* no range is indicated in the params, the empty string is returned.
*/
function getPatchRangeExpression(params: PatchRangeParams) {
let range = '';
if (params.patchNum) {
range = `${params.patchNum}`;
}
if (params.basePatchNum && params.basePatchNum !== PARENT) {
range = `${params.basePatchNum}..${range}`;
}
return range;
}
function generateChangeUrl(params: GenerateUrlChangeViewParameters) {
let range = getPatchRangeExpression(params);
if (range.length) {
range = '/' + range;
}
let suffix = `${range}`;
const queries = [];
if (params.forceReload) {
queries.push('forceReload=true');
}
if (params.openReplyDialog) {
queries.push('openReplyDialog=true');
}
if (params.usp) {
queries.push(`usp=${params.usp}`);
}
if (params.edit) {
suffix += ',edit';
}
if (params.commentId) {
suffix = suffix + `/comments/${params.commentId}`;
}
if (queries.length > 0) {
suffix += '?' + queries.join('&');
}
if (params.messageHash) {
suffix += params.messageHash;
}
if (params.project) {
const encodedProject = encodeURL(params.project, true);
return `/c/${encodedProject}/+/${params.changeNum}${suffix}`;
} else {
return `/c/${params.changeNum}${suffix}`;
}
}
const REPO_TOKEN_PATTERN = /\${(project|repo)}/g;
function sectionsToEncodedParams(
sections: DashboardSection[],
repoName?: RepoName
) {
return sections.map(section => {
// If there is a repo name provided, make sure to substitute it into the
// ${repo} (or legacy ${project}) query tokens.
const query = repoName
? section.query.replace(REPO_TOKEN_PATTERN, repoName)
: section.query;
return encodeURIComponent(section.name) + '=' + encodeURIComponent(query);
});
}
function generateDashboardUrl(params: GenerateUrlDashboardViewParameters) {
const repoName = params.repo || params.project || undefined;
if (params.sections) {
// Custom dashboard.
const queryParams = sectionsToEncodedParams(params.sections, repoName);
if (params.title) {
queryParams.push('title=' + encodeURIComponent(params.title));
}
const user = params.user ? params.user : '';
return `/dashboard/${user}?${queryParams.join('&')}`;
} else if (repoName) {
// Project dashboard.
const encodedRepo = encodeURL(repoName, true);
return `/p/${encodedRepo}/+/dashboard/${params.dashboard}`;
} else {
// User dashboard.
return `/dashboard/${params.user || 'self'}`;
}
}
function generateSearchUrl(params: GenerateUrlSearchViewParameters) {
let offsetExpr = '';
if (params.offset && params.offset > 0) {
offsetExpr = `,${params.offset}`;
}
if (params.query) {
return '/q/' + encodeURL(params.query, true) + offsetExpr;
}
const operators: string[] = [];
if (params.owner) {
operators.push('owner:' + encodeURL(params.owner, false));
}
if (params.project) {
operators.push('project:' + encodeURL(params.project, false));
}
if (params.branch) {
operators.push('branch:' + encodeURL(params.branch, false));
}
if (params.topic) {
operators.push(
'topic:' +
addQuotesWhen(
encodeURL(params.topic, false),
/[\s:]/.test(params.topic)
)
);
}
if (params.hashtag) {
operators.push(
'hashtag:' +
addQuotesWhen(
encodeURL(params.hashtag.toLowerCase(), false),
/[\s:]/.test(params.hashtag)
)
);
}
if (params.statuses) {
if (params.statuses.length === 1) {
operators.push('status:' + encodeURL(params.statuses[0], false));
} else if (params.statuses.length > 1) {
operators.push(
'(' +
params.statuses
.map(s => `status:${encodeURL(s, false)}`)
.join(' OR ') +
')'
);
}
}
return '/q/' + operators.join('+') + offsetExpr;
}
function generateDiffOrEditUrl(
params: GenerateUrlDiffViewParameters | GenerateUrlEditViewParameters
) {
let range = getPatchRangeExpression(params);
if (range.length) {
range = '/' + range;
}
let suffix = `${range}/${encodeURL(params.path || '', true)}`;
if (params.view === GerritView.EDIT) {
suffix += ',edit';
}
if (params.lineNum) {
suffix += '#';
if (isGenerateUrlDiffViewParameters(params) && params.leftSide) {
suffix += 'b';
}
suffix += params.lineNum;
}
if (isGenerateUrlDiffViewParameters(params) && params.commentId) {
suffix = `/comment/${params.commentId}` + suffix;
}
if (params.project) {
const encodedProject = encodeURL(params.project, true);
return `/c/${encodedProject}/+/${params.changeNum}${suffix}`;
} else {
return `/c/${params.changeNum}${suffix}`;
}
}
function generateGroupUrl(params: GenerateUrlGroupViewParameters) {
let url = `/admin/groups/${encodeURL(`${params.groupId}`, true)}`;
if (params.detail === GroupDetailView.MEMBERS) {
url += ',members';
} else if (params.detail === GroupDetailView.LOG) {
url += ',audit-log';
}
return url;
}
function generateRepoUrl(params: GenerateUrlRepoViewParameters) {
let url = `/admin/repos/${encodeURL(`${params.repoName}`, true)}`;
if (params.detail === RepoDetailView.GENERAL) {
url += ',general';
} else if (params.detail === RepoDetailView.ACCESS) {
url += ',access';
} else if (params.detail === RepoDetailView.BRANCHES) {
url += ',branches';
} else if (params.detail === RepoDetailView.TAGS) {
url += ',tags';
} else if (params.detail === RepoDetailView.COMMANDS) {
url += ',commands';
} else if (params.detail === RepoDetailView.DASHBOARDS) {
url += ',dashboards';
}
return url;
}
function generateSettingsUrl() {
return '/settings';
}