diff --git a/ui/app/components/breadcrumbs/job.hbs b/ui/app/components/breadcrumbs/job.hbs index 87e5e2a7b81..d3956485f06 100644 --- a/ui/app/components/breadcrumbs/job.hbs +++ b/ui/app/components/breadcrumbs/job.hbs @@ -12,8 +12,7 @@
  • @@ -30,8 +29,7 @@
  • diff --git a/ui/app/components/job-dispatch.js b/ui/app/components/job-dispatch.js index d0a5af2d23a..3300c5304cc 100644 --- a/ui/app/components/job-dispatch.js +++ b/ui/app/components/job-dispatch.js @@ -104,9 +104,12 @@ export default class JobDispatch extends Component { const dispatch = yield this.args.job.dispatch(paramValues, this.payload); // Navigate to the newly created instance. - this.router.transitionTo('jobs.job', dispatch.DispatchedJobID, { - queryParams: { namespace: this.args.job.get('namespace.name') }, - }); + const namespaceId = this.args.job.belongsTo('namespace').id(); + const jobId = namespaceId + ? `${dispatch.DispatchedJobID}@${namespaceId}` + : dispatch.DispatchedJobID; + + this.router.transitionTo('jobs.job', jobId); } catch (err) { const error = messageFromAdapterError(err) || 'Could not dispatch job'; this.errors.pushObject(error); diff --git a/ui/app/components/job-page/parts/job-client-status-summary.js b/ui/app/components/job-page/parts/job-client-status-summary.js index 6aa683ce39c..2464b2dd3c6 100644 --- a/ui/app/components/job-page/parts/job-client-status-summary.js +++ b/ui/app/components/job-page/parts/job-client-status-summary.js @@ -24,7 +24,6 @@ export default class JobClientStatusSummary extends Component { this.router.transitionTo('jobs.job.clients', this.job, { queryParams: { status: JSON.stringify(statusFilter), - namespace: this.job.get('namespace.name'), }, }); } diff --git a/ui/app/components/job-row.js b/ui/app/components/job-row.js index c353813c0bd..107d47fc93a 100644 --- a/ui/app/components/job-row.js +++ b/ui/app/components/job-row.js @@ -26,8 +26,6 @@ export default class JobRow extends Component { @action gotoJob() { const { job } = this; - this.router.transitionTo('jobs.job', job.plainId, { - queryParams: { namespace: job.get('namespace.name') }, - }); + this.router.transitionTo('jobs.job.index', job.idWithNamespace); } } diff --git a/ui/app/controllers/allocations/allocation.js b/ui/app/controllers/allocations/allocation.js index 142c9f3ff68..b6ebcbacd81 100644 --- a/ui/app/controllers/allocations/allocation.js +++ b/ui/app/controllers/allocations/allocation.js @@ -37,9 +37,8 @@ export default class AllocationsAllocationController extends Controller { label: allocation.taskGroupName, args: [ 'jobs.job.task-group', - job.plainId, + job.idWithNamespace, allocation.taskGroupName, - jobQueryParams, ], }, { diff --git a/ui/app/controllers/csi/volumes/index.js b/ui/app/controllers/csi/volumes/index.js index a7ca5b9efdb..43c30a22695 100644 --- a/ui/app/controllers/csi/volumes/index.js +++ b/ui/app/controllers/csi/volumes/index.js @@ -114,9 +114,10 @@ export default class IndexController extends Controller.extend( gotoVolume(volume, event) { lazyClick([ () => - this.transitionToRoute('csi.volumes.volume', volume.get('plainId'), { - queryParams: { volumeNamespace: volume.get('namespace.name') }, - }), + this.transitionToRoute( + 'csi.volumes.volume', + volume.get('idWithNamespace') + ), event, ]); } diff --git a/ui/app/controllers/jobs/job/task-group.js b/ui/app/controllers/jobs/job/task-group.js index 592f1508e0d..d02252e0a5b 100644 --- a/ui/app/controllers/jobs/job/task-group.js +++ b/ui/app/controllers/jobs/job/task-group.js @@ -5,7 +5,6 @@ import Controller from '@ember/controller'; import { action, computed, get } from '@ember/object'; import { scheduleOnce } from '@ember/runloop'; import intersection from 'lodash.intersection'; -import { qpBuilder } from 'nomad-ui/utils/classes/query-params'; import Sortable from 'nomad-ui/mixins/sortable'; import Searchable from 'nomad-ui/mixins/searchable'; import WithNamespaceResetting from 'nomad-ui/mixins/with-namespace-resetting'; @@ -174,12 +173,7 @@ export default class TaskGroupController extends Controller.extend( return { title: 'Task Group', label: name, - args: [ - 'jobs.job.task-group', - job, - name, - qpBuilder({ jobNamespace: job.get('namespace.name') || 'default' }), - ], + args: ['jobs.job.task-group', job, name], }; } } diff --git a/ui/app/models/job.js b/ui/app/models/job.js index b157592efa5..2e921383e86 100644 --- a/ui/app/models/job.js +++ b/ui/app/models/job.js @@ -35,6 +35,17 @@ export default class Job extends Model { @attr() periodicDetails; @attr() parameterizedDetails; + @computed('plainId') + get idWithNamespace() { + const namespaceId = this.belongsTo('namespace').id(); + + if (!namespaceId || namespaceId === 'default') { + return this.plainId; + } else { + return `${this.plainId}@${namespaceId}`; + } + } + @computed('periodic', 'parameterized', 'dispatched') get hasChildren() { return this.periodic || (this.parameterized && !this.dispatched); diff --git a/ui/app/models/volume.js b/ui/app/models/volume.js index f7de4b1f0b2..c230cfda833 100644 --- a/ui/app/models/volume.js +++ b/ui/app/models/volume.js @@ -40,6 +40,13 @@ export default class Volume extends Model { @attr('number') controllersHealthy; @attr('number') controllersExpected; + @computed('plainId') + get idWithNamespace() { + // does this handle default namespace -- I think the backend handles this for us + // but the client would always need to recreate that logic + return `${this.plainId}@${this.belongsTo('namespace').id()}`; + } + @computed('controllersHealthy', 'controllersExpected') get controllersHealthyProportion() { return this.controllersHealthy / this.controllersExpected; diff --git a/ui/app/routes/csi/volumes/volume.js b/ui/app/routes/csi/volumes/volume.js index e5722374a0d..dbf0b44504f 100644 --- a/ui/app/routes/csi/volumes/volume.js +++ b/ui/app/routes/csi/volumes/volume.js @@ -21,13 +21,17 @@ export default class VolumeRoute extends Route.extend(WithWatchers) { } serialize(model) { - return { volume_name: model.get('plainId') }; + return { volume_name: JSON.parse(model.get('id')).join('@') }; } - model(params, transition) { - const namespace = transition.to.queryParams.namespace; - const name = params.volume_name; + model(params) { + // Issue with naming collissions + const url = params.volume_name.split('@'); + const namespace = url.pop(); + const name = url.join(''); + const fullId = JSON.stringify([`csi/${name}`, namespace || 'default']); + return RSVP.hash({ volume: this.store.findRecord('volume', fullId, { reload: true }), namespaces: this.store.findAll('namespace'), diff --git a/ui/app/routes/jobs/job.js b/ui/app/routes/jobs/job.js index a39c1618315..e5a50d939b2 100644 --- a/ui/app/routes/jobs/job.js +++ b/ui/app/routes/jobs/job.js @@ -11,12 +11,12 @@ export default class JobRoute extends Route { @service token; serialize(model) { - return { job_name: model.get('plainId') }; + return { job_name: model.get('idWithNamespace') }; } - model(params, transition) { - const namespace = transition.to.queryParams.namespace || 'default'; - const name = params.job_name; + model(params) { + const [name, namespace = 'default'] = params.job_name.split('@'); + const fullId = JSON.stringify([name, namespace]); return this.store diff --git a/ui/app/templates/allocations/allocation/index.hbs b/ui/app/templates/allocations/allocation/index.hbs index e049e7e2443..0069a9fba85 100644 --- a/ui/app/templates/allocations/allocation/index.hbs +++ b/ui/app/templates/allocations/allocation/index.hbs @@ -5,11 +5,19 @@
    -

    {{this.error.title}}

    +

    {{this.error.title}}

    {{this.error.description}}

    - +
    @@ -17,13 +25,19 @@

    - Allocation {{this.model.name}} - {{this.model.clientStatus}} + Allocation + {{this.model.name}} + {{this.model.clientStatus}}
    {{#if this.model.isRunning}}
    - +
    + @disabled={{or + this.stopAllocation.isRunning + this.restartAllocation.isRunning + }} + @onConfirm={{perform this.stopAllocation}} + /> + @disabled={{or + this.stopAllocation.isRunning + this.restartAllocation.isRunning + }} + @onConfirm={{perform this.restartAllocation}} + /> {{/if}}

    @@ -55,13 +77,24 @@
    -
    +
    Allocation Details Job - {{this.model.job.name}} + {{this.model.job.name}} Client - {{this.model.node.shortId}} + {{this.model.node.shortId}}
    @@ -74,16 +107,26 @@ {{#if this.model.isRunning}}
    - +
    - +
    {{else}}
    -

    Allocation isn't running

    -

    Only running allocations utilize resources.

    +

    Allocation isn't running

    +

    Only running allocations utilize + resources.

    {{/if}}
    @@ -95,13 +138,17 @@
    Tasks
    -
    +
    {{#if this.sortedStates.length}} + @class="is-striped" + as |t| + > Name @@ -116,13 +163,20 @@ + @onClick={{action "taskClick" row.model.allocation row.model}} + /> {{else}}
    -

    No Tasks

    -

    Allocations will not have tasks until they are in a running state.

    +

    No Tasks

    +

    Allocations will not have tasks until they are in a running state.

    {{/if}}
    @@ -144,7 +198,11 @@ {{row.model.label}} - {{row.model.hostIp}}:{{row.model.value}} + {{row.model.hostIp}}:{{row.model.value}} {{row.model.to}} @@ -173,11 +231,21 @@ {{row.model.name}} {{row.model.portLabel}} - {{join ", " row.model.tags}} + {{join + ", " + row.model.tags + }} {{row.model.onUpdate}} - {{if row.model.connect "Yes" "No"}} + {{if + row.model.connect + "Yes" + "No" + }} - {{#each row.model.connect.sidecarService.proxy.upstreams as |upstream|}} + {{#each + row.model.connect.sidecarService.proxy.upstreams + as |upstream| + }} {{upstream.destinationName}}:{{upstream.localBindPort}} {{/each}} @@ -207,48 +275,81 @@
    - + {{this.preempter.clientStatus}} - {{this.preempter.name}} - {{this.preempter.shortId}} + {{this.preempter.name}} + {{this.preempter.shortId}} Job - {{this.preempter.job.name}} + {{this.preempter.job.name}} Priority - {{this.preempter.job.priority}} + {{this.preempter.job.priority}} Client - {{this.preempter.node.shortId}} + {{this.preempter.node.shortId}} Reserved CPU - {{format-scheduled-hertz this.preempter.resources.cpu}} + {{format-scheduled-hertz + this.preempter.resources.cpu + }} Reserved Memory - {{format-scheduled-bytes this.preempter.resources.memory start="MiB"}} + {{format-scheduled-bytes + this.preempter.resources.memory + start="MiB" + }}
    {{else}}

    Allocation is gone

    -

    This allocation has been stopped and garbage collected.

    +

    This allocation has been stopped and + garbage collected.

    {{/if}}
    {{/if}} - {{#if (and this.model.preemptedAllocations.isFulfilled this.model.preemptedAllocations.length)}} + {{#if + (and + this.model.preemptedAllocations.isFulfilled + this.model.preemptedAllocations.length + ) + }}
    Preempted Allocations
    + @class="allocations is-isolated" + as |t| + > ID @@ -262,7 +363,11 @@ Memory - +
    diff --git a/ui/app/templates/allocations/allocation/task/index.hbs b/ui/app/templates/allocations/allocation/task/index.hbs index e033567c7b8..49e90ec3cac 100644 --- a/ui/app/templates/allocations/allocation/task/index.hbs +++ b/ui/app/templates/allocations/allocation/task/index.hbs @@ -5,23 +5,40 @@
    -

    {{this.error.title}}

    -

    {{this.error.description}}

    +

    + {{this.error.title}} +

    +

    + {{this.error.description}} +

    - +
    {{/if}} -

    {{this.model.name}} {{#if this.model.isConnectProxy}} {{/if}} - {{this.model.state}} + + {{this.model.state}} +
    {{#if this.model.isRunning}} @@ -30,7 +47,8 @@ @job={{this.model.task.taskGroup.job}} @taskGroup={{this.model.task.taskGroup}} @allocation={{this.model.allocation}} - @task={{this.model.task}} /> + @task={{this.model.task}} + />
    + @onConfirm={{perform this.restartTask}} + /> {{/if}}

    -
    - Task Details + + Task Details + - Started At + + Started At + {{format-ts this.model.startedAt}} {{#if this.model.finishedAt}} - Finished At + + Finished At + {{format-ts this.model.finishedAt}} {{/if}} - Driver + + Driver + {{this.model.task.driver}} - Lifecycle - {{this.model.task.lifecycleName}} + + Lifecycle + + + {{this.model.task.lifecycleName}} +
    -
    Resource Utilization @@ -86,13 +115,16 @@
    {{else}}
    -

    Task isn't running

    -

    Only running tasks utilize resources.

    +

    + Task isn't running +

    +

    + Only running tasks utilize resources. +

    {{/if}}
    - {{#if this.model.task.volumeMounts.length}}
    @@ -101,20 +133,40 @@
    - Name - Destination - Permissions - Client Source + + Name + + + Destination + + + Permissions + + + Client Source + - {{row.model.volume}} - {{row.model.destination}} - {{if row.model.readOnly "Read" "Read/Write"}} + + {{row.model.volume}} + + + + {{row.model.destination}} + + + + {{if row.model.readOnly "Read" "Read/Write"}} + {{#if row.model.isCSI}} - - {{row.model.source}} + + {{row.model.volume}} {{else}} {{row.model.source}} @@ -126,27 +178,41 @@
    {{/if}} -
    Recent Events
    - + - Time - Type - Description + + Time + + + Type + + + Description + - {{format-ts row.model.time}} - {{row.model.type}} + + {{format-ts row.model.time}} + + + {{row.model.type}} + {{#if row.model.message}} {{row.model.message}} {{else}} - No message + + No message + {{/if}} @@ -154,4 +220,4 @@
    - + \ No newline at end of file diff --git a/ui/app/templates/components/global-header.hbs b/ui/app/templates/components/global-header.hbs index ac01bde0aa0..b18b49d93ba 100644 --- a/ui/app/templates/components/global-header.hbs +++ b/ui/app/templates/components/global-header.hbs @@ -24,12 +24,20 @@ {{/if}} {{#if this.system.agent.config.UI.Consul.BaseUIURL}} - + Consul {{/if}} {{#if this.system.agent.config.UI.Vault.BaseUIURL}} - + Vault {{/if}} @@ -50,4 +58,4 @@ {{yield}} -
    + \ No newline at end of file diff --git a/ui/app/templates/components/job-row.hbs b/ui/app/templates/components/job-row.hbs index eeeb523e424..3146a931a2e 100644 --- a/ui/app/templates/components/job-row.hbs +++ b/ui/app/templates/components/job-row.hbs @@ -1,8 +1,7 @@ {{this.job.name}} @@ -47,7 +46,10 @@ {{/if}} {{else}} - + {{/if}} \ No newline at end of file diff --git a/ui/app/templates/components/job-subnav.hbs b/ui/app/templates/components/job-subnav.hbs index f9c11e8e6f6..13d260d7c79 100644 --- a/ui/app/templates/components/job-subnav.hbs +++ b/ui/app/templates/components/job-subnav.hbs @@ -3,7 +3,6 @@
  • @@ -24,7 +22,6 @@
  • @@ -35,7 +32,6 @@
  • @@ -46,7 +42,6 @@
  • @@ -56,7 +51,6 @@
  • @@ -67,7 +61,6 @@
  • diff --git a/ui/app/templates/components/task-row.hbs b/ui/app/templates/components/task-row.hbs index 151b5cca7c6..521f0b088d0 100644 --- a/ui/app/templates/components/task-row.hbs +++ b/ui/app/templates/components/task-row.hbs @@ -1,34 +1,54 @@ {{#unless this.task.driverStatus.healthy}} - + {{x-icon "alert-triangle" class="is-warning"}} {{/unless}} - + {{this.task.name}} {{#if this.task.isConnectProxy}} {{/if}} -{{this.task.state}} + + {{this.task.state}} + {{#if this.task.events.lastObject.message}} {{this.task.events.lastObject.message}} {{else}} - No message + + No message + {{/if}} -{{format-ts this.task.events.lastObject.time}} + + {{format-ts this.task.events.lastObject.time}} +
      {{#each this.task.task.volumeMounts as |volume|}}
    • - {{volume.volume}}: + + {{volume.volume}} + : + {{#if volume.isCSI}} - + {{volume.source}} {{else}} @@ -43,15 +63,26 @@ {{#if (and (not this.cpu) this.fetchStats.isRunning)}} ... {{else if this.statsError}} - + {{x-icon "alert-triangle" class="is-warning"}} {{else}} - {{/if}} {{#if this.isForbidden}} - {{else}} - {{#if this.sortedVolumes}} - - - - Name + {{else if this.sortedVolumes}} + + + + + Name + + {{#if this.system.shouldShowNamespaces}} + + Namespace + + {{/if}} + + Volume Health + + + Controller Health + + + Node Health + + + Provider + + + # Allocs + + + + + + + {{row.model.name}} + + {{#if this.system.shouldShowNamespaces}} - Namespace - {{/if}} - Volume Health - Controller Health - Node Health - Provider - # Allocs - - - - - - {{row.model.name}} - + + {{row.model.namespace.name}} - {{#if this.system.shouldShowNamespaces}} - {{row.model.namespace.name}} + {{/if}} + + {{if row.model.schedulable "Schedulable" "Unschedulable"}} + + + {{#if row.model.controllerRequired}} + {{if (gt row.model.controllersHealthy 0) "Healthy" "Unhealthy"}} + ( + {{row.model.controllersHealthy}} + / + {{row.model.controllersExpected}} + ) + {{else if (gt row.model.controllersExpected 0)}} + {{if (gt row.model.controllersHealthy 0) "Healthy" "Unhealthy"}} + ( + {{row.model.controllersHealthy}} + / + {{row.model.controllersExpected}} + ) + {{else}} + + Node Only + {{/if}} - {{if row.model.schedulable "Schedulable" "Unschedulable"}} - - {{#if row.model.controllerRequired}} - {{if (gt row.model.controllersHealthy 0) "Healthy" "Unhealthy"}} - ({{row.model.controllersHealthy}}/{{row.model.controllersExpected}}) - {{else}} - {{#if (gt row.model.controllersExpected 0)}} - {{if (gt row.model.controllersHealthy 0) "Healthy" "Unhealthy"}} - ({{row.model.controllersHealthy}}/{{row.model.controllersExpected}}) - {{else}} - Node Only - {{/if}} - {{/if}} - - - {{if (gt row.model.nodesHealthy 0) "Healthy" "Unhealthy"}} - ({{row.model.nodesHealthy}}/{{row.model.nodesExpected}}) - - {{row.model.provider}} - {{row.model.allocationCount}} - - - -
      - - -
      -
      - {{else}} -
      - {{#if (eq this.visibleVolumes.length 0)}} -

      No Volumes

      -

      - This namespace currently has no CSI Volumes. -

      - {{else if this.searchTerm}} -

      No Matches

      -

      - No volumes match the term {{this.searchTerm}} -

      - {{/if}} + + + {{if (gt row.model.nodesHealthy 0) "Healthy" "Unhealthy"}} + ( + {{row.model.nodesHealthy}} + / + {{row.model.nodesExpected}} + ) + + + {{row.model.provider}} + + + {{row.model.allocationCount}} + + + + +
      + +
      - {{/if}} + + {{else}} +
      + {{#if (eq this.visibleVolumes.length 0)}} +

      + No Volumes +

      +

      + This namespace currently has no CSI Volumes. +

      + {{else if this.searchTerm}} +

      + No Matches +

      +

      + No volumes match the term + + {{this.searchTerm}} + +

      + {{/if}} +
      {{/if}} - + \ No newline at end of file diff --git a/ui/app/templates/jobs/index.hbs b/ui/app/templates/jobs/index.hbs index 903453591f9..e08aa1335e7 100644 --- a/ui/app/templates/jobs/index.hbs +++ b/ui/app/templates/jobs/index.hbs @@ -44,7 +44,10 @@ @options={{this.optionsNamespaces}} @selection={{this.qpNamespace}} @onSelect={{action - (queue (action this.cacheNamespace) (action this.setFacetQueryParam "qpNamespace")) + (queue + (action this.cacheNamespace) + (action this.setFacetQueryParam "qpNamespace") + ) }} /> {{/if}} @@ -109,13 +112,15 @@ diff --git a/ui/app/templates/jobs/job/task-group.hbs b/ui/app/templates/jobs/job/task-group.hbs index ce4468b5342..2402b5df63a 100644 --- a/ui/app/templates/jobs/job/task-group.hbs +++ b/ui/app/templates/jobs/job/task-group.hbs @@ -2,64 +2,116 @@ {{page-title "Task group " this.model.name " - Job " this.model.job.name}}
        -
      • Overview
      • +
      • + + Overview + +

      - {{this.model.name}} + + {{this.model.name}} +
      {{#if this.model.scaling}} - - Count - + + Count + {{/if}}

      -
      - Task Group Details - - # Tasks {{this.model.tasks.length}} - Reserved CPU {{format-scheduled-hertz this.model.reservedCPU}} + + Task Group Details + + + + # Tasks + + {{this.model.tasks.length}} + + + + Reserved CPU + + {{format-scheduled-hertz this.model.reservedCPU}} + - Reserved Memory + + Reserved Memory + {{format-scheduled-bytes this.model.reservedMemory start="MiB"}} {{#if (gt this.model.reservedMemoryMax this.model.reservedMemory)}} - ({{format-scheduled-bytes this.model.reservedMemoryMax start="MiB"}} Max) + ({{format-scheduled-bytes this.model.reservedMemoryMax start="MiB"}}Max) {{/if}} - Reserved Disk {{format-scheduled-bytes this.model.reservedEphemeralDisk start="MiB"}} + + + Reserved Disk + + {{format-scheduled-bytes this.model.reservedEphemeralDisk start="MiB"}} + {{#if this.model.scaling}} - Count Range - {{this.model.scaling.min}} to {{this.model.scaling.max}} + + + Count Range + + {{this.model.scaling.min}} + to + {{this.model.scaling.max}} - Scaling Policy? + + + Scaling Policy? + {{if this.model.scaling.policy "Yes" "No"}} {{/if}}
      -
      -
      Allocation Status {{this.allocations.length}}
      +
      + Allocation Status + + {{this.allocations.length}} + +
      - +
        {{#each chart.data as |datum index|}} -
      1. +
      2. {{/each}} @@ -100,62 +152,102 @@ @source={{this.sortedAllocations}} @size={{this.pageSize}} @page={{this.currentPage}} - @class="allocations" as |p|> + @class="allocations" as |p| + > + @class="with-foot" as |t| + > - ID - Created - Modified - Status - Version - Client - Volume - CPU - Memory + + ID + + + Created + + + Modified + + + Status + + + Version + + + Client + + + Volume + + + CPU + + + Memory + - +
        - {{else}} - {{#if this.allocations.length}} -
        -
        -

        No Matches

        -

        No allocations match the term {{this.searchTerm}}

        -
        + {{else if this.allocations.length}} +
        +
        +

        + No Matches +

        +

        + No allocations match the term + + {{this.searchTerm}} + +

        - {{else}} -
        -
        -

        No Allocations

        -

        No allocations have been placed.

        -
        +
        + {{else}} +
        +
        +

        + No Allocations +

        +

        + No allocations have been placed. +

        - {{/if}} +
        {{/if}}
        - - {{#if this.model.scaleState.isVisible}} {{#if this.shouldShowScaleEventTimeline}}
        @@ -167,7 +259,6 @@
      {{/if}} -
      Recent Scaling Events @@ -177,7 +268,6 @@
      {{/if}} - {{#if this.model.volumes.length}}
      @@ -186,29 +276,46 @@
      - Name - Type - Source - Permissions + + Name + + + Type + + + Source + + + Permissions + {{#if row.model.isCSI}} - + {{row.model.name}} {{else}} {{row.model.name}} {{/if}} - {{row.model.type}} - {{row.model.source}} - {{if row.model.readOnly "Read" "Read/Write"}} + + {{row.model.type}} + + + {{row.model.source}} + + + {{if row.model.readOnly "Read" "Read/Write"}} +
      {{/if}} -
      + \ No newline at end of file diff --git a/ui/config/environment.js b/ui/config/environment.js index d0bfae30743..5cfb8fc4b08 100644 --- a/ui/config/environment.js +++ b/ui/config/environment.js @@ -25,8 +25,8 @@ module.exports = function (environment) { APP: { blockingQueries: true, - mirageScenario: 'topoMedium', - mirageWithNamespaces: false, + mirageScenario: 'smallCluster', + mirageWithNamespaces: true, mirageWithTokens: true, mirageWithRegions: true, showStorybookLink: process.env.STORYBOOK_LINK === 'true', diff --git a/ui/tests/acceptance/allocation-detail-test.js b/ui/tests/acceptance/allocation-detail-test.js index a22ff469fa1..ed447d45699 100644 --- a/ui/tests/acceptance/allocation-detail-test.js +++ b/ui/tests/acceptance/allocation-detail-test.js @@ -499,6 +499,7 @@ module('Acceptance | allocation detail (preemptions)', function (hooks) { ); await Allocation.visit({ id: allocation.id }); + await Allocation.preempter.visitJob(); assert.equal( currentURL(), diff --git a/ui/tests/acceptance/job-detail-test.js b/ui/tests/acceptance/job-detail-test.js index e6027ef3c95..4dc485cf1b4 100644 --- a/ui/tests/acceptance/job-detail-test.js +++ b/ui/tests/acceptance/job-detail-test.js @@ -251,13 +251,16 @@ module('Acceptance | job detail (with namespaces)', function (hooks) { test('it passes an accessibility audit', async function (assert) { const namespace = server.db.namespaces.find(job.namespaceId); - await JobDetail.visit({ id: job.id, namespace: namespace.name }); + await JobDetail.visit({ id: `${job.id}@${namespace.name}` }); await a11yAudit(assert); }); test('when there are namespaces, the job detail page states the namespace for the job', async function (assert) { const namespace = server.db.namespaces.find(job.namespaceId); - await JobDetail.visit({ id: job.id, namespace: namespace.name }); + + await JobDetail.visit({ + id: `${job.id}@${namespace.name}`, + }); assert.ok( JobDetail.statFor('namespace').text, @@ -301,7 +304,8 @@ module('Acceptance | job detail (with namespaces)', function (hooks) { assert.notOk(JobDetail.execButton.isDisabled); const secondNamespace = server.db.namespaces[1]; - await JobDetail.visit({ id: job2.id, namespace: secondNamespace.name }); + await JobDetail.visit({ id: `${job2.id}@${secondNamespace.name}` }); + assert.ok(JobDetail.execButton.isDisabled); }); @@ -322,9 +326,9 @@ module('Acceptance | job detail (with namespaces)', function (hooks) { }); await JobDetail.visit({ - id: job.id, - namespace: server.db.namespaces[1].name, + id: `${job.id}@${server.db.namespaces[1].name}`, }); + assert.notOk(JobDetail.execButton.isDisabled); }); @@ -338,14 +342,13 @@ module('Acceptance | job detail (with namespaces)', function (hooks) { }); await JobDetail.visit({ - id: job.id, - namespace: server.db.namespaces[1].name, + id: `${job.id}@${server.db.namespaces[1].name}`, }); + assert.notOk(JobDetail.metaTable, 'Meta table not present'); await JobDetail.visit({ - id: jobWithMeta.id, - namespace: server.db.namespaces[1].name, + id: `${jobWithMeta.id}@${server.db.namespaces[1].name}`, }); assert.ok(JobDetail.metaTable, 'Meta table is present'); }); @@ -361,7 +364,8 @@ module('Acceptance | job detail (with namespaces)', function (hooks) { }, }); - await JobDetail.visit({ id: jobFromPack.id, namespace }); + await JobDetail.visit({ id: `${jobFromPack.id}@${namespace}` }); + assert.ok(JobDetail.packTag, 'Pack tag is present'); assert.equal( JobDetail.packStatFor('name').text, @@ -388,8 +392,7 @@ module('Acceptance | job detail (with namespaces)', function (hooks) { window.localStorage.nomadTokenSecret = managementToken.secretId; await JobDetail.visit({ - id: job.id, - namespace: server.db.namespaces[1].name, + id: `${job.id}@${server.db.namespaces[1].name}`, }); const groupsWithRecommendations = job.taskGroups.filter((group) => @@ -439,8 +442,7 @@ module('Acceptance | job detail (with namespaces)', function (hooks) { test('resource recommendations are not fetched when the feature doesn’t exist', async function (assert) { window.localStorage.nomadTokenSecret = managementToken.secretId; await JobDetail.visit({ - id: job.id, - namespace: server.db.namespaces[1].name, + id: `${job.id}@${server.db.namespaces[1].name}`, }); assert.equal(JobDetail.recommendations.length, 0); @@ -518,10 +520,10 @@ module('Acceptance | job detail (with namespaces)', function (hooks) { clientToken.save(); window.localStorage.nomadTokenSecret = clientToken.secretId; - await JobDetail.visit({ id: job.id, namespace: namespace.name }); + await JobDetail.visit({ id: `${job.id}@${namespace.name}` }); assert.notOk(JobDetail.incrementButton.isDisabled); - await JobDetail.visit({ id: job2.id, namespace: secondNamespace.name }); + await JobDetail.visit({ id: `${job2.id}@${secondNamespace.name}` }); assert.ok(JobDetail.incrementButton.isDisabled); }); }); diff --git a/ui/tests/acceptance/job-dispatch-test.js b/ui/tests/acceptance/job-dispatch-test.js index ef476431074..e7f0e011370 100644 --- a/ui/tests/acceptance/job-dispatch-test.js +++ b/ui/tests/acceptance/job-dispatch-test.js @@ -54,12 +54,12 @@ function moduleForJobDispatch(title, jobFactory) { }); test('it passes an accessibility audit', async function (assert) { - await JobDispatch.visit({ id: job.id, namespace: namespace.name }); + await JobDispatch.visit({ id: `${job.id}@${namespace.name}` }); await a11yAudit(assert); }); test('the dispatch button is displayed with management token', async function (assert) { - await JobDetail.visit({ id: job.id, namespace: namespace.name }); + await JobDetail.visit({ id: `${job.id}@${namespace.name}` }); assert.notOk(JobDetail.dispatchButton.isDisabled); }); @@ -82,7 +82,7 @@ function moduleForJobDispatch(title, jobFactory) { clientToken.policyIds = [policy.id]; clientToken.save(); - await JobDetail.visit({ id: job.id, namespace: namespace.name }); + await JobDetail.visit({ id: `${job.id}@${namespace.name}` }); assert.notOk(JobDetail.dispatchButton.isDisabled); // Reset clientToken policies. @@ -93,12 +93,12 @@ function moduleForJobDispatch(title, jobFactory) { test('the dispatch button is disabled when not allowed', async function (assert) { window.localStorage.nomadTokenSecret = clientToken.secretId; - await JobDetail.visit({ id: job.id, namespace: namespace.name }); + await JobDetail.visit({ id: `${job.id}@${namespace.name}` }); assert.ok(JobDetail.dispatchButton.isDisabled); }); test('all meta fields are displayed', async function (assert) { - await JobDispatch.visit({ id: job.id, namespace: namespace.name }); + await JobDispatch.visit({ id: `${job.id}@${namespace.name}` }); assert.equal( JobDispatch.metaFields.length, job.parameterizedJob.MetaOptional.length + @@ -107,7 +107,7 @@ function moduleForJobDispatch(title, jobFactory) { }); test('required meta fields are properly indicated', async function (assert) { - await JobDispatch.visit({ id: job.id, namespace: namespace.name }); + await JobDispatch.visit({ id: `${job.id}@${namespace.name}` }); JobDispatch.metaFields.forEach((f) => { const hasIndicator = f.label.includes(REQUIRED_INDICATOR); @@ -136,10 +136,7 @@ function moduleForJobDispatch(title, jobFactory) { }, }); - await JobDispatch.visit({ - id: jobWithoutMeta.id, - namespace: namespace.name, - }); + await JobDispatch.visit({ id: `${jobWithoutMeta.id}@${namespace.name}` }); assert.ok(JobDispatch.dispatchButton.isPresent); }); @@ -147,7 +144,7 @@ function moduleForJobDispatch(title, jobFactory) { job.parameterizedJob.Payload = 'forbidden'; job.save(); - await JobDispatch.visit({ id: job.id, namespace: namespace.name }); + await JobDispatch.visit({ id: `${job.id}@${namespace.name}` }); assert.ok(JobDispatch.payload.emptyMessage.isPresent); assert.notOk(JobDispatch.payload.editor.isPresent); @@ -170,8 +167,7 @@ function moduleForJobDispatch(title, jobFactory) { }); await JobDispatch.visit({ - id: jobPayloadRequired.id, - namespace: namespace.name, + id: `${jobPayloadRequired.id}@${namespace.name}`, }); let payloadTitle = JobDispatch.payload.title; @@ -181,8 +177,7 @@ function moduleForJobDispatch(title, jobFactory) { ); await JobDispatch.visit({ - id: jobPayloadOptional.id, - namespace: namespace.name, + id: `${jobPayloadOptional.id}@${namespace.name}`, }); payloadTitle = JobDispatch.payload.title; @@ -199,7 +194,7 @@ function moduleForJobDispatch(title, jobFactory) { ).length; } - await JobDispatch.visit({ id: job.id, namespace: namespace.name }); + await JobDispatch.visit({ id: `${job.id}@${namespace.name}` }); // Fill form. JobDispatch.metaFields.map((f) => f.field.input('meta value')); @@ -222,7 +217,7 @@ function moduleForJobDispatch(title, jobFactory) { job.parameterizedJob.Payload = 'forbidden'; job.save(); - await JobDispatch.visit({ id: job.id, namespace: namespace.name }); + await JobDispatch.visit({ id: `${job.id}@${namespace.name}` }); // Fill only optional meta params. JobDispatch.optionalMetaFields.map((f) => f.field.input('meta value')); @@ -237,7 +232,7 @@ function moduleForJobDispatch(title, jobFactory) { job.parameterizedJob.Payload = 'required'; job.save(); - await JobDispatch.visit({ id: job.id, namespace: namespace.name }); + await JobDispatch.visit({ id: `${job.id}@${namespace.name}` }); await JobDispatch.dispatchButton.click(); assert.ok(JobDispatch.hasError, 'Dispatch error message is shown'); diff --git a/ui/tests/acceptance/job-versions-test.js b/ui/tests/acceptance/job-versions-test.js index af43ded0a28..596c5df8880 100644 --- a/ui/tests/acceptance/job-versions-test.js +++ b/ui/tests/acceptance/job-versions-test.js @@ -30,7 +30,7 @@ module('Acceptance | job versions', function (hooks) { const managementToken = server.create('token'); window.localStorage.nomadTokenSecret = managementToken.secretId; - await Versions.visit({ id: job.id, namespace: namespace.id }); + await Versions.visit({ id: `${job.id}@${namespace.id}` }); }); test('it passes an accessibility audit', async function (assert) { diff --git a/ui/tests/acceptance/task-detail-test.js b/ui/tests/acceptance/task-detail-test.js index b9adbb3d082..ab3f8c16766 100644 --- a/ui/tests/acceptance/task-detail-test.js +++ b/ui/tests/acceptance/task-detail-test.js @@ -375,7 +375,7 @@ module('Acceptance | task detail (different namespace)', function (hooks) { await Layout.breadcrumbFor('jobs.job.index').visit(); assert.equal( currentURL(), - `/jobs/${job.id}?namespace=other-namespace`, + `/jobs/${job.id}@other-namespace`, 'Job breadcrumb links correctly' ); @@ -383,7 +383,7 @@ module('Acceptance | task detail (different namespace)', function (hooks) { await Layout.breadcrumbFor('jobs.job.task-group').visit(); assert.equal( currentURL(), - `/jobs/${job.id}/${taskGroup}?namespace=other-namespace`, + `/jobs/${job.id}@other-namespace/${taskGroup}`, 'Task Group breadcrumb links correctly' ); diff --git a/ui/tests/acceptance/task-group-detail-test.js b/ui/tests/acceptance/task-group-detail-test.js index 97facf08c54..dc54767564a 100644 --- a/ui/tests/acceptance/task-group-detail-test.js +++ b/ui/tests/acceptance/task-group-detail-test.js @@ -104,7 +104,7 @@ module('Acceptance | task group detail', function (hooks) { totalMemoryMaxAddendum = ` (${formatScheduledBytes( totalMemoryMax, 'MiB' - )} Max)`; + )}Max)`; } assert.equal( @@ -232,25 +232,23 @@ module('Acceptance | task group detail', function (hooks) { window.localStorage.nomadTokenSecret = clientToken.secretId; await TaskGroup.visit({ - id: job.id, + id: `${job.id}@${SCALE_AND_WRITE_NAMESPACE}`, name: scalingGroup.name, - namespace: SCALE_AND_WRITE_NAMESPACE, }); assert.equal( - currentURL(), - `/jobs/${job.id}/scaling?namespace=${SCALE_AND_WRITE_NAMESPACE}` + decodeURIComponent(currentURL()), + `/jobs/${job.id}@${SCALE_AND_WRITE_NAMESPACE}/scaling` ); assert.notOk(TaskGroup.countStepper.increment.isDisabled); await TaskGroup.visit({ - id: job2.id, + id: `${job2.id}@${secondNamespace.name}`, name: scalingGroup2.name, - namespace: secondNamespace.name, }); assert.equal( - currentURL(), - `/jobs/${job2.id}/scaling?namespace=${READ_ONLY_NAMESPACE}` + decodeURIComponent(currentURL()), + `/jobs/${job2.id}@${READ_ONLY_NAMESPACE}/scaling` ); assert.ok(TaskGroup.countStepper.increment.isDisabled); }); diff --git a/ui/tests/acceptance/volume-detail-test.js b/ui/tests/acceptance/volume-detail-test.js index fb264110acf..fdb6f291c4b 100644 --- a/ui/tests/acceptance/volume-detail-test.js +++ b/ui/tests/acceptance/volume-detail-test.js @@ -34,12 +34,12 @@ module('Acceptance | volume detail', function (hooks) { }); test('it passes an accessibility audit', async function (assert) { - await VolumeDetail.visit({ id: volume.id }); + await VolumeDetail.visit({ id: `${volume.id}@default` }); await a11yAudit(assert); }); test('/csi/volumes/:id should have a breadcrumb trail linking back to Volumes and Storage', async function (assert) { - await VolumeDetail.visit({ id: volume.id }); + await VolumeDetail.visit({ id: `${volume.id}@default` }); assert.equal(Layout.breadcrumbFor('csi.index').text, 'Storage'); assert.equal(Layout.breadcrumbFor('csi.volumes').text, 'Volumes'); @@ -47,14 +47,14 @@ module('Acceptance | volume detail', function (hooks) { }); test('/csi/volumes/:id should show the volume name in the title', async function (assert) { - await VolumeDetail.visit({ id: volume.id }); + await VolumeDetail.visit({ id: `${volume.id}@default` }); assert.equal(document.title, `CSI Volume ${volume.name} - Nomad`); assert.equal(VolumeDetail.title, volume.name); }); test('/csi/volumes/:id should list additional details for the volume below the title', async function (assert) { - await VolumeDetail.visit({ id: volume.id }); + await VolumeDetail.visit({ id: `${volume.id}@default` }); assert.ok( VolumeDetail.health.includes( @@ -75,7 +75,7 @@ module('Acceptance | volume detail', function (hooks) { writeAllocations.forEach((alloc) => assignWriteAlloc(volume, alloc)); readAllocations.forEach((alloc) => assignReadAlloc(volume, alloc)); - await VolumeDetail.visit({ id: volume.id }); + await VolumeDetail.visit({ id: `${volume.id}@default` }); assert.equal(VolumeDetail.writeAllocations.length, writeAllocations.length); writeAllocations @@ -95,7 +95,7 @@ module('Acceptance | volume detail', function (hooks) { writeAllocations.forEach((alloc) => assignWriteAlloc(volume, alloc)); readAllocations.forEach((alloc) => assignReadAlloc(volume, alloc)); - await VolumeDetail.visit({ id: volume.id }); + await VolumeDetail.visit({ id: `${volume.id}@default` }); assert.equal(VolumeDetail.readAllocations.length, readAllocations.length); readAllocations @@ -126,7 +126,7 @@ module('Acceptance | volume detail', function (hooks) { 0 ); - await VolumeDetail.visit({ id: volume.id }); + await VolumeDetail.visit({ id: `${volume.id}@default` }); VolumeDetail.writeAllocations.objectAt(0).as((allocationRow) => { assert.equal( @@ -198,28 +198,28 @@ module('Acceptance | volume detail', function (hooks) { const allocation = server.create('allocation'); assignWriteAlloc(volume, allocation); - await VolumeDetail.visit({ id: volume.id }); + await VolumeDetail.visit({ id: `${volume.id}@default` }); await VolumeDetail.writeAllocations.objectAt(0).visit(); assert.equal(currentURL(), `/allocations/${allocation.id}`); }); test('when there are no write allocations, the table presents an empty state', async function (assert) { - await VolumeDetail.visit({ id: volume.id }); + await VolumeDetail.visit({ id: `${volume.id}@default` }); assert.ok(VolumeDetail.writeTableIsEmpty); assert.equal(VolumeDetail.writeEmptyState.headline, 'No Write Allocations'); }); test('when there are no read allocations, the table presents an empty state', async function (assert) { - await VolumeDetail.visit({ id: volume.id }); + await VolumeDetail.visit({ id: `${volume.id}@default` }); assert.ok(VolumeDetail.readTableIsEmpty); assert.equal(VolumeDetail.readEmptyState.headline, 'No Read Allocations'); }); test('the constraints table shows access mode and attachment mode', async function (assert) { - await VolumeDetail.visit({ id: volume.id }); + await VolumeDetail.visit({ id: `${volume.id}@default` }); assert.equal(VolumeDetail.constraints.accessMode, volume.accessMode); assert.equal( @@ -244,7 +244,7 @@ module('Acceptance | volume detail (with namespaces)', function (hooks) { }); test('/csi/volumes/:id detail ribbon includes the namespace of the volume', async function (assert) { - await VolumeDetail.visit({ id: volume.id, namespace: volume.namespaceId }); + await VolumeDetail.visit({ id: `${volume.id}@${volume.namespaceId}` }); assert.ok(VolumeDetail.hasNamespace); assert.ok(VolumeDetail.namespace.includes(volume.namespaceId || 'default')); diff --git a/ui/tests/acceptance/volumes-list-test.js b/ui/tests/acceptance/volumes-list-test.js index 0876aa36a1b..c7a3fee9b21 100644 --- a/ui/tests/acceptance/volumes-list-test.js +++ b/ui/tests/acceptance/volumes-list-test.js @@ -79,7 +79,7 @@ module('Acceptance | volumes list', function (hooks) { const isHealthy = healthy > 0; controllerHealthStr = `${ isHealthy ? 'Healthy' : 'Unhealthy' - } (${healthy}/${expected})`; + } ( ${healthy} / ${expected} )`; } const nodeHealthStr = volume.nodesHealthy > 0 ? 'Healthy' : 'Unhealthy'; @@ -93,7 +93,7 @@ module('Acceptance | volumes list', function (hooks) { assert.equal(volumeRow.controllerHealth, controllerHealthStr); assert.equal( volumeRow.nodeHealth, - `${nodeHealthStr} (${volume.nodesHealthy}/${volume.nodesExpected})` + `${nodeHealthStr} ( ${volume.nodesHealthy} / ${volume.nodesExpected} )` ); assert.equal(volumeRow.provider, volume.provider); assert.equal(volumeRow.allocations, readAllocs.length + writeAllocs.length); @@ -110,7 +110,7 @@ module('Acceptance | volumes list', function (hooks) { await VolumesList.volumes.objectAt(0).clickName(); assert.equal( currentURL(), - `/csi/volumes/${volume.id}?namespace=${secondNamespace.id}` + `/csi/volumes/${volume.id}@${secondNamespace.id}` ); await VolumesList.visit({ namespace: '*' }); @@ -119,7 +119,7 @@ module('Acceptance | volumes list', function (hooks) { await VolumesList.volumes.objectAt(0).clickRow(); assert.equal( currentURL(), - `/csi/volumes/${volume.id}?namespace=${secondNamespace.id}` + `/csi/volumes/${volume.id}@${secondNamespace.id}` ); }); diff --git a/ui/tests/helpers/module-for-job.js b/ui/tests/helpers/module-for-job.js index 53d373ae812..327481acc95 100644 --- a/ui/tests/helpers/module-for-job.js +++ b/ui/tests/helpers/module-for-job.js @@ -41,7 +41,7 @@ export default function moduleForJob( if (!job.namespace || job.namespace === 'default') { await JobDetail.visit({ id: job.id }); } else { - await JobDetail.visit({ id: job.id, namespace: job.namespace }); + await JobDetail.visit({ id: `${job.id}@${job.namespace}` }); } const hasClientStatus = ['system', 'sysbatch'].includes(job.type); @@ -51,52 +51,52 @@ export default function moduleForJob( }); test('visiting /jobs/:job_id', async function (assert) { - assert.equal( - currentURL(), - urlWithNamespace(`/jobs/${encodeURIComponent(job.id)}`, job.namespace) - ); + const expectedURL = job.namespace + ? `/jobs/${job.name}@${job.namespace}` + : `/jobs/${job.name}`; + + assert.equal(decodeURIComponent(currentURL()), expectedURL); assert.equal(document.title, `Job ${job.name} - Nomad`); }); test('the subnav links to overview', async function (assert) { await JobDetail.tabFor('overview').visit(); - assert.equal( - currentURL(), - urlWithNamespace(`/jobs/${encodeURIComponent(job.id)}`, job.namespace) - ); + + const expectedURL = job.namespace + ? `/jobs/${job.name}@${job.namespace}` + : `/jobs/${job.name}`; + + assert.equal(decodeURIComponent(currentURL()), expectedURL); }); test('the subnav links to definition', async function (assert) { await JobDetail.tabFor('definition').visit(); - assert.equal( - currentURL(), - urlWithNamespace( - `/jobs/${encodeURIComponent(job.id)}/definition`, - job.namespace - ) - ); + + const expectedURL = job.namespace + ? `/jobs/${job.name}@${job.namespace}/definition` + : `/jobs/${job.name}/definition`; + + assert.equal(decodeURIComponent(currentURL()), expectedURL); }); test('the subnav links to versions', async function (assert) { await JobDetail.tabFor('versions').visit(); - assert.equal( - currentURL(), - urlWithNamespace( - `/jobs/${encodeURIComponent(job.id)}/versions`, - job.namespace - ) - ); + + const expectedURL = job.namespace + ? `/jobs/${job.name}@${job.namespace}/versions` + : `/jobs/${job.name}/versions`; + + assert.equal(decodeURIComponent(currentURL()), expectedURL); }); test('the subnav links to evaluations', async function (assert) { await JobDetail.tabFor('evaluations').visit(); - assert.equal( - currentURL(), - urlWithNamespace( - `/jobs/${encodeURIComponent(job.id)}/evaluations`, - job.namespace - ) - ); + + const expectedURL = job.namespace + ? `/jobs/${job.name}@${job.namespace}/evaluations` + : `/jobs/${job.name}/evaluations`; + + assert.equal(decodeURIComponent(currentURL()), expectedURL); }); test('the title buttons are dependent on job status', async function (assert) { @@ -145,7 +145,7 @@ export default function moduleForJob( const encodedStatus = encodeURIComponent(JSON.stringify([status])); const expectedURL = new URL( urlWithNamespace( - `/jobs/${job.name}/clients?status=${encodedStatus}`, + `/jobs/${job.name}@default/clients?status=${encodedStatus}`, job.namespace ), window.location @@ -248,13 +248,12 @@ export function moduleForJobWithClientStatus( test('the subnav links to clients', async function (assert) { await JobDetail.tabFor('clients').visit(); - assert.equal( - currentURL(), - urlWithNamespace( - `/jobs/${encodeURIComponent(job.id)}/clients`, - job.namespace - ) - ); + + const expectedURL = job.namespace + ? `/jobs/${job.id}@${job.namespace}/clients` + : `/jobs/${job.id}/clients`; + + assert.equal(currentURL(), expectedURL); }); test('job status summary is shown in the overview', async function (assert) { @@ -289,23 +288,12 @@ export function moduleForJobWithClientStatus( await slice.click(); const encodedStatus = encodeURIComponent(JSON.stringify([status])); - const expectedURL = new URL( - urlWithNamespace( - `/jobs/${job.name}/clients?status=${encodedStatus}`, - job.namespace - ), - window.location - ); - const gotURL = new URL(currentURL(), window.location); - assert.deepEqual(gotURL.pathname, expectedURL.pathname); - // Sort and compare URL query params. - gotURL.searchParams.sort(); - expectedURL.searchParams.sort(); - assert.equal( - gotURL.searchParams.toString(), - expectedURL.searchParams.toString() - ); + const expectedURL = job.namespace + ? `/jobs/${job.name}@${job.namespace}/clients?status=${encodedStatus}` + : `/jobs/${job.name}/clients?status=${encodedStatus}`; + + assert.deepEqual(currentURL(), expectedURL, 'url is correct'); }); for (var testName in additionalTests) { @@ -368,6 +356,6 @@ async function visitJobDetailPage({ id, namespace }) { if (!namespace || namespace === 'default') { await JobDetail.visit({ id }); } else { - await JobDetail.visit({ id, namespace }); + await JobDetail.visit({ id: `${id}@${namespace}` }); } }