-
+
+
+
-
-
-
-
-
- ${%No builds}
-
-
-
+
+
+ ${%No builds}
+
-
+
+
-
+
+
+
+ ${%Newer builds}
+
+
+
+ ${%Older builds}
+
+
+
-
-
-
+
+
+
+
-
diff --git a/core/src/main/resources/hudson/widgets/HistoryWidget/index.properties b/core/src/main/resources/hudson/widgets/HistoryWidget/index.properties
index 160484221909..f88fe7c51e93 100644
--- a/core/src/main/resources/hudson/widgets/HistoryWidget/index.properties
+++ b/core/src/main/resources/hudson/widgets/HistoryWidget/index.properties
@@ -1 +1 @@
-find=Filter...
+find=Filter
diff --git a/core/src/main/resources/jenkins/widgets/HistoryPageFilter/ajaxBuildHistory.jelly b/core/src/main/resources/jenkins/widgets/HistoryPageFilter/ajaxBuildHistory.jelly
index 6fdf095c515a..bb5d5afac359 100644
--- a/core/src/main/resources/jenkins/widgets/HistoryPageFilter/ajaxBuildHistory.jelly
+++ b/core/src/main/resources/jenkins/widgets/HistoryPageFilter/ajaxBuildHistory.jelly
@@ -22,20 +22,16 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
-->
-
-
-
-
-
-
-
-
-
+
+
+
+
+
-
\ No newline at end of file
+
diff --git a/core/src/main/resources/jenkins/widgets/HistoryPageFilter/queue-items.jelly b/core/src/main/resources/jenkins/widgets/HistoryPageFilter/queue-items.jelly
index 4f51055a7a98..ea07c6114279 100644
--- a/core/src/main/resources/jenkins/widgets/HistoryPageFilter/queue-items.jelly
+++ b/core/src/main/resources/jenkins/widgets/HistoryPageFilter/queue-items.jelly
@@ -28,50 +28,43 @@ THE SOFTWARE.
-->
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- #${queuedItems.size()==1 ? it.widget.owner.nextBuildNumber
- : it.widget.owner.nextBuildNumber+queuedItems.size()-i-1}
-
-
-
-
-
-
- (${%pending}— )
-
-
- (${%pending})
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+ ${%Pending}
+
+
+
+
+
+
+
+
+
+
+
+ #${queuedItems.size() == 1 ? it.widget.owner.nextBuildNumber : it.widget.owner.nextBuildNumber+queuedItems.size()-i-1}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/core/src/main/resources/lib/layout/card.jelly b/core/src/main/resources/lib/layout/card.jelly
new file mode 100644
index 000000000000..2c6845738227
--- /dev/null
+++ b/core/src/main/resources/lib/layout/card.jelly
@@ -0,0 +1,58 @@
+
+
+
+
+
+
+ Title of the card
+
+
+ Optional ID for the card
+
+
+ Shows controls in the top right of the card
+
+
+ Shows the expand icon if set, navigates to the value provided on click
+
+
+
+
+
diff --git a/test/src/test/java/hudson/model/RunTest.java b/test/src/test/java/hudson/model/RunTest.java
index c59245c78c2c..b6a145857020 100644
--- a/test/src/test/java/hudson/model/RunTest.java
+++ b/test/src/test/java/hudson/model/RunTest.java
@@ -157,7 +157,7 @@ private void ensureXssIsPrevented(FreeStyleProject upProject, String validationP
HtmlPage htmlPage = wc.goTo(upProject.getUrl());
// trigger the tooltip display
- htmlPage.executeJavaScript("document.querySelector('#buildHistory table .build-badge svg')._tippy.show()");
+ htmlPage.executeJavaScript("document.querySelector('#jenkins-build-history .app-builds-container__item__inner__controls svg')._tippy.show()");
wc.waitForBackgroundJavaScript(500);
ScriptResult result = htmlPage.executeJavaScript("document.querySelector('.tippy-content').innerHTML;");
Object jsResult = result.getJavaScriptResult();
diff --git a/test/src/test/java/hudson/scm/AbstractScmTagActionTest.java b/test/src/test/java/hudson/scm/AbstractScmTagActionTest.java
index 4fe53f261657..1df03bfacd64 100644
--- a/test/src/test/java/hudson/scm/AbstractScmTagActionTest.java
+++ b/test/src/test/java/hudson/scm/AbstractScmTagActionTest.java
@@ -66,7 +66,7 @@ private String buildAndExtractTooltipAttribute(FreeStyleProject p) throws Except
HtmlPage page = wc.getPage(p);
- DomElement buildHistory = page.getElementById("buildHistory");
+ DomElement buildHistory = page.getElementById("buildHistoryPage");
DomNodeList
imgs = buildHistory.getElementsByTagName("img");
HtmlImage tagImage = (HtmlImage) imgs.stream()
.filter(i -> i.getAttribute("class").contains("icon-save"))
diff --git a/war/src/main/js/components/dropdowns/jumplists.js b/war/src/main/js/components/dropdowns/jumplists.js
index 3297da22e9ea..f831f452bafe 100644
--- a/war/src/main/js/components/dropdowns/jumplists.js
+++ b/war/src/main/js/components/dropdowns/jumplists.js
@@ -31,7 +31,7 @@ function generateJumplistAccessors() {
*/
function generateDropdowns() {
behaviorShim.specify(
- "li.children, #menuSelector, .jenkins-menu-dropdown-chevron",
+ "li.children, .jenkins-jumplist-link, #menuSelector, .jenkins-menu-dropdown-chevron",
"-dropdown-",
1000,
(element) =>
diff --git a/war/src/main/js/filter-build-history.js b/war/src/main/js/filter-build-history.js
deleted file mode 100644
index 06a83a5b503d..000000000000
--- a/war/src/main/js/filter-build-history.js
+++ /dev/null
@@ -1,590 +0,0 @@
-import debounce from "lodash/debounce";
-
-const buildHistoryContainer = document.getElementById("buildHistory");
-const pageSearchInputContainer = buildHistoryContainer.querySelector(
- ".build-search-row .jenkins-search",
-);
-const pageSearchInput = buildHistoryContainer.querySelector(
- ".build-search-row input",
-);
-const buildHistoryPage = document.getElementById("buildHistoryPage");
-const properties = document.getElementById("properties");
-const ajaxUrl = buildHistoryPage.getAttribute("page-ajax");
-const nextBuild = properties.getAttribute("page-next-build");
-const noBuildsBanner = document.getElementById("no-builds");
-
-const sidePanel = document.getElementById("side-panel");
-const buildHistoryPageNav = document.getElementById("buildHistoryPageNav");
-
-const pageOne = buildHistoryPageNav.querySelector(".pageOne");
-const pageUp = buildHistoryPageNav.querySelector(".pageUp");
-const pageDown = buildHistoryPageNav.querySelector(".pageDown");
-
-const leftRightPadding = 4;
-const updateBuildsRefreshInterval = 5000;
-
-function updateBuilds(params) {
- if (isPageVisible()) {
- fetch(ajaxUrl + toQueryString(params), {
- headers: {
- n: buildHistoryContainer.headers[1],
- },
- }).then((rsp) => {
- if (rsp.ok) {
- rsp.text().then((responseText) => {
- var dataTable = getDataTable(buildHistoryContainer);
- var rows = dataTable.rows;
-
- // Check there are no existing rows (except the search bar) before showing the no builds banner
- if (
- rows.length <= 1 &&
- responseText === ''
- ) {
- noBuildsBanner.style.display = "block";
- if (
- typeof params === "object" &&
- "search" in params &&
- params.search !== ""
- ) {
- pageSearchInputContainer.classList.remove("jenkins-hidden");
- } else {
- pageSearchInputContainer.classList.add("jenkins-hidden");
- }
- } else {
- noBuildsBanner.style.display = "none";
- pageSearchInputContainer.classList.remove("jenkins-hidden");
- }
-
- //delete rows with transitive data
- var firstBuildRow = 0;
- if (rows[firstBuildRow].classList.contains("build-search-row")) {
- firstBuildRow++;
- }
- while (
- rows.length > 1 &&
- rows[firstBuildRow].classList.contains("transitive")
- ) {
- rows[firstBuildRow].remove();
- }
-
- // insert new rows
- var div = document.createElement("div");
- div.innerHTML = responseText;
- Behaviour.applySubtree(div);
-
- var pivot = rows[firstBuildRow];
- var newDataTable = getDataTable(div);
- var newRows = newDataTable.rows;
- while (newRows.length > 0) {
- if (pivot !== undefined) {
- // The data table has rows. Insert before a "pivot" row (first row).
- pivot.parentNode.insertBefore(newRows[0], pivot);
- } else {
- // The data table has no rows. In this case, we just add all new rows directly to the
- // table, one after the other i.e. we don't insert before a "pivot" row (first row).
- dataTable
- .getElementsByTagName("tbody")[0]
- .appendChild(newRows[0]);
- }
- }
-
- if (newDataTable.classList.contains("hasPageData")) {
- buildHistoryPage.setAttribute(
- "page-entry-newest",
- newDataTable.getAttribute("page-entry-newest"),
- );
- }
-
- // next update
- buildHistoryContainer.headers = ["n", rsp.headers.get("n")];
- checkAllRowCellOverflows();
- createRefreshTimeout(params);
- });
- }
- });
- } else {
- createRefreshTimeout(params);
- }
-}
-
-var buildRefreshTimeout;
-function createRefreshTimeout(params) {
- cancelRefreshTimeout();
- buildRefreshTimeout = window.setTimeout(
- () => updateBuilds(params),
- updateBuildsRefreshInterval,
- );
-}
-
-function cancelRefreshTimeout() {
- if (buildRefreshTimeout) {
- window.clearTimeout(buildRefreshTimeout);
- buildRefreshTimeout = undefined;
- }
-}
-
-function hasPageUp() {
- return buildHistoryPage.getAttribute("page-has-up") === "true";
-}
-function hasPageDown() {
- return buildHistoryPage.getAttribute("page-has-down") === "true";
-}
-function getNewestEntryId() {
- return buildHistoryPage.getAttribute("page-entry-newest");
-}
-function getOldestEntryId() {
- return buildHistoryPage.getAttribute("page-entry-oldest");
-}
-
-function getDataTable(buildHistoryDiv) {
- return buildHistoryDiv.querySelector("table.pane");
-}
-
-function updatePageParams(dataTable) {
- buildHistoryPage.setAttribute(
- "page-has-up",
- dataTable.getAttribute("page-has-up"),
- );
- buildHistoryPage.setAttribute(
- "page-has-down",
- dataTable.getAttribute("page-has-down"),
- );
- buildHistoryPage.setAttribute(
- "page-entry-newest",
- dataTable.getAttribute("page-entry-newest"),
- );
- buildHistoryPage.setAttribute(
- "page-entry-oldest",
- dataTable.getAttribute("page-entry-oldest"),
- );
-}
-function togglePageUpDown() {
- buildHistoryPageNav.classList.remove("hasUpPage");
- buildHistoryPageNav.classList.remove("hasDownPage");
- if (hasPageUp()) {
- buildHistoryPageNav.classList.add("hasUpPage");
- }
- if (hasPageDown()) {
- buildHistoryPageNav.classList.add("hasDownPage");
- }
-}
-
-function checkRowCellOverflows(row) {
- if (!row) {
- return;
- }
-
- if (row.classList.contains("overflow-checked")) {
- // already done.
- return;
- }
-
- function markSingleline() {
- row.classList.add("single-line");
- row.classList.remove("multi-line");
- }
- function markMultiline() {
- row.classList.remove("single-line");
- row.classList.add("multi-line");
- }
- function indentMultiline(element) {
- element.classList.add("indent-multiline");
- }
-
- function blockWrap(el1, el2) {
- var div = document.createElement("div");
-
- div.classList.add("block");
- div.classList.add("wrap");
- el1.classList.add("wrapped");
- el2.classList.add("wrapped");
-
- el1.parentNode.insertBefore(div, el1);
- el1.parentNode.removeChild(el1);
- el2.parentNode.removeChild(el2);
- div.appendChild(el1);
- div.appendChild(el2);
-
- return div;
- }
- function blockUnwrap(element) {
- element.querySelectorAll(".wrapped").forEach(function (wrappedEl) {
- wrappedEl.parentNode.removeChild(wrappedEl);
- element.parentNode.insertBefore(wrappedEl, element);
- wrappedEl.classList.remove("wrapped");
- });
- element.parentNode.removeChild(element);
- }
-
- var buildName = row.querySelector(".build-name");
- var buildDetails = row.querySelector(".build-details");
-
- if (!buildName || !buildDetails) {
- return;
- }
-
- var buildControls = row.querySelector(".build-controls");
- var desc = row.querySelector(".desc");
-
- function resetCellOverflows() {
- markSingleline();
-
- // undo block wraps
- row.querySelectorAll(".block.wrap").forEach(function (blockWrap) {
- blockUnwrap(blockWrap);
- });
-
- buildName.classList.remove("block");
- buildName.removeAttribute("style");
- buildDetails.classList.remove("block");
- buildDetails.removeAttribute("style");
- if (buildControls) {
- buildControls.classList.remove("block");
- buildDetails.removeAttribute("style");
- }
- }
-
- // Undo everything from the previous poll.
- resetCellOverflows();
-
- // Mark the text as multiline, if it has more than one line
- if (desc) {
- markMultiline();
- }
-
- var rowWidth = buildHistoryContainer.clientWidth;
- var usableRowWidth = rowWidth - leftRightPadding * 2;
- var nameOverflowParams = getElementOverflowParams(buildName);
- var detailsOverflowParams = getElementOverflowParams(buildDetails);
-
- var controlsOverflowParams;
- if (buildControls) {
- controlsOverflowParams = getElementOverflowParams(buildControls);
- }
-
- function fitToControlsHeight(element) {
- if (buildControls) {
- if (element.clientHeight < buildControls.clientHeight) {
- element.style.height = buildControls.clientHeight.toString() + "px";
- }
- }
- }
-
- function setBuildControlWidths() {
- if (buildControls) {
- var buildBadge = buildControls.querySelector(".build-badge");
-
- if (buildBadge) {
- var buildControlsWidth = buildControls.clientWidth;
- var buildBadgeWidth;
-
- var buildStop = buildControls.querySelector(".build-stop");
- if (buildStop) {
- buildStop.style.width = "24px";
- // Minus 24 for the buildStop width,
- // minus 4 for left+right padding in the controls container
- buildBadgeWidth = buildControlsWidth - 24 - leftRightPadding;
- if (buildControls.classList.contains("indent-multiline")) {
- buildBadgeWidth = buildBadgeWidth - 20;
- }
- buildBadge.style.width = buildBadgeWidth + "px";
- } else {
- buildBadge.style.width = "100%";
- }
- }
- controlsOverflowParams = getElementOverflowParams(buildControls);
- }
- }
- setBuildControlWidths();
-
- var controlsRepositioned = false;
-
- if (nameOverflowParams.isOverflowed || detailsOverflowParams.isOverflowed) {
- // At least one of the cells (name or details) needs to move to a row of its own.
-
- markMultiline();
-
- if (buildControls) {
- // We have build controls. Lets see can we find a combination that allows the build controls
- // to sit beside either the build name or the build details.
-
- var badgesOverflowing = false;
- var nameLessThanHalf = true;
- var detailsLessThanHalf = true;
- var buildBadge = buildControls.querySelector(".build-badge");
- if (buildBadge) {
- var badgeOverflowParams = getElementOverflowParams(buildBadge);
-
- if (badgeOverflowParams.isOverflowed) {
- // The badges are also overflowing. In this case, we will only attempt to
- // put the controls on the same line as the name or details (see below)
- // if the name or details is using less than half the width of the build history
- // widget.
- badgesOverflowing = true;
- nameLessThanHalf =
- nameOverflowParams.scrollWidth < usableRowWidth / 2;
- detailsLessThanHalf =
- detailsOverflowParams.scrollWidth < usableRowWidth / 2;
- }
- }
- function expandLeftWithRight(
- leftCellOverFlowParams,
- rightCellOverflowParams,
- ) {
- // Float them left and right...
- leftCellOverFlowParams.element.style.float = "left";
- rightCellOverflowParams.element.style.float = "right";
-
- if (
- !leftCellOverFlowParams.isOverflowed &&
- !rightCellOverflowParams.isOverflowed
- ) {
- // If neither left nor right are overflowed, just leave as is and let them float left and right.
- return;
- }
- if (
- leftCellOverFlowParams.isOverflowed &&
- !rightCellOverflowParams.isOverflowed
- ) {
- leftCellOverFlowParams.element.style.width =
- leftCellOverFlowParams.scrollWidth + "px";
- return;
- }
- if (
- !leftCellOverFlowParams.isOverflowed &&
- rightCellOverflowParams.isOverflowed
- ) {
- rightCellOverflowParams.element.style.width =
- rightCellOverflowParams.scrollWidth + "px";
- return;
- }
- }
-
- if (
- (!badgesOverflowing || nameLessThanHalf) &&
- nameOverflowParams.scrollWidth + controlsOverflowParams.scrollWidth <=
- usableRowWidth
- ) {
- // Build name and controls can go on one row (first row). Need to move build details down
- // to a row of its own (second row) by making it a block element, forcing it to wrap. If there
- // are controls, we move them up to position them after the build name by inserting before the
- // build details.
- buildDetails.classList.add("block");
- buildControls.parentNode.removeChild(buildControls);
- buildDetails.parentNode.insertBefore(buildControls, buildDetails);
- var wrap = blockWrap(buildName, buildControls);
- wrap.classList.add("build-name-controls");
- indentMultiline(buildDetails);
- nameOverflowParams = getElementOverflowParams(buildName); // recalculate
- expandLeftWithRight(nameOverflowParams, controlsOverflowParams);
- setBuildControlWidths();
- fitToControlsHeight(buildName);
- } else if (
- (!badgesOverflowing || detailsLessThanHalf) &&
- detailsOverflowParams.scrollWidth +
- controlsOverflowParams.scrollWidth <=
- usableRowWidth
- ) {
- // Build details and controls can go on one row. Need to make the
- // build name (first field) a block element, forcing the details and controls to wrap
- // onto the next row (creating a second row).
- buildName.classList.add("block");
- wrap = blockWrap(buildDetails, buildControls);
- indentMultiline(wrap);
- wrap.classList.add("build-details-controls");
- detailsOverflowParams = getElementOverflowParams(buildDetails); // recalculate
- expandLeftWithRight(detailsOverflowParams, controlsOverflowParams);
- setBuildControlWidths();
- fitToControlsHeight(buildDetails);
- } else {
- // No suitable combo fits on a row. All need to go on rows of their own.
- buildName.classList.add("block");
- buildDetails.classList.add("block");
- buildControls.classList.add("block");
- indentMultiline(buildDetails);
- indentMultiline(buildControls);
- nameOverflowParams = getElementOverflowParams(buildName); // recalculate
- detailsOverflowParams = getElementOverflowParams(buildDetails); // recalculate
- setBuildControlWidths();
- }
- controlsRepositioned = true;
- } else {
- buildName.classList.add("block");
- buildDetails.classList.add("block");
- indentMultiline(buildDetails);
- }
- }
-
- if (buildControls && !controlsRepositioned) {
- buildBadge = buildControls.querySelector(".build-badge");
- if (buildBadge) {
- badgeOverflowParams = getElementOverflowParams(buildBadge);
-
- if (badgeOverflowParams.isOverflowed) {
- markMultiline();
- indentMultiline(buildControls);
- buildControls.classList.add("block");
- controlsRepositioned = true;
- setBuildControlWidths();
- }
- }
- }
-
- if (
- !nameOverflowParams.isOverflowed &&
- !detailsOverflowParams.isOverflowed &&
- !controlsRepositioned
- ) {
- fitToControlsHeight(buildName);
- fitToControlsHeight(buildDetails);
- }
-
- row.classList.add("overflow-checked");
-}
-
-function checkAllRowCellOverflows() {
- if (isRunAsTest) {
- return;
- }
-
- var dataTable = getDataTable(buildHistoryContainer);
- var rows = dataTable.rows;
-
- for (var i = 0; i < rows.length; i++) {
- var row = rows[i];
- checkRowCellOverflows(row);
- }
-}
-
-function loadPage(params, focusOnSearch) {
- var searchString = pageSearchInput.value;
-
- if (searchString !== "") {
- if (params === undefined) {
- params = {};
- }
- params.search = searchString;
- }
-
- fetch(ajaxUrl + toQueryString(params)).then((rsp) => {
- if (rsp.ok) {
- rsp.text().then((responseText) => {
- pageSearchInputContainer.classList.remove("jenkins-search--loading");
- buildHistoryContainer.classList.remove("jenkins-pane--loading");
-
- if (responseText === '') {
- noBuildsBanner.style.display = "block";
- if (
- typeof params === "object" &&
- "search" in params &&
- params.search !== ""
- ) {
- pageSearchInputContainer.classList.remove("jenkins-hidden");
- } else {
- pageSearchInputContainer.classList.add("jenkins-hidden");
- }
- } else {
- noBuildsBanner.style.display = "none";
- pageSearchInputContainer.classList.remove("jenkins-hidden");
- }
-
- var dataTable = getDataTable(buildHistoryContainer);
- var tbody = dataTable.getElementsByTagName("tbody")[0];
- var rows = tbody.getElementsByClassName("build-row");
-
- // Delete all build rows
- while (rows.length > 0) {
- rows[0].remove();
- }
-
- // insert new rows
- var div = document.createElement("div");
- div.innerHTML = responseText;
- Behaviour.applySubtree(div);
-
- var newDataTable = getDataTable(div);
- var newRows = newDataTable.rows;
- while (newRows.length > 0) {
- tbody.appendChild(newRows[0]);
- }
-
- checkAllRowCellOverflows();
- updatePageParams(newDataTable);
- togglePageUpDown();
- if (!hasPageUp()) {
- createRefreshTimeout(params);
- }
-
- if (focusOnSearch) {
- pageSearchInput.focus();
- }
- });
- }
- });
-}
-
-const handleFilter = function () {
- loadPage({}, true);
-};
-
-const debouncedFilter = debounce(handleFilter, 300);
-
-document.addEventListener("DOMContentLoaded", function () {
- // Apply correct styling upon filter bar text change, call API after wait
- if (pageSearchInput !== null) {
- pageSearchInput.addEventListener("input", function () {
- pageSearchInputContainer.classList.add("jenkins-search--loading");
- buildHistoryContainer.classList.add("jenkins-pane--loading");
- noBuildsBanner.style.display = "none";
-
- debouncedFilter();
- });
- }
-
- if (isRunAsTest) {
- return;
- }
-
- // If the build history pane is collapsed, just return immediately and don't set up
- // the build history refresh.
- if (buildHistoryContainer.classList.contains("collapsed")) {
- return;
- }
-
- buildHistoryContainer.headers = ["n", nextBuild];
-
- createRefreshTimeout();
- checkAllRowCellOverflows();
-
- // Show/hide the nav as the mouse moves into the sidepanel and build history.
- sidePanel.addEventListener("mouseover", function () {
- buildHistoryPageNav.classList.add("mouseOverSidePanel");
- });
- sidePanel.addEventListener("mouseout", function () {
- buildHistoryPageNav.classList.remove("mouseOverSidePanel");
- });
- buildHistoryContainer.addEventListener("mouseover", function () {
- buildHistoryPageNav.classList.add("mouseOverSidePanelBuildHistory");
- });
- buildHistoryContainer.addEventListener("mouseout", function () {
- buildHistoryPageNav.classList.remove("mouseOverSidePanelBuildHistory");
- });
-
- pageOne.addEventListener("click", function () {
- loadPage();
- });
- pageUp.addEventListener("click", function () {
- loadPage({ "newer-than": getNewestEntryId() });
- });
- pageDown.addEventListener("click", function () {
- if (hasPageDown()) {
- cancelRefreshTimeout();
- loadPage({ "older-than": getOldestEntryId() });
- } else {
- // wrap back around to the top
- loadPage();
- }
- });
-
- togglePageUpDown();
-});
diff --git a/war/src/main/js/pages/project/builds-card.js b/war/src/main/js/pages/project/builds-card.js
new file mode 100644
index 000000000000..bea375d337d8
--- /dev/null
+++ b/war/src/main/js/pages/project/builds-card.js
@@ -0,0 +1,142 @@
+import debounce from "lodash/debounce";
+import behaviorShim from "@/util/behavior-shim";
+
+// Card/item controls
+const buildHistoryPage = document.getElementById("buildHistoryPage");
+const pageSearch = buildHistoryPage.querySelector(".jenkins-search");
+const pageSearchInput = buildHistoryPage.querySelector("input");
+const ajaxUrl = buildHistoryPage.getAttribute("page-ajax");
+const card = document.querySelector("#jenkins-builds");
+const contents = card.querySelector("#jenkins-build-history");
+const container = card.querySelector(".app-builds-container");
+const noBuilds = card.querySelector("#no-builds");
+
+// Pagination controls
+const paginationControls = document.querySelector("#controls");
+const paginationPrevious = document.querySelector("#up");
+const paginationNext = document.querySelector("#down");
+
+// Refresh variables
+let buildRefreshTimeout;
+const updateBuildsRefreshInterval = 5000;
+
+/**
+ * Refresh the 'Builds' card
+ * @param {QueryParameters} options
+ */
+function load(options = {}) {
+ /** @type {QueryParameters} */
+ const params = Object.assign({}, options, { search: pageSearchInput.value });
+
+ // Avoid fetching if the page isn't active
+ if (document.hidden) {
+ return;
+ }
+
+ fetch(ajaxUrl + toQueryString(params)).then((rsp) => {
+ if (rsp.ok) {
+ rsp.text().then((responseText) => {
+ container.classList.remove("app-builds-container--loading");
+ pageSearch.classList.remove("jenkins-search--loading");
+
+ // Show the 'No builds' text if there are no builds
+ if (responseText.trim() === "") {
+ contents.innerHTML = "";
+ noBuilds.style.display = "block";
+ updateCardControls({
+ pageHasUp: false,
+ pageHasDown: false,
+ pageEntryNewest: false,
+ pageEntryOldest: false,
+ });
+ return;
+ }
+
+ // Show the refreshed builds list
+ contents.innerHTML = responseText;
+ noBuilds.style.display = "none";
+ behaviorShim.applySubtree(contents);
+
+ // Show the card controls
+ const div = document.createElement("div");
+ div.innerHTML = responseText;
+ const innerChild = div.children[0];
+ updateCardControls({
+ pageHasUp: innerChild.dataset.pageHasUp === "true",
+ pageHasDown: innerChild.dataset.pageHasDown === "true",
+ pageEntryNewest: innerChild.dataset.pageEntryNewest,
+ pageEntryOldest: innerChild.dataset.pageEntryOldest,
+ });
+ });
+ } else {
+ console.error("Failed to load 'Builds' card, response from API is:", rsp);
+ }
+ });
+}
+
+/**
+ * Shows/hides the card's pagination controls depending on the passed parameter
+ * @param {CardControlsOptions} parameters
+ */
+function updateCardControls(parameters) {
+ paginationControls.classList.toggle(
+ "jenkins-hidden",
+ !parameters.pageHasUp && !parameters.pageHasDown,
+ );
+ paginationPrevious.classList.toggle(
+ "app-builds-container__button--disabled",
+ !parameters.pageHasUp,
+ );
+ paginationNext.classList.toggle(
+ "app-builds-container__button--disabled",
+ !parameters.pageHasDown,
+ );
+
+ // We only want the list to refresh if the user is on the first page of results
+ if (!parameters.pageHasUp) {
+ createRefreshTimeout();
+ } else {
+ cancelRefreshTimeout();
+ }
+
+ buildHistoryPage.dataset.pageEntryNewest = parameters.pageEntryNewest;
+ buildHistoryPage.dataset.pageEntryOldest = parameters.pageEntryOldest;
+}
+
+paginationPrevious.addEventListener("click", () => {
+ load({ "newer-than": buildHistoryPage.dataset.pageEntryNewest });
+});
+
+paginationNext.addEventListener("click", () => {
+ cancelRefreshTimeout();
+ load({ "older-than": buildHistoryPage.dataset.pageEntryOldest });
+});
+
+function createRefreshTimeout() {
+ cancelRefreshTimeout();
+ buildRefreshTimeout = window.setTimeout(
+ () => load(),
+ updateBuildsRefreshInterval,
+ );
+}
+
+function cancelRefreshTimeout() {
+ if (buildRefreshTimeout) {
+ window.clearTimeout(buildRefreshTimeout);
+ buildRefreshTimeout = undefined;
+ }
+}
+
+const debouncedLoad = debounce(() => {
+ load();
+}, 150);
+
+document.addEventListener("DOMContentLoaded", function () {
+ pageSearchInput.addEventListener("input", function () {
+ container.classList.add("app-builds-container--loading");
+ pageSearch.classList.add("jenkins-search--loading");
+ debouncedLoad();
+ });
+
+ load();
+});
diff --git a/war/src/main/js/pages/project/builds-card.types.js b/war/src/main/js/pages/project/builds-card.types.js
new file mode 100644
index 000000000000..4745f088bd43
--- /dev/null
+++ b/war/src/main/js/pages/project/builds-card.types.js
@@ -0,0 +1,16 @@
+/**
+ * @typedef QueryParameters
+ * @type {object}
+ * @property {string | undefined} search
+ * @property {string | undefined} older-than
+ * @property {string | undefined} newer-than
+ */
+
+/**
+ * @typedef CardControlsOptions
+ * @type {object}
+ * @property {boolean} pageHasUp
+ * @property {boolean} pageHasDown
+ * @property {string | undefined} pageEntryNewest
+ * @property {string | undefined} pageEntryOldest
+ */
diff --git a/war/src/main/resources/images/symbols/expand.svg b/war/src/main/resources/images/symbols/expand.svg
new file mode 100644
index 000000000000..0ad346df7f67
--- /dev/null
+++ b/war/src/main/resources/images/symbols/expand.svg
@@ -0,0 +1 @@
+
diff --git a/war/src/main/resources/images/symbols/menu.svg b/war/src/main/resources/images/symbols/menu.svg
new file mode 100644
index 000000000000..fadc2bab3081
--- /dev/null
+++ b/war/src/main/resources/images/symbols/menu.svg
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/war/src/main/scss/abstracts/_theme.scss b/war/src/main/scss/abstracts/_theme.scss
index 673ba34dcfb4..a6d6aed91e11 100644
--- a/war/src/main/scss/abstracts/_theme.scss
+++ b/war/src/main/scss/abstracts/_theme.scss
@@ -244,7 +244,7 @@ $semantics: (
--pane-link-color--visited: black;
// Cards
- --card-background: transparent;
+ --card-background: var(--background);
--card-background--hover: transparent;
--card-background--active: transparent;
--card-border-color: hsla(240, 25%, 75%, 0.25);
diff --git a/war/src/main/scss/base/_style.scss b/war/src/main/scss/base/_style.scss
index b4a6effefdb0..56b8e40161bf 100644
--- a/war/src/main/scss/base/_style.scss
+++ b/war/src/main/scss/base/_style.scss
@@ -449,189 +449,6 @@ div.listview-jobs {
display: block;
}
-/* ========================= build history ========================= */
-#buildHistory a:visited {
- color: fuchsia;
-}
-
-#buildHistory tr.no-wrap td.middle-align {
- padding: 0;
-}
-
-#buildHistory .desc {
- position: relative;
- padding: 0;
- margin-top: 5px;
- white-space: normal;
- color: var(--text-color-secondary);
- word-break: break-word;
-}
-
-#buildHistory .build-row-cell {
- position: relative;
-}
-
-#buildHistory .build-rss-links {
- display: flex;
- justify-content: end;
-}
-
-#buildHistory .build-rss-links a {
- display: inline-flex;
- align-items: center;
- margin-right: 0.5rem;
-}
-
-#buildHistory .build-rss-all-icon,
-#buildHistory .build-rss-failed-icon {
- margin-right: 0.25rem;
-
- svg {
- width: 16px;
- height: 16px;
- }
-}
-
-#buildHistoryPage {
- position: relative;
-
- .build-search-row,
- .build-search-no-results-row {
- &:hover {
- background: transparent !important;
- }
-
- td {
- padding: 0 8px 8px;
- }
- }
-
- .build-search-no-results-row {
- border: none !important;
- width: 100% !important;
- background: transparent;
-
- td {
- padding-top: 0 !important;
- }
- }
-}
-
-#buildHistoryPageNav {
- position: absolute;
- right: -28px;
- top: 96px;
- border-radius: 6px;
- background: var(--input-color);
- border: 2px solid var(--input-border);
- visibility: hidden;
- z-index: 0;
- opacity: 0;
- transition: 0.2s ease;
-
- // Invisible pseudo element on the left so #buildHistoryPageNav
- // doesn't disappear when moving cursor over gap
- &::before {
- content: "";
- position: absolute;
- top: 0;
- left: -8px;
- bottom: 0;
- width: 8px;
- background: transparent;
- z-index: -1;
- }
-
- .buildHistoryPageNav__item {
- position: relative;
- display: flex;
- align-items: center;
- justify-content: center;
- flex-direction: column;
- padding: 0 8px;
- height: 30px;
- cursor: pointer;
- transition: opacity 0.2s ease;
-
- &:hover {
- opacity: 0.5;
- }
-
- &:active {
- opacity: 0.25;
- }
-
- &:not(:last-child) {
- border-bottom: 2px solid rgba(0, 0, 0, 0.05);
- }
-
- &-page-one-top {
- width: 12px;
- height: 2px;
- background: currentColor;
- margin: 2px 0;
- border-radius: 2px;
- }
-
- svg {
- fill: currentColor;
- width: 12px;
- height: 12px;
- }
- }
-}
-
-#buildHistoryPageNav.mouseOverSidePanel {
- visibility: visible;
- right: -32px;
- opacity: 1;
-}
-
-.build-row.model-link-active {
- background: var(--light-grey) !important;
-}
-
-.build-row-cell {
- font-size: var(--font-size-xs);
-}
-
-.build-row-cell .pane.build-name {
- width: 25%;
- font-weight: 500;
- vertical-align: top;
-}
-
-.build-row-cell .pane.build-details {
- width: 50%;
-}
-
-.build-row-cell .pane.build-controls {
- width: 25%;
- text-align: right;
-}
-
-.build-row-cell .pane.build-details.block {
- width: 100%;
-}
-
-.pane.build-name a,
-.pane.build-name a:visited {
- color: var(--pane-link-color);
- text-decoration: underline;
-}
-
-.pane.build-details a,
-.pane.build-details a:visited {
- color: var(--pane-link-color--visited);
- opacity: 0.6;
- text-decoration: none;
-}
-
-.pane.build-details a:hover {
- opacity: 1;
- text-decoration: underline;
-}
-
/* ================ Element overflow calculation helper styles ================ */
.force-wrap,
diff --git a/war/src/main/scss/components/_cards.scss b/war/src/main/scss/components/_cards.scss
new file mode 100644
index 000000000000..be86bb390337
--- /dev/null
+++ b/war/src/main/scss/components/_cards.scss
@@ -0,0 +1,97 @@
+$card-padding: 1rem;
+
+.jenkins-card {
+ position: relative;
+ border-radius: 1rem;
+ margin-bottom: calc(var(--section-padding) / 2);
+ background: var(--card-background);
+
+ &__title {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 0 $card-padding;
+ height: 50px;
+ font-size: var(--font-size-sm) !important;
+ font-weight: 500;
+ width: 100%;
+ z-index: 1;
+ }
+
+ &__controls {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: 0.4rem;
+ margin-right: -0.2rem;
+ }
+
+ &:not(:hover) {
+ .jenkins-card__unveil {
+ color: var(--text-color-secondary) !important;
+ }
+ }
+
+ &:hover {
+ .jenkins-card__reveal {
+ color: var(--text-color) !important;
+ }
+ }
+
+ &__content {
+ display: flex;
+ flex-direction: column;
+ padding: 0 $card-padding $card-padding;
+ color: var(--text-color-secondary);
+
+ &:empty {
+ display: none;
+ }
+ }
+
+ &::after {
+ content: "";
+ position: absolute;
+ inset: 0;
+ border-radius: inherit;
+ border: var(--card-border-width) solid var(--card-border-color);
+ z-index: 1;
+ pointer-events: none;
+ }
+
+ .jenkins-card__reveal {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ margin-block: -0.5rem;
+ min-height: 0;
+ padding: 0;
+ width: 26px;
+ height: 26px;
+ border-radius: 0.33rem;
+ color: var(--text-color-secondary) !important;
+ transition:
+ scale var(--standard-transition),
+ opacity var(--standard-transition);
+
+ svg {
+ width: 1rem;
+ height: 1rem;
+ transition: color var(--standard-transition);
+ }
+
+ &::before,
+ &::after {
+ opacity: 0;
+ }
+
+ &:hover {
+ opacity: 0.75;
+ }
+
+ &:active {
+ scale: 95%;
+ opacity: 0.5;
+ }
+ }
+}
diff --git a/war/src/main/scss/components/_dropdowns.scss b/war/src/main/scss/components/_dropdowns.scss
index 586a07fc5adf..51a5c4b9bc36 100644
--- a/war/src/main/scss/components/_dropdowns.scss
+++ b/war/src/main/scss/components/_dropdowns.scss
@@ -254,3 +254,18 @@ $dropdown-padding: 0.4rem;
border-radius: 50%;
}
}
+
+.jenkins-jumplist-link {
+ appearance: none;
+ border: none;
+ background: none;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ cursor: pointer;
+
+ svg {
+ width: 1.25rem;
+ height: 1.25rem;
+ }
+}
diff --git a/war/src/main/scss/components/_index.scss b/war/src/main/scss/components/_index.scss
index 3048d06ae2c5..f3167bd091f9 100644
--- a/war/src/main/scss/components/_index.scss
+++ b/war/src/main/scss/components/_index.scss
@@ -2,8 +2,9 @@
@use "alert";
@use "badges";
@use "breadcrumbs";
-@use "buttons-deprecated";
@use "buttons";
+@use "buttons-deprecated";
+@use "cards";
@use "content-blocks";
@use "dialogs";
@use "dropdowns";
diff --git a/war/src/main/scss/components/_progress-bar.scss b/war/src/main/scss/components/_progress-bar.scss
index 1e1faf37d630..2a92b570a226 100644
--- a/war/src/main/scss/components/_progress-bar.scss
+++ b/war/src/main/scss/components/_progress-bar.scss
@@ -10,7 +10,6 @@
var(--text-color-secondary) 25%,
transparent
);
- margin-top: 2px;
display: block;
opacity: 1 !important;
diff --git a/war/src/main/scss/components/_side-panel-widgets.scss b/war/src/main/scss/components/_side-panel-widgets.scss
index 6772890bbffa..449646aaad4a 100644
--- a/war/src/main/scss/components/_side-panel-widgets.scss
+++ b/war/src/main/scss/components/_side-panel-widgets.scss
@@ -90,165 +90,3 @@
#executors th.pane {
text-align: left;
}
-
-/**
- * Build history
- */
-.build-row {
- padding: 3px 4px;
-}
-
-.build-row.model-link-active {
- background: var(--very-light-grey) !important;
-}
-
-.build-row-cell {
- font-size: var(--font-size-xs);
-}
-
-.build-row-cell .pane.build-name {
- width: 32%;
- font-weight: 500;
- vertical-align: top;
-}
-
-.build-row-cell .pane.build-details {
- width: 50%;
-}
-
-.build-row-cell .pane.build-controls {
- width: 18%;
- text-align: right;
-}
-
-.build-row-cell .pane.build-details.block {
- width: 100%;
-}
-
-.build-row.multi-line .build-row-cell .pane.build-name.block {
- width: 100%;
-}
-
-.build-row-cell .pane.build-controls.block {
- width: 100%;
-}
-
-.build-row-cell .pane.build-name .build-icon,
-.build-row-cell .pane.build-name .display-name {
- display: inline-block;
-}
-
-.build-row-cell .pane.build-name .build-icon {
- position: absolute;
- margin-top: 2px;
- z-index: 1;
-}
-
-.build-row-cell .build-stop {
- display: inline-block;
- width: 30%;
-}
-
-.build-row-cell .build-badge {
- display: inline-block;
- text-align: right;
- width: 70%;
- padding: 2px 0;
-}
-
-.build-row-cell .build-badge > span {
- display: inline-block;
- max-width: 256px;
- padding: 0 1px;
- overflow: hidden;
-}
-
-.build-row-cell .build-badge > span + span {
- margin: 0 0 0 2px !important;
-}
-
-@media (width >= 1170px) {
- .build-row-cell .build-badge > span {
- max-width: 296px;
- }
-}
-
-.build-row .build-name-controls .pane.build-name,
-.build-row .build-details-controls .pane.build-details {
- width: 70%;
-}
-
-.build-row .build-row-cell .pane,
-#side-panel .build-row .build-row-cell .pane {
- padding: 0 2px; /* Sync changes with func expandControlsTo50Percent in hudson-behavior.js */
- display: inline-block;
- overflow: hidden;
-}
-
-.build-row.multi-line .build-row-cell .block {
- display: block;
- overflow: auto;
-}
-
-.build-row.multi-line .build-row-cell .indent-multiline {
- margin-top: 5px;
-}
-
-.build-row.multi-line .build-row-cell .left-bar {
- position: absolute;
- top: 31px;
- bottom: 10px;
- left: 17px;
- border-left: 1px solid var(--medium-grey);
-}
-
-.build-row-cell .pane.build-name .display-name {
- margin-left: 20px;
- word-break: break-all;
-}
-
-.build-row-cell .indent-multiline {
- padding-left: 20px !important; /* Sync changes with func expandControlsTo50Percent in hudson-behavior.js */
-}
-
-.build-row.overflow-checked .build-row-cell {
- visibility: visible;
-}
-
-.jenkins-pane {
- &__information {
- text-align: center;
- line-height: 80px;
- background-color: var(--panel-header-bg-color);
- margin-top: 10px;
- font-weight: 600;
- border-radius: var(--form-input-border-radius);
- }
-
- .build-row {
- transition: opacity 0.2s ease;
-
- &-cell {
- padding: 4px 8px;
- }
- }
-
- &--loading .build-row {
- opacity: 0.5;
- }
-}
-
-.jenkins-pane__header--build-history {
- display: grid;
- grid-template-columns: auto 1fr auto;
- font-weight: 500 !important;
-
- .build-health-link {
- margin: -9px -15px;
- }
-
- .jenkins-table__cell--tight {
- width: auto;
- margin-right: 1rem;
- }
-}
diff --git a/war/src/main/scss/pages/_index.scss b/war/src/main/scss/pages/_index.scss
index 0e0b224dc6a0..c8e20e272271 100644
--- a/war/src/main/scss/pages/_index.scss
+++ b/war/src/main/scss/pages/_index.scss
@@ -2,6 +2,7 @@
@use "build";
@use "dashboard";
@use "icon-legend";
+@use "job";
@use "manage-jenkins";
@use "plugin-manager";
@use "setupWizardFirstUser";
diff --git a/war/src/main/scss/pages/_job.scss b/war/src/main/scss/pages/_job.scss
new file mode 100644
index 000000000000..3b982fcffcf4
--- /dev/null
+++ b/war/src/main/scss/pages/_job.scss
@@ -0,0 +1,184 @@
+@use "../abstracts/mixins";
+
+#buildHistoryPage {
+ margin: 10px 0 10px 10px;
+
+ .jenkins-search {
+ margin-inline: -0.25rem;
+ margin-bottom: 5px;
+ }
+}
+
+.app-builds-container {
+ transition: opacity var(--standard-transition);
+
+ &__items {
+ margin-bottom: -0.5rem;
+ }
+
+ &__placeholder {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ text-align: center;
+ padding: 3rem;
+ animation: fade-in-builds-placeholder var(--standard-transition);
+
+ @keyframes fade-in-builds-placeholder {
+ from {
+ opacity: 0;
+ }
+ }
+ }
+
+ &__heading {
+ display: flex;
+ font-size: 0.75rem;
+ color: var(--text-color-secondary);
+ margin-top: 10px;
+ font-weight: 450;
+ margin-bottom: 4px;
+ }
+
+ &__controls {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ gap: 15px;
+ margin: 0 -0.35rem;
+ margin-top: 1rem;
+ margin-bottom: -0.5rem;
+
+ .jenkins-button {
+ padding: 10px;
+
+ svg {
+ transition: translate var(--standard-transition);
+ }
+
+ &:first-of-type {
+ justify-content: start;
+
+ &:hover {
+ translate: -2px 0;
+
+ svg {
+ translate: -4px 0;
+ }
+ }
+ }
+
+ &:last-of-type {
+ justify-content: end;
+
+ &:hover {
+ translate: 2px 0;
+
+ svg {
+ translate: 4px 0;
+ }
+ }
+ }
+ }
+
+ .app-builds-container__button--disabled {
+ color: var(--text-color-secondary) !important;
+ opacity: 0.25;
+ pointer-events: none;
+ }
+ }
+
+ &--loading {
+ opacity: 0.4;
+ filter: blur(0.5px);
+ }
+}
+
+.app-builds-container__item {
+ @include mixins.item();
+
+ display: grid;
+ grid-template-columns: auto 1fr auto;
+ gap: 0.5rem 0.65rem;
+ padding: 0 0 0.25rem;
+ margin: 0 -0.5rem;
+ font-size: 0.8125rem !important;
+ min-height: 2rem;
+
+ &__icon {
+ display: inline-flex;
+ justify-content: center;
+ padding: 0 0 0 0.5rem;
+ margin-top: 0.385rem;
+
+ svg {
+ width: 1.25rem;
+ height: 1.25rem;
+ }
+ }
+
+ .app-builds-container__item__inner {
+ display: flex;
+ align-items: stretch;
+ flex-wrap: wrap;
+
+ &__link {
+ display: flex;
+ color: var(--text-color);
+ gap: 0.5rem;
+ text-decoration: none;
+ font-weight: 450;
+ flex-grow: 1;
+ padding: 0.45rem 0 0;
+
+ .app-builds-container__item__time {
+ color: var(--text-color-secondary);
+ }
+ }
+
+ &__controls {
+ display: flex;
+ align-items: center;
+ justify-content: start;
+ flex-wrap: wrap;
+ gap: 0.5rem;
+ margin-top: 0.3rem;
+ }
+ }
+
+ &--not-interactable {
+ cursor: default;
+
+ &::before,
+ &::after {
+ display: none;
+ }
+
+ .app-builds-container__item__description {
+ margin-bottom: 0;
+ }
+ }
+
+ .jenkins-jumplist-link {
+ margin-top: 0.2rem;
+ padding-right: 0.8rem;
+ }
+
+ &__description {
+ color: var(--text-color-secondary);
+ padding-left: 2.25rem;
+ margin-top: -2px;
+ grid-column: 1 / span 2;
+
+ &::before {
+ content: "";
+ position: absolute;
+ left: 17px;
+ top: 34px;
+ bottom: 6px;
+ width: 2px;
+ background: var(--text-color-secondary);
+ border-radius: 10px;
+ opacity: 0.3;
+ }
+ }
+}
diff --git a/war/src/main/webapp/scripts/hudson-behavior.js b/war/src/main/webapp/scripts/hudson-behavior.js
index 161db88cfd72..ff6a506e77f6 100644
--- a/war/src/main/webapp/scripts/hudson-behavior.js
+++ b/war/src/main/webapp/scripts/hudson-behavior.js
@@ -2220,35 +2220,6 @@ function toQueryString(params) {
return query;
}
-// eslint-disable-next-line no-unused-vars
-function getElementOverflowParams(element) {
- // First we force it to wrap so we can get those dimension.
- // Then we force it to "nowrap", so we can get those dimension.
- // We can then compare the two sets, which will indicate if
- // wrapping is potentially happening, or not.
-
- // Force it to wrap.
- element.classList.add("force-wrap");
- var wrappedClientWidth = element.clientWidth;
- var wrappedClientHeight = element.clientHeight;
- element.classList.remove("force-wrap");
-
- // Force it to nowrap. Return the comparisons.
- element.classList.add("force-nowrap");
- var nowrapClientHeight = element.clientHeight;
- try {
- var overflowParams = {
- element: element,
- clientWidth: wrappedClientWidth,
- scrollWidth: element.scrollWidth,
- isOverflowed: wrappedClientHeight > nowrapClientHeight,
- };
- return overflowParams;
- } finally {
- element.classList.remove("force-nowrap");
- }
-}
-
// get the cascaded computed style value. 'a' is the style name like 'backgroundColor'
function getStyle(e, a) {
if (document.defaultView && document.defaultView.getComputedStyle) {
diff --git a/war/webpack.config.js b/war/webpack.config.js
index c5e8e17442d1..b9f012095ff4 100644
--- a/war/webpack.config.js
+++ b/war/webpack.config.js
@@ -50,8 +50,8 @@ module.exports = (env, argv) => ({
"components/row-selection-controller": [
path.join(__dirname, "src/main/js/components/row-selection-controller"),
],
- "filter-build-history": [
- path.join(__dirname, "src/main/js/filter-build-history.js"),
+ "pages/project/builds-card": [
+ path.join(__dirname, "src/main/js/pages/project/builds-card.js"),
],
"simple-page": [path.join(__dirname, "src/main/scss/simple-page.scss")],
styles: [path.join(__dirname, "src/main/scss/styles.scss")],