diff --git a/.changelog/23328.txt b/.changelog/23328.txt new file mode 100644 index 00000000000..3e0f8928010 --- /dev/null +++ b/.changelog/23328.txt @@ -0,0 +1,3 @@ +```release-note:improvement +ui: adds a Stopped label for jobs that a user has manually stopped +``` diff --git a/nomad/job_endpoint_statuses.go b/nomad/job_endpoint_statuses.go index c62fb37c193..7b54c2b831f 100644 --- a/nomad/job_endpoint_statuses.go +++ b/nomad/job_endpoint_statuses.go @@ -219,7 +219,8 @@ func jobStatusesJobFromJob(ws memdb.WatchSet, store *state.StateStore, job *stru GroupCountSum: 0, ChildStatuses: nil, LatestDeployment: nil, - Stop: job.Stop, + Stop: job.Stop, + Status: job.Status, } // the GroupCountSum will map to how many allocations we expect to run diff --git a/nomad/structs/job.go b/nomad/structs/job.go index 547e913eae6..93ca19a9f75 100644 --- a/nomad/structs/job.go +++ b/nomad/structs/job.go @@ -97,7 +97,8 @@ type JobStatusesJob struct { // ParentID is set on child (batch) jobs, specifying the parent job ID ParentID string LatestDeployment *JobStatusesLatestDeployment - Stop bool // has the job been manually stopped? + Stop bool // has the job been manually stopped? + Status string } // JobStatusesAlloc contains a subset of Allocation info. diff --git a/ui/app/models/job.js b/ui/app/models/job.js index 71285f64be2..94ff651d286 100644 --- a/ui/app/models/job.js +++ b/ui/app/models/job.js @@ -241,7 +241,7 @@ export default class Job extends Model { } // if manually stopped by a user: - if (this.stopped) { + if (this.status === 'dead' && this.stopped) { return { label: 'Stopped', state: 'neutral', diff --git a/ui/mirage/config.js b/ui/mirage/config.js index 4e1dd0c8a67..10650cde78c 100644 --- a/ui/mirage/config.js +++ b/ui/mirage/config.js @@ -307,7 +307,7 @@ export default function () { }); job.ChildStatuses = children ? children.mapBy('Status') : null; job.Datacenters = j.Datacenters; - job.DeploymentID = j.DeploymentID; + job.LatestDeployment = j.LatestDeployment; job.GroupCountSum = j.TaskGroups.mapBy('Count').reduce( (a, b) => a + b, 0 diff --git a/ui/mirage/factories/job.js b/ui/mirage/factories/job.js index ebe84f36323..89bc6903de4 100644 --- a/ui/mirage/factories/job.js +++ b/ui/mirage/factories/job.js @@ -205,6 +205,8 @@ export default Factory.extend({ // When true, the job will simulate a "scheduled" block's paused state withPausedTasks: false, + latestDeployment: null, + afterCreate(job, server) { Ember.assert( '[Mirage] No node pools! make sure node pools are created before jobs', @@ -319,6 +321,18 @@ export default Factory.extend({ }); } + if (job.activeDeployment) { + job.latestDeployment = { + IsActive: true, + Status: 'running', + StatusDescription: 'Deployment is running', + RequiresPromotion: false, + AllAutoPromote: true, + JobVersion: 1, + ID: faker.random.uuid(), + }; + } + if (!job.shallow) { const knownEvaluationProperties = { jobId: job.id, diff --git a/ui/tests/acceptance/jobs-list-test.js b/ui/tests/acceptance/jobs-list-test.js index 3f978cae98d..e27f24f7f65 100644 --- a/ui/tests/acceptance/jobs-list-test.js +++ b/ui/tests/acceptance/jobs-list-test.js @@ -554,6 +554,138 @@ module('Acceptance | jobs list', function (hooks) { localStorage.removeItem('nomadPageSize'); }); + test('aggregateAllocStatus reflects job status correctly', async function (assert) { + const defaultJobParams = { + createAllocations: true, + shallow: true, + resourceSpec: Array(1).fill('M: 257, C: 500'), + groupAllocCount: 10, + noActiveDeployment: true, + noFailedPlacements: true, + status: 'running', + type: 'service', + }; + + server.create('job', { + ...defaultJobParams, + id: 'healthy-job', + allocStatusDistribution: { + running: 1, + }, + }); + + server.create('job', { + ...defaultJobParams, + id: 'degraded-job', + allocStatusDistribution: { + running: 0.9, + failed: 0.1, + }, + }); + + server.create('job', { + ...defaultJobParams, + id: 'recovering-job', + allocStatusDistribution: { + running: 0.9, + pending: 0.1, + }, + }); + + server.create('job', { + ...defaultJobParams, + id: 'completed-job', + allocStatusDistribution: { + complete: 1, + }, + type: 'batch', + }); + + server.create('job', { + ...defaultJobParams, + id: 'running-job', + allocStatusDistribution: { + running: 1, + }, + type: 'batch', + }); + + server.create('job', { + ...defaultJobParams, + id: 'failed-job', + allocStatusDistribution: { + failed: 1, + }, + }); + + server.create('job', { + ...defaultJobParams, + id: 'failed-garbage-collected-job', + type: 'service', + allocStatusDistribution: { + unknown: 1, + }, + status: 'running', + }); + + server.create('job', { + ...defaultJobParams, + id: 'stopped-job', + type: 'service', + allocStatusDistribution: { + unknown: 1, + }, + status: 'dead', + stopped: true, + }); + + server.create('job', { + ...defaultJobParams, + id: 'deploying-job', + allocStatusDistribution: { + running: 0.5, + pending: 0.5, + }, + noActiveDeployment: false, + activeDeployment: true, + }); + + await JobsList.visit(); + + assert + .dom('[data-test-job-row="healthy-job"] [data-test-job-status]') + .hasText('Healthy', 'Healthy job is healthy'); + // and all the rest + assert + .dom('[data-test-job-row="degraded-job"] [data-test-job-status]') + .hasText('Degraded', 'Degraded job is degraded'); + assert + .dom('[data-test-job-row="recovering-job"] [data-test-job-status]') + .hasText('Recovering', 'Recovering job is recovering'); + assert + .dom('[data-test-job-row="completed-job"] [data-test-job-status]') + .hasText('Complete', 'Completed job is completed'); + assert + .dom('[data-test-job-row="running-job"] [data-test-job-status]') + .hasText('Running', 'Running job is running'); + assert + .dom('[data-test-job-row="failed-job"] [data-test-job-status]') + .hasText('Failed', 'Failed job is failed'); + assert + .dom( + '[data-test-job-row="failed-garbage-collected-job"] [data-test-job-status]' + ) + .hasText('Failed', 'Failed garbage collected job is failed'); + assert + .dom('[data-test-job-row="stopped-job"] [data-test-job-status]') + .hasText('Stopped', 'Stopped job is stopped'); + assert + .dom('[data-test-job-row="deploying-job"] [data-test-job-status]') + .hasText('Deploying', 'Deploying job is deploying'); + + await percySnapshot(assert); + }); + test('Jobs with schedule blocks indicate when a task is paused', async function (assert) { server.create('job', { name: 'regular-job-1',