Skip to content

Commit

Permalink
Support searching by commit for results.webkit.org
Browse files Browse the repository at this point in the history
https://bugs.webkit.org/show_bug.cgi?id=256809

Reviewed by Jonathan Bedard.

* Tools/Scripts/libraries/resultsdbpy/resultsdbpy/view/static/js/timeline.js: Add commit search input and jump to next regess point button
* Tools/Scripts/libraries/resultsdbpy/resultsdbpy/view/static/library/css/webkit.css: Add new highlight color and make timeline series seamless
* Tools/Scripts/libraries/resultsdbpy/resultsdbpy/view/static/library/js/components/TimelineComponents.js: Add new search API, highlight and jump to highlighted scale, add new search dot and hightlight the dot's scale API

Canonical link: https://commits.webkit.org/270406@main
  • Loading branch information
facetothefate authored and JonWBedard committed Nov 8, 2023
1 parent 0deb2fd commit 67030da
Show file tree
Hide file tree
Showing 7 changed files with 347 additions and 48 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,50 @@ function Drawer(controls = [], onCollapseChange) {
</div>`;
}

function CommitSearchBar(onSearchAction = null) {
const searchInputRef = REF.createRef({
onElementUnmount: () => {
window.removeEventListener("keypress", searchHotKeyFunction);
}
});
const searchHotKeyFunction = (e) => {
if (e.key == "f" && ( e.ctrlKey || e.metaKey )) {
e.preventDefault();
searchInputRef.element.focus();
}
};
const searchInputEventStream = searchInputRef.fromEvent("keyup");
searchInputEventStream.action((e) => {
const searchValue = searchInputRef.element.value;
if (e.key === "Enter") {
if (onSearchAction)
onSearchAction(searchValue);
}
});

const searchButtonRef = REF.createRef({});
const searchButtonClikEventStream = searchButtonRef.fromEvent("click");
searchButtonClikEventStream.action((e) => {
const searchValue = searchInputRef.element.value;
if (onSearchAction)
onSearchAction(searchValue);
});

window.addEventListener("keypress", searchHotKeyFunction);

return `<div class="input">
<div class="row">
<div class="input col-9">
<input type="text" ref="${searchInputRef}" autocomplete="off" autocapitalize="none" required/>
<label>Search commit</label>
</div>
<button class="button col-3 primary" ref="${searchButtonRef}">
<img src="library/icons/search.svg" style="height: var(--largeSize); filter: invert(1);">
</button>
</div>
</div>`;
}

let configurations = []
let configurationsDefinedCallbacks = [];
function refreshConfigurations() {
Expand Down Expand Up @@ -400,4 +444,4 @@ function CommitRepresentation(callback) {
</div>`;
}

