blob: ac4bf590b14d9086b0a34cc81905a2267c0699fa [file] [log] [blame]
/**
* @module modules/commit-range-sk
* @description <h2><code>commit-range-sk</code></h2>
*
* Displays a link that describes a range of commits in a repo. This element
* uses the global `window.perf.commit_range_url`, which can be set on Perf via
* the command line.
*/
import { html } from 'lit/html.js';
import { define } from '../../../elements-sk/modules/define';
import { ElementSk } from '../../../infra-sk/modules/ElementSk';
import { lookupCids } from '../cid/cid';
import { MISSING_DATA_SENTINEL } from '../const/const';
import { ColumnHeader, CommitNumber } from '../json';
import '../window/window';
// Converts CommitNumbers to Git hashes.
type commitNumberToHashes = (commitNumbers: CommitNumber[]) => Promise<string[]>;
/** The default implementation for commitNumberToHashes run the commit numbers
* through cid lookup to get the hashes by making a request to the server.
*/
const defaultcommitNumberToHashes = async (cids: CommitNumber[]): Promise<string[]> => {
const json = await lookupCids(cids);
return [json.commitSlice![0].hash, json.commitSlice![1].hash];
};
export class CommitRangeSk extends ElementSk {
private _trace: number[] = [];
private _commitIndex: number = -1;
private _header: (ColumnHeader | null)[] | null = null;
private _htmlTemplate = html``;
private _commitIds: [CommitNumber, CommitNumber] | null = null;
private _hashes: string[] | null = null;
private _autoload: boolean = true;
// commitNumberToHashes can be replaced to make testing easier.
private commitNumberToHashes: commitNumberToHashes = async (
cids: CommitNumber[]
): Promise<string[]> => {
if (this._autoload) {
const hashes = await defaultcommitNumberToHashes(cids);
return hashes;
}
return [];
};
constructor() {
super(CommitRangeSk.template);
}
private static template = (ele: CommitRangeSk) => html`${ele.htmlTemplate}`;
connectedCallback(): void {
super.connectedCallback();
this._upgradeProperty('trace');
this._upgradeProperty('commitIndex');
this._upgradeProperty('header');
this._render();
}
reset(): void {
this._commitIndex = -1;
this._trace = [];
this._header = null;
this._htmlTemplate = html``;
this._commitIds = null;
this._hashes = null;
this._render();
}
clear(): void {
this._htmlTemplate = html``;
this._render();
}
/** Check start and end commits and determines if delta is more than 1.
* If so, it is a range and returns true.
* If not, it is a single commit and returns false.
* Returns false if no commits are set.
* @returns boolean
*/
isRange(): boolean | null {
if (!this._commitIds) {
return null;
}
if (this._commitIds[0] >= this._commitIds[1]) {
return false;
}
if (this._commitIds[0] + 1 === this._commitIds[1]) {
return false;
}
return true;
}
/**
* Recalculates the link based on the current state of the object.
* If there is not enough information to build the link, it clears the
* current link.
*
* Supported URL formats for commit_range_url in k8s-config yaml files:
* https://<googlesource_repo>/+log/{begin}..{end}
* https://<github_repo>/commits/{end}
*
* GitHub does not support ranges in the URL.
* When only one commit is being referenced, the {begin}.. is removed
* from the URL.
*/
async recalcLink(): Promise<void> {
if (window.perf.commit_range_url === '' || this._commitIndex === -1) {
this.clear();
return;
}
this._commitIds = this.setCommitIds(this._commitIndex);
if (!this._commitIds || this._commitIds.length !== 2) {
this.clear();
return;
}
try {
let text = `${this._commitIds[1]}`;
// Check if there are no points between start and end.
const isRange = this.isRange();
if (isRange) {
// Add +1 to the previous commit to only show commits after previous.
text = `${this._commitIds[0] + 1} - ${this._commitIds[1]}`;
}
this.htmlTemplate = html`${text}`;
if (!this.hashes) {
// Run the commit numbers through cid lookup to get the hashes.
this.hashes = await this.commitNumberToHashes(this._commitIds);
}
// If we have the hashes, then we can build the link.
if (this.hashes && this.hashes.length > 1) {
let url = window.perf.commit_range_url;
// Always replace {end} with the second hash.
if (url.includes('{end}')) {
url = url.replace('{end}', this.hashes[1]);
}
if (isRange) {
// Handle range URLs (Googlesource)
if (url.includes('{begin}')) {
url = url.replace('{begin}', this.hashes[0]);
}
} else {
// Handle single commit scenarios
if (url.includes('+log/{begin}..')) {
// Googlesource style: transform to single commit view
url = url.replace('+log/{begin}..', '+/');
} else {
// Fallback for any other template, remove {begin} if it exists
if (url.includes('{begin}')) {
url = url.replace('{begin}', '');
}
// If GitHub, show short hash instead of commit number.
if (url.includes('github')) {
text = this.hashes[1].substring(0, 7);
}
}
}
this.htmlTemplate = html`<a href="${url}" target="_blank">${text}</a>`;
// Ensure element is connected to tooltip before dispatching event.
if (this._connected) {
this.dispatchEvent(
new CustomEvent('commit-range-changed', {
bubbles: true, // Allows parent elements to catch the event.
composed: true, // Allows event to cross shadow DOM boundaries.
})
);
}
}
} catch (error) {
console.log(error);
this.clear();
}
}
/** A single trace. */
get trace(): number[] {
return this._trace;
}
set trace(val: number[]) {
this._trace = val;
this.recalcLink();
}
/** An index into trace, the location of the commit being referenced. */
get commitIndex(): number {
return this._commitIndex;
}
set commitIndex(val: number) {
this._commitIndex = val;
this.recalcLink();
}
/** The ColumnHeader of the DataFrame that contains the trace. */
get header(): (ColumnHeader | null)[] | null {
return this._header;
}
set header(val: (ColumnHeader | null)[] | null) {
this._header = val;
this.recalcLink();
}
/** The hashes of the commits. */
get hashes(): string[] | null {
return this._hashes;
}
set hashes(val: string[] | null) {
if (val !== null && val.length > 1) {
// Only set the hashes if there are two hashes.
this._hashes = val;
this.recalcLink();
}
}
set htmlTemplate(val: any) {
// If the template is the same, don't re-render.
if (this._htmlTemplate !== val) {
this._htmlTemplate = val;
this._render();
}
}
get htmlTemplate() {
return this._htmlTemplate;
}
set autoload(val: boolean) {
this._autoload = val;
}
setCommitIds(commitIndex: number): [CommitNumber, CommitNumber] | null {
if (this._trace.length === 0 || this._header === null) {
this.clear();
return null;
}
// First the previous commit that has data.
let prevCommit = commitIndex - 1;
while (prevCommit > 0 && this._trace[prevCommit] === MISSING_DATA_SENTINEL) {
prevCommit -= 1;
}
// If we don't find a second commit then we can't present the information.
if (prevCommit < 0) {
this.clear();
return null;
}
const startOffset = this._header[prevCommit]?.offset ?? null;
const endOffset = this._header[commitIndex]?.offset ?? null;
if (startOffset === null || endOffset === null) {
this.clear();
return null;
}
return [startOffset, endOffset];
}
}
define('commit-range-sk', CommitRangeSk);