From c62ab98b85c3c85197f8235f5d91dbd5b93a9382 Mon Sep 17 00:00:00 2001 From: Matthew Ball Date: Fri, 22 May 2026 00:29:55 -0700 Subject: [PATCH 1/5] add /sub-issue, /parent-issue, and unlink variants to comment-commands --- .github/workflows/comment-commands.yml | 198 ++++++++++++++++++++++++- 1 file changed, 197 insertions(+), 1 deletion(-) diff --git a/.github/workflows/comment-commands.yml b/.github/workflows/comment-commands.yml index 3300db1353d..f8300380ecd 100644 --- a/.github/workflows/comment-commands.yml +++ b/.github/workflows/comment-commands.yml @@ -14,7 +14,8 @@ # See the License for the specific language governing permissions and # limitations under the License. -# /take, /untake, /request-review, and /unrequest-review comment commands. +# /take, /untake, /request-review, /unrequest-review, /sub-issue, +# /unsub-issue, /parent-issue, and /unparent-issue comment commands. # # Triage state is no longer materialized as a label — it is the search # filter `is:issue is:open no:assignee`. Anyone can self-claim an issue @@ -25,6 +26,14 @@ # via `/request-review @user [@user ...]` and `/unrequest-review @user # [@user ...]`. We avoid the `/review` namespace so it stays free for # future use (e.g. self-review). +# +# Sub-issue linking can be driven from either end of the relationship: +# `/sub-issue #N [#M ...]` on a parent links those issues as children; +# `/parent-issue #N` on a child sets #N as its parent. Unlinking mirrors +# this: `/unsub-issue #N [#M ...]` from the parent, `/unparent-issue` +# from the child (omit the number to auto-detect via GraphQL, or pass +# `/unparent-issue #N` to be explicit). Cross-repo links are not +# supported; references like `owner/repo#N` are ignored. name: Comment commands on: issue_comment: @@ -165,3 +174,190 @@ jobs: `${action} on #${pull_number} failed: ${e.message}`, ); } + + sub-issue: + # The sub-issue REST endpoints key off the issue's database `id`, so + # each #N reference needs a lookup before link/unlink. + if: >- + github.event_name == 'issue_comment' + && github.event.action == 'created' + && github.event.issue.pull_request == null + && github.event.comment.user.type != 'Bot' + && (startsWith(github.event.comment.body, '/sub-issue') + || startsWith(github.event.comment.body, '/unsub-issue') + || startsWith(github.event.comment.body, '/parent-issue') + || startsWith(github.event.comment.body, '/unparent-issue')) + runs-on: ubuntu-latest + steps: + - uses: actions/github-script@v8 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const body = (context.payload.comment.body || '').trim(); + const issue_number = context.payload.issue.number; + const commenter = context.payload.comment.user.login; + const { owner, repo } = context.repo; + + // Longest alternatives first so `unsub-issue` isn't shadowed + // by `sub-issue`. + const match = body.match( + /^\/(unsub-issue|unparent-issue|sub-issue|parent-issue)\b(.*)$/s, + ); + if (!match) { + core.info(`Comment does not match exact command; skipping.`); + return; + } + const action = match[1]; + const rest = match[2]; + core.info( + `${action} candidate: ${commenter} on issue #${issue_number}; ` + + `body=${JSON.stringify(body)}`, + ); + + // Accept `#N` or bare `N`; cross-repo `owner/repo#N` is not + // supported by the sub-issue endpoint. + const refs = []; + for (const token of rest.split(/\s+/)) { + if (!token) continue; + if (token.includes('/')) { + core.warning(`Ignoring cross-repo reference '${token}'.`); + continue; + } + const m = token.match(/^#?(\d+)$/); + if (m) refs.push(Number(m[1])); + } + + async function getIssueId(number) { + const { data } = await github.rest.issues.get({ + owner, repo, issue_number: number, + }); + return data.id; + } + + async function getParentNumber(number) { + const query = ` + query($owner:String!, $name:String!, $number:Int!) { + repository(owner:$owner, name:$name) { + issue(number:$number) { parent { number } } + } + }`; + const result = await github.graphql(query, { + owner, name: repo, number, + }); + return result.repository.issue.parent?.number ?? null; + } + + async function linkChild(parent_number, child_number) { + const sub_issue_id = await getIssueId(child_number); + await github.request( + 'POST /repos/{owner}/{repo}/issues/{issue_number}/sub_issues', + { owner, repo, issue_number: parent_number, sub_issue_id }, + ); + } + + async function unlinkChild(parent_number, child_number) { + const sub_issue_id = await getIssueId(child_number); + await github.request( + 'DELETE /repos/{owner}/{repo}/issues/{issue_number}/sub_issue', + { owner, repo, issue_number: parent_number, sub_issue_id }, + ); + } + + if (action === 'sub-issue' || action === 'unsub-issue') { + if (!refs.length) { + core.warning(`No #N refs in '/${action}'; skipping.`); + return; + } + for (const n of refs) { + if (n === issue_number) { + core.warning( + `Refusing to self-link #${n}; skipping.`, + ); + continue; + } + try { + if (action === 'sub-issue') { + await linkChild(issue_number, n); + core.info( + `Linked #${n} as sub-issue of #${issue_number}`, + ); + } else { + await unlinkChild(issue_number, n); + core.info( + `Unlinked #${n} from sub-issues of #${issue_number}`, + ); + } + } catch (e) { + core.warning( + `${action} #${n} on #${issue_number} failed: ${e.message}`, + ); + } + } + return; + } + + if (action === 'parent-issue') { + if (refs.length !== 1) { + core.warning( + `/parent-issue expects exactly one #N; skipping.`, + ); + return; + } + const parent_number = refs[0]; + if (parent_number === issue_number) { + core.warning( + `Refusing to set #${issue_number} as its own parent; skipping.`, + ); + return; + } + try { + await linkChild(parent_number, issue_number); + core.info( + `Linked #${issue_number} as sub-issue of #${parent_number}`, + ); + } catch (e) { + core.warning( + `parent-issue #${parent_number} on #${issue_number} ` + + `failed: ${e.message}`, + ); + } + return; + } + + if (action === 'unparent-issue') { + if (refs.length > 1) { + core.warning( + `/unparent-issue accepts at most one #N; skipping.`, + ); + return; + } + let parent_number = refs[0]; + if (parent_number === undefined) { + try { + parent_number = await getParentNumber(issue_number); + } catch (e) { + core.warning( + `parent lookup for #${issue_number} failed: ${e.message}`, + ); + return; + } + if (parent_number == null) { + core.warning( + `#${issue_number} has no parent; skipping.`, + ); + return; + } + } + try { + await unlinkChild(parent_number, issue_number); + core.info( + `Unlinked #${issue_number} from parent #${parent_number}`, + ); + } catch (e) { + core.warning( + `unparent-issue on #${issue_number} (parent #${parent_number}) ` + + `failed: ${e.message}`, + ); + } + return; + } From 6449b50dfc1fb0aa99a2ef70493ec8842cc4209c Mon Sep 17 00:00:00 2001 From: Matthew Ball Date: Fri, 22 May 2026 01:32:38 -0700 Subject: [PATCH 2/5] drop withDefaultValue from StatisticsManager so checkpoint state round-trips --- .../worker/managers/StatisticsManager.scala | 10 +++++----- .../amber/engine/faulttolerance/CheckpointSpec.scala | 8 ++++++-- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/amber/src/main/scala/org/apache/texera/amber/engine/architecture/worker/managers/StatisticsManager.scala b/amber/src/main/scala/org/apache/texera/amber/engine/architecture/worker/managers/StatisticsManager.scala index 8ae0419f0a3..2d393e63254 100644 --- a/amber/src/main/scala/org/apache/texera/amber/engine/architecture/worker/managers/StatisticsManager.scala +++ b/amber/src/main/scala/org/apache/texera/amber/engine/architecture/worker/managers/StatisticsManager.scala @@ -30,11 +30,11 @@ import org.apache.texera.amber.engine.architecture.worker.statistics.{ import scala.collection.mutable class StatisticsManager { - // DataProcessor + // Plain maps (no withDefaultValue) so they survive Kryo round-trip. private val inputStatistics: mutable.Map[PortIdentity, (Long, Long)] = - mutable.Map.empty.withDefaultValue((0L, 0L)) + mutable.Map.empty private val outputStatistics: mutable.Map[PortIdentity, (Long, Long)] = - mutable.Map.empty.withDefaultValue((0L, 0L)) + mutable.Map.empty private var dataProcessingTime: Long = 0L private var totalExecutionTime: Long = 0L private var workerStartTime: Long = 0L @@ -82,7 +82,7 @@ class StatisticsManager { */ def increaseInputStatistics(portId: PortIdentity, size: Long): Unit = { require(size >= 0, "Tuple size must be non-negative") - val (count, totalSize) = inputStatistics(portId) + val (count, totalSize) = inputStatistics.getOrElse(portId, (0L, 0L)) inputStatistics.update(portId, (count + 1, totalSize + size)) } @@ -93,7 +93,7 @@ class StatisticsManager { */ def increaseOutputStatistics(portId: PortIdentity, size: Long): Unit = { require(size >= 0, "Tuple size must be non-negative") - val (count, totalSize) = outputStatistics(portId) + val (count, totalSize) = outputStatistics.getOrElse(portId, (0L, 0L)) outputStatistics.update(portId, (count + 1, totalSize + size)) } diff --git a/amber/src/test/scala/org/apache/texera/amber/engine/faulttolerance/CheckpointSpec.scala b/amber/src/test/scala/org/apache/texera/amber/engine/faulttolerance/CheckpointSpec.scala index fbc7e8044df..3d207fd23b3 100644 --- a/amber/src/test/scala/org/apache/texera/amber/engine/faulttolerance/CheckpointSpec.scala +++ b/amber/src/test/scala/org/apache/texera/amber/engine/faulttolerance/CheckpointSpec.scala @@ -63,7 +63,7 @@ class CheckpointSpec extends AnyFlatSpecLike with BeforeAndAfterAll { system.actorOf(Props[SingleNodeListener](), "cluster-info") } - "Default controller state" should "be serializable" in { + "Default controller state" should "round-trip through CheckpointState" in { val cp = new ControllerProcessor( workflow.context, @@ -73,9 +73,11 @@ class CheckpointSpec extends AnyFlatSpecLike with BeforeAndAfterAll { ) val chkpt = new CheckpointState() chkpt.save(CP_STATE_KEY, cp) + val restored: ControllerProcessor = chkpt.load(CP_STATE_KEY) + assert(restored.actorId == cp.actorId) } - "Default worker state" should "be serializable" in { + "Default worker state" should "round-trip through CheckpointState" in { val dp = new DataProcessor( SELF, msg => {}, @@ -83,6 +85,8 @@ class CheckpointSpec extends AnyFlatSpecLike with BeforeAndAfterAll { ) val chkpt = new CheckpointState() chkpt.save(DP_STATE_KEY, dp) + val restored: DataProcessor = chkpt.load(DP_STATE_KEY) + assert(restored.actorId == dp.actorId) } "CheckpointState" should "fail loudly on an unknown key" in { From aae8b1473db228afb1b277477f345aa39b04eeaa Mon Sep 17 00:00:00 2001 From: Matthew Ball Date: Fri, 22 May 2026 02:06:22 -0700 Subject: [PATCH 3/5] added test cases to complete code coverage --- .../worker/managers/StatisticsManager.scala | 12 ++++++++---- .../worker/managers/WorkerManagersSpec.scala | 14 +++++++++----- 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/amber/src/main/scala/org/apache/texera/amber/engine/architecture/worker/managers/StatisticsManager.scala b/amber/src/main/scala/org/apache/texera/amber/engine/architecture/worker/managers/StatisticsManager.scala index 2d393e63254..c34595cf3bd 100644 --- a/amber/src/main/scala/org/apache/texera/amber/engine/architecture/worker/managers/StatisticsManager.scala +++ b/amber/src/main/scala/org/apache/texera/amber/engine/architecture/worker/managers/StatisticsManager.scala @@ -82,8 +82,10 @@ class StatisticsManager { */ def increaseInputStatistics(portId: PortIdentity, size: Long): Unit = { require(size >= 0, "Tuple size must be non-negative") - val (count, totalSize) = inputStatistics.getOrElse(portId, (0L, 0L)) - inputStatistics.update(portId, (count + 1, totalSize + size)) + inputStatistics.updateWith(portId) { + case Some((count, totalSize)) => Some((count + 1, totalSize + size)) + case None => Some((1L, size)) + } } /** @@ -93,8 +95,10 @@ class StatisticsManager { */ def increaseOutputStatistics(portId: PortIdentity, size: Long): Unit = { require(size >= 0, "Tuple size must be non-negative") - val (count, totalSize) = outputStatistics.getOrElse(portId, (0L, 0L)) - outputStatistics.update(portId, (count + 1, totalSize + size)) + outputStatistics.updateWith(portId) { + case Some((count, totalSize)) => Some((count + 1, totalSize + size)) + case None => Some((1L, size)) + } } /** diff --git a/amber/src/test/scala/org/apache/texera/amber/engine/architecture/worker/managers/WorkerManagersSpec.scala b/amber/src/test/scala/org/apache/texera/amber/engine/architecture/worker/managers/WorkerManagersSpec.scala index 3fbff39148c..1932823f5dc 100644 --- a/amber/src/test/scala/org/apache/texera/amber/engine/architecture/worker/managers/WorkerManagersSpec.scala +++ b/amber/src/test/scala/org/apache/texera/amber/engine/architecture/worker/managers/WorkerManagersSpec.scala @@ -76,11 +76,15 @@ class WorkerManagersSpec extends AnyFlatSpec { val sm = new StatisticsManager() sm.increaseOutputStatistics(PortIdentity(0), 30) sm.increaseOutputStatistics(PortIdentity(0), 70) - assert(sm.getOutputTupleCount == 2L) - val out = sm.getStatistics(nullExec).outputTupleMetrics - assert(out.size == 1) - assert(out.head.tupleMetrics.count == 2L) - assert(out.head.tupleMetrics.size == 100L) + sm.increaseOutputStatistics(PortIdentity(1), 25) + assert(sm.getOutputTupleCount == 3L) + val byPort = sm + .getStatistics(nullExec) + .outputTupleMetrics + .map(m => m.portId -> (m.tupleMetrics.count, m.tupleMetrics.size)) + .toMap + assert(byPort(PortIdentity(0)) == (2L, 100L)) + assert(byPort(PortIdentity(1)) == (1L, 25L)) } it should "reject negative tuple sizes" in { From adef119aeffd1911909953430fb10e4138070bf4 Mon Sep 17 00:00:00 2001 From: Matthew Ball Date: Fri, 22 May 2026 02:40:58 -0700 Subject: [PATCH 4/5] drop /dashboard prefix from user-facing URLs --- .../web/service/WorkflowEmailNotifier.scala | 2 +- frontend/src/app/app-routing.constant.ts | 2 +- frontend/src/app/app-routing.module.ts | 33 +++++-------------- .../execution/admin-execution.component.html | 2 +- .../component/dashboard.component.spec.ts | 4 +-- .../component/dashboard.component.ts | 2 +- .../share-access/share-access.component.ts | 2 +- 7 files changed, 16 insertions(+), 31 deletions(-) diff --git a/amber/src/main/scala/org/apache/texera/web/service/WorkflowEmailNotifier.scala b/amber/src/main/scala/org/apache/texera/web/service/WorkflowEmailNotifier.scala index af9a26286d4..c49f63e9394 100644 --- a/amber/src/main/scala/org/apache/texera/web/service/WorkflowEmailNotifier.scala +++ b/amber/src/main/scala/org/apache/texera/web/service/WorkflowEmailNotifier.scala @@ -107,7 +107,7 @@ class WorkflowEmailNotifier( private def createDashboardUrl(): String = { val host = sessionUri.getHost val port = sessionUri.getPort - val path = s"/dashboard/user/workspace/$workflowId" + val path = s"/user/workspace/$workflowId" if (port == -1 || port == 80 || port == 443) { s"http://$host$path" } else { diff --git a/frontend/src/app/app-routing.constant.ts b/frontend/src/app/app-routing.constant.ts index 4181df8a954..d3aca2c823f 100644 --- a/frontend/src/app/app-routing.constant.ts +++ b/frontend/src/app/app-routing.constant.ts @@ -17,7 +17,7 @@ * under the License. */ -export const DASHBOARD = "/dashboard"; +export const DASHBOARD = ""; export const DASHBOARD_HOME = `${DASHBOARD}/home`; export const DASHBOARD_ABOUT = `${DASHBOARD}/about`; diff --git a/frontend/src/app/app-routing.module.ts b/frontend/src/app/app-routing.module.ts index 179caf5c088..4e3e68016d8 100644 --- a/frontend/src/app/app-routing.module.ts +++ b/frontend/src/app/app-routing.module.ts @@ -17,8 +17,8 @@ * under the License. */ -import { inject, NgModule } from "@angular/core"; -import { CanActivateFn, Router, RouterModule, Routes } from "@angular/router"; +import { NgModule } from "@angular/core"; +import { RouterModule, Routes } from "@angular/router"; import { DashboardComponent } from "./dashboard/component/dashboard.component"; import { UserWorkflowComponent } from "./dashboard/component/user/user-workflow/user-workflow.component"; import { UserQuotaComponent } from "./dashboard/component/user/user-quota/user-quota.component"; @@ -38,28 +38,21 @@ import { DatasetDetailComponent } from "./dashboard/component/user/user-dataset/ import { UserDatasetComponent } from "./dashboard/component/user/user-dataset/user-dataset.component"; import { HubWorkflowDetailComponent } from "./hub/component/workflow/detail/hub-workflow-detail.component"; import { LandingPageComponent } from "./hub/component/landing-page/landing-page.component"; -import { DASHBOARD_ABOUT, DASHBOARD_USER_WORKFLOW } from "./app-routing.constant"; +import { DASHBOARD_USER_WORKFLOW } from "./app-routing.constant"; import { HubSearchResultComponent } from "./hub/component/hub-search-result/hub-search-result.component"; import { AdminSettingsComponent } from "./dashboard/component/admin/settings/admin-settings.component"; -import { GuiConfigService } from "./common/service/gui-config.service"; - -const rootRedirectGuard: CanActivateFn = () => { - const config = inject(GuiConfigService); - const router = inject(Router); - try { - return router.parseUrl(DASHBOARD_ABOUT); - } catch { - // config not loaded yet, swallow the error and let the app handle it - } - return true; -}; const routes: Routes = []; routes.push({ - path: "dashboard", + path: "", component: DashboardComponent, children: [ + { + path: "", + redirectTo: "about", + pathMatch: "full", + }, { path: "home", component: LandingPageComponent, @@ -174,14 +167,6 @@ routes.push({ ], }); -// default route renders the workspace editor directly; if userSystem is enabled at runtime, -// AppComponent will navigate to DASHBOARD_ABOUT instead. -routes.push({ - path: "", - component: WorkspaceComponent, - canActivate: [rootRedirectGuard], -}); - // redirect all other paths to index. routes.push({ path: "**", diff --git a/frontend/src/app/dashboard/component/admin/execution/admin-execution.component.html b/frontend/src/app/dashboard/component/admin/execution/admin-execution.component.html index 41a2ceb9656..907ffa8d727 100644 --- a/frontend/src/app/dashboard/component/admin/execution/admin-execution.component.html +++ b/frontend/src/app/dashboard/component/admin/execution/admin-execution.component.html @@ -100,7 +100,7 @@
- + {{ maxStringLength(execution.workflowName, 16) }} ({{ execution.workflowId }})
diff --git a/frontend/src/app/dashboard/component/dashboard.component.spec.ts b/frontend/src/app/dashboard/component/dashboard.component.spec.ts index 6f508ca136a..3e211eb4fff 100644 --- a/frontend/src/app/dashboard/component/dashboard.component.spec.ts +++ b/frontend/src/app/dashboard/component/dashboard.component.spec.ts @@ -80,8 +80,8 @@ describe("DashboardComponent", () => { }; routerMock = { - events: of(new NavigationEnd(1, "/dashboard", "/dashboard")), - url: "/dashboard", + events: of(new NavigationEnd(1, "/", "/")), + url: "/", navigateByUrl: vi.fn(), }; diff --git a/frontend/src/app/dashboard/component/dashboard.component.ts b/frontend/src/app/dashboard/component/dashboard.component.ts index 57e6e8e284e..2b8374bfb84 100644 --- a/frontend/src/app/dashboard/component/dashboard.component.ts +++ b/frontend/src/app/dashboard/component/dashboard.component.ts @@ -232,7 +232,7 @@ export class DashboardComponent implements OnInit { isNavbarEnabled(currentRoute: string) { // Hide navbar for workflow workspace pages (with numeric ID) - if (currentRoute.match(/\/dashboard\/user\/workflow\/\d+/)) { + if (currentRoute.match(/\/user\/workflow\/\d+/)) { return false; } return true; diff --git a/frontend/src/app/dashboard/component/user/share-access/share-access.component.ts b/frontend/src/app/dashboard/component/user/share-access/share-access.component.ts index 576e104aede..0833f778830 100644 --- a/frontend/src/app/dashboard/component/user/share-access/share-access.component.ts +++ b/frontend/src/app/dashboard/component/user/share-access/share-access.component.ts @@ -190,7 +190,7 @@ export class ShareAccessComponent implements OnInit, OnDestroy { this.emailTags.forEach(email => { let message = `${this.userService.getCurrentUser()?.email} shared a ${this.type} with you`; if (this.type !== "computing-unit") - message += `, access the ${this.type} at ${location.origin}/dashboard/user/workflow/${this.id}`; + message += `, access the ${this.type} at ${location.origin}/user/workflow/${this.id}`; this.accessService .grantAccess(this.type, this.id, email, this.validateForm.value.accessLevel) .pipe(untilDestroyed(this)) From d2502d382eba6320dd1443d51a8599176c305d9b Mon Sep 17 00:00:00 2001 From: Matthew Ball Date: Fri, 22 May 2026 02:57:49 -0700 Subject: [PATCH 5/5] replace git hash with build number in UI footer --- frontend/{git-version.js => build-version.js} | 16 +++++------ frontend/package.json | 4 +-- .../component/dashboard.component.html | 2 +- .../component/dashboard.component.scss | 2 +- .../component/dashboard.component.ts | 2 +- frontend/yarn.lock | 27 ++++++++----------- 6 files changed, 23 insertions(+), 30 deletions(-) rename frontend/{git-version.js => build-version.js} (82%) diff --git a/frontend/git-version.js b/frontend/build-version.js similarity index 82% rename from frontend/git-version.js rename to frontend/build-version.js index 54cfdf8d3f5..97a3bbd1c84 100644 --- a/frontend/git-version.js +++ b/frontend/build-version.js @@ -17,17 +17,12 @@ * under the License. */ -const { gitDescribeSync } = require("git-describe"); +const { generate } = require("build-number-generator"); const { version } = require("./package.json"); const { resolve, relative } = require("path"); const { writeFileSync, existsSync, mkdirSync } = require("fs-extra"); -const gitInfo = gitDescribeSync({ - dirtyMark: false, - dirtySemver: false, -}); - -gitInfo.version = version; +const buildNumber = generate(version); if (!existsSync(__dirname + "/src/environments")) { mkdirSync(__dirname + "/src/environments"); @@ -37,10 +32,13 @@ writeFileSync( file, `// IMPORTANT: THIS FILE IS AUTO GENERATED! DO NOT MANUALLY EDIT OR CHECKIN! /* tslint:disable */ -export const Version = ${JSON.stringify(gitInfo, null, 4)}; +export const Version = { + "buildNumber": ${JSON.stringify(buildNumber)}, + "version": ${JSON.stringify(version)} +}; /* tslint:enable */ `, { encoding: "utf-8" } ); -console.log(`Wrote version info ${gitInfo.raw} to ${relative(resolve(__dirname, ".."), file)}`); +console.log(`Wrote build number ${buildNumber} to ${relative(resolve(__dirname, ".."), file)}`); diff --git a/frontend/package.json b/frontend/package.json index 481e720d976..6075a8dec84 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -17,7 +17,7 @@ "eslint:fix": "yarn eslint --fix ./src", "format:fix": "yarn prettier-eslint --write \"src/**/*.{ts,js,html,scss,less,json}\"", "format:ci": "yarn prettier-eslint --list-different \"src/**/*.{ts,js,html,scss,less,json}\" && yarn eslint ./src", - "postinstall": "node git-version.js" + "postinstall": "node build-version.js" }, "private": true, "dependencies": { @@ -122,12 +122,12 @@ "@vitest/browser": "4.1.5", "@vitest/browser-playwright": "4.1.5", "@vitest/coverage-v8": "4.1.5", + "build-number-generator": "3.1.0", "concurrently": "7.4.0", "eslint": "8.57.0", "eslint-plugin-rxjs": "5.0.3", "eslint-plugin-rxjs-angular": "2.0.1", "fs-extra": "10.0.1", - "git-describe": "4.1.0", "jsdom": "25.0.1", "nodecat": "2.0.0", "nx": "22.7.0", diff --git a/frontend/src/app/dashboard/component/dashboard.component.html b/frontend/src/app/dashboard/component/dashboard.component.html index 9edea629159..27e4af82dd5 100644 --- a/frontend/src/app/dashboard/component/dashboard.component.html +++ b/frontend/src/app/dashboard/component/dashboard.component.html @@ -201,7 +201,7 @@ About - Git hash: {{ gitCommitHash }} + Build: {{ buildNumber }} diff --git a/frontend/src/app/dashboard/component/dashboard.component.scss b/frontend/src/app/dashboard/component/dashboard.component.scss index edbe88658fb..3e404ecf6f7 100644 --- a/frontend/src/app/dashboard/component/dashboard.component.scss +++ b/frontend/src/app/dashboard/component/dashboard.component.scss @@ -100,7 +100,7 @@ nz-content { padding: 5px 0; } -#git-commit-id { +#build-number { position: absolute; left: 5px; bottom: 5px; diff --git a/frontend/src/app/dashboard/component/dashboard.component.ts b/frontend/src/app/dashboard/component/dashboard.component.ts index 2b8374bfb84..0d8354d72d2 100644 --- a/frontend/src/app/dashboard/component/dashboard.component.ts +++ b/frontend/src/app/dashboard/component/dashboard.component.ts @@ -83,7 +83,7 @@ export class DashboardComponent implements OnInit { isAdmin: boolean = this.userService.isAdmin(); isLogin = this.userService.isLogin(); - public gitCommitHash: string = Version.raw; + public buildNumber: string = Version.buildNumber; displayForum: boolean = true; displayNavbar: boolean = true; isCollapsed: boolean = false; diff --git a/frontend/yarn.lock b/frontend/yarn.lock index b929da0c62c..46a49e1e1ec 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -6397,7 +6397,7 @@ __metadata: languageName: node linkType: hard -"@types/semver@npm:^7.3.12, @types/semver@npm:^7.3.8": +"@types/semver@npm:^7.3.12": version: 7.7.1 resolution: "@types/semver@npm:7.7.1" checksum: 10c0/c938aef3bf79a73f0f3f6037c16e2e759ff40c54122ddf0b2583703393d8d3127130823facb880e694caa324eb6845628186aac1997ee8b31dc2d18fafe26268 @@ -8135,6 +8135,15 @@ __metadata: languageName: node linkType: hard +"build-number-generator@npm:3.1.0": + version: 3.1.0 + resolution: "build-number-generator@npm:3.1.0" + bin: + buildnumgen: bin/buildnumgen.js + checksum: 10c0/10d2366b74493a12aa7390e08dc5cd27f77ba600234c757e59ae9436eb7b7dde837322d34f0e177de5e5da3f4e5cabb089a03f9fcb32195ceaecd3ebab0b3c5b + languageName: node + linkType: hard + "bundle-name@npm:^4.1.0": version: 4.1.0 resolution: "bundle-name@npm:4.1.0" @@ -10869,20 +10878,6 @@ __metadata: languageName: node linkType: hard -"git-describe@npm:4.1.0": - version: 4.1.0 - resolution: "git-describe@npm:4.1.0" - dependencies: - "@types/semver": "npm:^7.3.8" - lodash: "npm:^4.17.21" - semver: "npm:^5.6.0" - dependenciesMeta: - semver: - optional: true - checksum: 10c0/2e5cbb0f5aa4a6f4dc9135276a85a29ec627a88dd0d73b63d1a3cd2ec2477a8d1d5fc83ae480a00c587c0ea8a193c6a9696a5b549dc6af02db0be74cdfac0eb5 - languageName: node - linkType: hard - "glob-parent@npm:^5.1.2, glob-parent@npm:~5.1.2": version: 5.1.2 resolution: "glob-parent@npm:5.1.2" @@ -11094,6 +11089,7 @@ __metadata: "@vitest/coverage-v8": "npm:4.1.5" ai: "npm:5.0.93" ajv: "npm:8.10.0" + build-number-generator: "npm:3.1.0" concaveman: "npm:2.0.0" concurrently: "npm:7.4.0" d3-shape: "npm:2.1.0" @@ -11104,7 +11100,6 @@ __metadata: file-saver: "npm:2.0.5" fs-extra: "npm:10.0.1" fuse.js: "npm:6.5.3" - git-describe: "npm:4.1.0" html2canvas: "npm:1.4.1" jointjs: "npm:3.5.4" jsdom: "npm:25.0.1"