export {Drawer, BranchSelector, ConfigurationSelectors, LimitSlider, CommitRepresentation};
export {Drawer, BranchSelector, ConfigurationSelectors, LimitSlider, CommitRepresentation, CommitSearchBar};
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,7 @@ function xAxisFromScale(scale, repository, updatesArray, isTop=false, viewport=n
return Timeline.CanvasXAxisComponent(scaleForRepository(scale), {
isTop: isTop,
height: 130,
compareFunc: (a, b) => {return b.uuid - a.uuid;},
onScaleClick: onScaleClick,
onScaleEnter: (node, event, canvas) => {
const scrollDelta = document.documentElement.scrollTop || document.body.scrollTop;
Expand Down Expand Up @@ -219,6 +220,11 @@ function xAxisFromScale(scale, repository, updatesArray, isTop=false, viewport=n
},
getLabelFunc: (commit) => {return commit && commit.label ? commit.label() : '?';},
getScaleFunc: (commit) => commit.uuid,
getDotScaleFunc: (value) => {
if (value && value.uuid)
return {uuid: value.uuid};
return {};
},
exporter: (updateFunction) => {
updatesArray.push((scale) => {updateFunction(scaleForRepository(scale));});
},
Expand Down Expand Up @@ -354,7 +360,7 @@ function combineResults() {
}

class TimelineFromEndpoint {
constructor(endpoint, suite = null, test = null, viewport = null) {
constructor(endpoint, {suite = null, test = null, viewport = null, searchEvent = null}) {
this.endpoint = endpoint;
this.displayAllCommits = true;

Expand All @@ -376,6 +382,8 @@ class TimelineFromEndpoint {
const self = this;

this.latestDispatch = Date.now();
searchEvent.action( (val) => this.onSearch(val));

this.ref = REF.createRef({
state: {},
onStateUpdate: (element, state) => {
Expand Down Expand Up @@ -404,7 +412,7 @@ class TimelineFromEndpoint {
this.selectedDotsButtonGroupRef.setState({show: false});
const params = queryToParams(document.URL.split('?')[1]);
const commits = commitsForResults(this.results, params.limit ? parseInt(params.limit[params.limit.length - 1]) : DEFAULT_LIMIT, this.allCommits);
const scale = scaleForCommits(commits);
this.scale = scaleForCommits(commits);

const newRepositories = repositoriesForCommits(commits);
let haveNewRepos = this.repositories.length !== newRepositories.length;
Expand All @@ -416,16 +424,16 @@ class TimelineFromEndpoint {
let components = [];

newRepositories.forEach(repository => {
components.push(xAxisFromScale(scale, repository, this.xaxisUpdates, top, this.viewport));
components.push(xAxisFromScale(this.scale, repository, this.xaxisUpdates, top, this.viewport));
top = false;
});

this.timelineUpdate(components);
this.repositories = newRepositories;
}

this.updates.forEach(func => {func(scale);})
this.xaxisUpdates.forEach(func => {func(scale);});
this.updates.forEach(func => {func(this.scale);})
this.xaxisUpdates.forEach(func => {func(this.scale);});
}
rerender() {
const params = queryToParams(document.URL.split('?')[1]);
Expand Down Expand Up @@ -635,11 +643,31 @@ class TimelineFromEndpoint {
</div>`);
}

onSearch(searchValue) {
for (let currentScale of this.scale) {
let found = false;
for (let repo of this.repositories) {
if (!currentScale[repo]) {
continue;
}
if (currentScale[repo].hash && currentScale[repo].hash.indexOf(searchValue) === 0 ||
currentScale[repo].identifier && currentScale[repo].identifier.indexOf(searchValue) === 0) {
found = true;
this.searchScale(currentScale[repo]);
break;
}
}
if (found)
break;
}
};

render(limit) {
const branch = queryToParams(document.URL.split('?')[1]).branch;
const self = this;
const commits = commitsForResults(this.results, limit, this.allCommits);
const scale = scaleForCommits(commits);
this.scale = scaleForCommits(commits);
this.repositories = repositoriesForCommits(commits);

this.selectedDotsButtonGroupRef = REF.createRef({
state: {
Expand Down Expand Up @@ -703,6 +731,9 @@ class TimelineFromEndpoint {
return {};
},
compareFunc: (a, b) => {return b.uuid - a.uuid;},
shouldHighLightFunc: (myScale, commit) => {
return this.repositories.filter(repo_id => myScale[repo_id].uuid === commit.uuid).length >= 1;
},
renderFactory: (drawDot) => (data, context, x, y) => {
if (!data)
return drawDot(context, x, y, true);
Expand Down Expand Up @@ -880,6 +911,7 @@ class TimelineFromEndpoint {
}

let children = [];
let allConfigResults = [];
this.configurations.forEach(configuration => {
if (!this.results[configuration.toKey()] || Object.keys(this.results[configuration.toKey()]).length === 0)
return;
Expand Down Expand Up @@ -921,9 +953,10 @@ class TimelineFromEndpoint {
queueParams['branch'];
let myTimeline = Timeline.SeriesWithHeaderComponent(
`${childrenConfigsBySDK[config.toKey()].length > 1 ? ' | ' : ''}<a href="/urls/queue?${paramsToQuery(queueParams)}" target="_blank">${config}</a>`,
Timeline.CanvasSeriesComponent(resultsForConfig, scale, {
Timeline.CanvasSeriesComponent(resultsForConfig, this.scale, {
getScaleFunc: options.getScaleFunc,
compareFunc: options.compareFunc,
shouldHighLightFunc: options.shouldHighLightFunc,
renderFactory: options.renderFactory,
exporter: options.exporter,
onDotClick: onDotClickFactory(config),
Expand All @@ -943,9 +976,10 @@ class TimelineFromEndpoint {
childrenConfigsBySDK[config.toKey()].forEach(sdkConfig => {
timelinesBySDK.push(
Timeline.SeriesWithHeaderComponent(`${Configuration.integerToVersion(sdkConfig.version)} (${sdkConfig.sdk})`,
Timeline.CanvasSeriesComponent(resultsByKey[sdkConfig.toKey()], scale, {
Timeline.CanvasSeriesComponent(resultsByKey[sdkConfig.toKey()], this.scale, {
getScaleFunc: options.getScaleFunc,
compareFunc: options.compareFunc,
shouldHighLightFunc: options.shouldHighLightFunc,
renderFactory: options.renderFactory,
exporter: options.exporter,
onDotClick: onDotClickFactory(sdkConfig),
Expand Down Expand Up @@ -974,12 +1008,14 @@ class TimelineFromEndpoint {
return;
}

allConfigResults = combineResults(allConfigResults, allResults);
children.push(
Timeline.ExpandableSeriesWithHeaderExpanderComponent(
Timeline.SeriesWithHeaderComponent(` ${configuration}`,
Timeline.CanvasSeriesComponent(allResults, scale, {
Timeline.CanvasSeriesComponent(allResults, this.scale, {
getScaleFunc: options.getScaleFunc,
compareFunc: options.compareFunc,
shouldHighLightFunc: options.shouldHighLightFunc,
renderFactory: options.renderFactory,
onDotClick: onDotClickFactory(configuration),
onDotEnter: onDotEnterFactory(configuration),
Expand All @@ -1001,15 +1037,16 @@ class TimelineFromEndpoint {
self.xaxisUpdates = [];
this.repositories = repositoriesForCommits(commits);
this.repositories.forEach(repository => {
const xAxisComponent = xAxisFromScale(scale, repository, self.xaxisUpdates, top, self.viewport);
const xAxisComponent = xAxisFromScale(this.scale, repository, self.xaxisUpdates, top, self.viewport);
if (top)
children.unshift(xAxisComponent);
else
children.push(xAxisComponent);
top = false;
});

const composer = FP.composer(FP.currying((updateTimeline, notifyRerender) => {
this.searchScale = null;
let searchDot = null;
const composer = FP.composer(FP.currying((updateTimeline, notifyRerender, exportedSearchScale, exportedSearchDot) => {
self.timelineUpdate = (xAxises) => {
children.splice(0, 1);
if (self.repositories.length > 1)
Expand All @@ -1026,21 +1063,107 @@ class TimelineFromEndpoint {
updateTimeline(children);
};
self.notifyRerender = notifyRerender;
this.searchScale = exportedSearchScale;
searchDot = exportedSearchDot;
}));
return Timeline.CanvasContainer({
customizedLayer: `<div style="position:absolute" ref="${this.selectedDotsButtonGroupRef}"></div>`,
onSelecting: (e) => {
this.selectedDotsButtonGroupRef.setState({show: false});

let currentResultIndex = 0;
const jumpNextRegressPoint = () => {
const lastResultStatus = this.getTestResultStatus(allConfigResults[currentResultIndex], InvestigateDrawer.willFilterExpected);
for(let i = currentResultIndex + 1; i < allConfigResults.length; i++) {
const currentTestStatus = this.getTestResultStatus(allConfigResults[i], InvestigateDrawer.willFilterExpected);
if (currentTestStatus.failureType !== lastResultStatus.failureType || currentTestStatus.failureNumber !== lastResultStatus.failureNumber) {
currentResultIndex = i;
searchDot(allConfigResults[i]);
break;
}
}
};

const nextRegressButtonRef = REF.createRef({});
const nextRegressButtonClickEventStream = nextRegressButtonRef.fromEvent("click");
nextRegressButtonClickEventStream.action((e) => {
jumpNextRegressPoint();
});

const searchBarRef = REF.createRef({
state: {
float: false
},
onSelect: (dots, selectedDotRect, seriesRect, e) => {
// this api will called with selected dots for each series once, and compose the selectedDotRect during the call
this.selectedDotsButtonGroupRef.setState({show: true, top: selectedDotRect.bottom, left: selectedDotRect.right});
onStateUpdate: (element, stateDiff, state) => {
if (stateDiff.float === state.float)
return;
const float = ("float" in stateDiff) ? stateDiff.float : state.float;
if (float !== false) {
element.style.position = "fixed";
element.style.top = `60px`;
} else {
element.style.removeProperty("position");
element.style.removeProperty("top");
}
}
});
const placeHolderRef = REF.createRef({
state: {
show: false
},
onSelectionScroll: (dots, selectedDotRect) => {
// this api will called with selected dots for each series once, and compose the selectedDotRect during the call
this.selectedDotsButtonGroupRef.setState({top: selectedDotRect.bottom, left: selectedDotRect.right});
onStateUpdate: (element, stateDiff, state) => {
const show = ("show" in stateDiff) ? stateDiff.show : state.show;
if (show)
element.style.display = "block";
else
element.style.display = "none";
}
});

const onScrollAction = (e) => {
const rect = containnerRef.element.getBoundingClientRect();
if (rect.top < 60 && 0 - rect.top < rect.height - 120) {
searchBarRef.setState({float: true});
placeHolderRef.setState({show: true});
} else {
searchBarRef.setState({float: false});
placeHolderRef.setState({show: false});
}
};
const onResizeAction = (e) => {
searchBarRef.element.style.width = `${containnerRef.element.getBoundingClientRect().width}px`;
};
const containnerRef = REF.createRef({
onElementMount: (element) => {
window.addEventListener("scroll", onScrollAction);
window.addEventListener("resize", onResizeAction);
onResizeAction();
},
}, composer, ...children);
onElementUnmount: (element) => {
window.removeEventListener("scroll", onScrollAction);
window.addEventListener("resize", onResizeAction);
}
});
return `<div ref="${containnerRef}" style="position: relative">
<div ref="${searchBarRef}" style="width:100%; background: var(--blurBackgroundColor); -webkit-backdrop-filter: blur(5px); z-index:10">
<button class="button" ref="${nextRegressButtonRef}">
Next Regress Point
</button>
</div>
<div class="row" ref="${placeHolderRef}">
<div class="col-12" style="height: 40px; padding: 0"></div>
</div>
${Timeline.CanvasContainer({
customizedLayer: `<div style="position:absolute" ref="${this.selectedDotsButtonGroupRef}"></div>`,
onSelecting: (e) => {
this.selectedDotsButtonGroupRef.setState({show: false});
},
onSelect: (dots, selectedDotRect, seriesRect, e) => {
// this api will called with selected dots for each series once, and compose the selectedDotRect during the call
this.selectedDotsButtonGroupRef.setState({show: true, top: selectedDotRect.bottom, left: selectedDotRect.right});
},
onSelectionScroll: (dots, selectedDotRect) => {
// this api will called with selected dots for each series once, and compose the selectedDotRect during the call
this.selectedDotsButtonGroupRef.setState({top: selectedDotRect.bottom, left: selectedDotRect.right});
},
}, composer, ...children)}
</div>`;
}
}

Expand Down
Loading

0 comments on commit 67030da

Please sign in to comment.