-
- {{#if (eq @model.status.state 1)}}
-
-
- Build running...
-
- {{else if (eq @model.status.state 2)}}
-
-
- Built in {{date-format-distance @model.status.startTime.seconds @model.status.completeTime.seconds }}
-
- {{else if (eq @model.status.state 3)}}
-
-
-
- Build failed
- {{#if @model.status.error.message}}
- : {{@model.status.error.message}}
- {{/if}}
-
-
- {{/if}}
+
-
+{{/let}}
diff --git a/ui/mirage/config.ts b/ui/mirage/config.ts
index 85a6a531da6..1239f82f50f 100644
--- a/ui/mirage/config.ts
+++ b/ui/mirage/config.ts
@@ -13,6 +13,7 @@ import * as versionInfo from './services/version-info';
import * as statusReport from './services/status-report';
import * as job from './services/job';
import * as log from './services/log';
+import * as pushedArtifact from './services/pushed-artifact';
export default function (this: Server) {
this.namespace = 'hashicorp.waypoint.Waypoint';
@@ -48,6 +49,7 @@ export default function (this: Server) {
this.post('/GetLatestStatusReport', statusReport.getLatest);
this.post('/GetJobStream', job.stream);
this.post('/GetLogStream', log.stream);
+ this.post('/ListPushedArtifacts', pushedArtifact.list);
if (!Ember.testing) {
// Pass through all other requests
diff --git a/ui/mirage/factories/build.ts b/ui/mirage/factories/build.ts
index eb950c30abc..07218ea90d8 100644
--- a/ui/mirage/factories/build.ts
+++ b/ui/mirage/factories/build.ts
@@ -11,6 +11,9 @@ export default Factory.extend({
server.schema.workspaces.findBy({ name: 'default' }) || server.create('workspace', 'default');
build.update('workspace', workspace);
}
+
+ build.pushedArtifact?.update('application', build.application);
+ build.pushedArtifact?.update('workspace', build.workspace);
},
random: trait({
@@ -20,6 +23,7 @@ export default Factory.extend({
}),
component: association('builder', 'with-random-name'),
status: association('random'),
+ pushedArtifact: association('random'),
}),
docker: trait({
diff --git a/ui/mirage/factories/component.ts b/ui/mirage/factories/component.ts
index 836012ab821..65983f7396b 100644
--- a/ui/mirage/factories/component.ts
+++ b/ui/mirage/factories/component.ts
@@ -48,6 +48,10 @@ export default Factory.extend({
name: 'kubernetes-apply',
}),
+ 'aws-ecr': trait({
+ name: 'aws-ecr',
+ }),
+
'with-random-name': trait({
afterCreate(component) {
component.update('name', randomNameForType(component.type));
diff --git a/ui/mirage/factories/project.ts b/ui/mirage/factories/project.ts
index a9c856c8009..02095bc7cf3 100644
--- a/ui/mirage/factories/project.ts
+++ b/ui/mirage/factories/project.ts
@@ -93,30 +93,37 @@ export default Factory.extend({
server.create('build', 'docker', 'days-old-success', {
application,
sequence: 1,
+ pushedArtifact: server.create('pushed-artifact', 'docker', 'days-old-success'),
}),
server.create('build', 'docker', 'days-old-success', {
application,
sequence: 2,
+ pushedArtifact: server.create('pushed-artifact', 'docker', 'days-old-success'),
}),
server.create('build', 'docker', 'hours-old-success', {
application,
sequence: 3,
+ pushedArtifact: server.create('pushed-artifact', 'docker', 'hours-old-success'),
}),
server.create('build', 'docker', 'hours-old-success', {
application,
sequence: 4,
+ pushedArtifact: server.create('pushed-artifact', 'docker', 'hours-old-success'),
}),
server.create('build', 'docker', 'minutes-old-success', {
application,
sequence: 5,
+ pushedArtifact: server.create('pushed-artifact', 'docker', 'minutes-old-success'),
}),
server.create('build', 'docker', 'minutes-old-success', {
application,
sequence: 6,
+ pushedArtifact: server.create('pushed-artifact', 'docker', 'minutes-old-success'),
}),
server.create('build', 'docker', 'seconds-old-success', {
application,
sequence: 7,
+ pushedArtifact: server.create('pushed-artifact', 'docker', 'seconds-old-success'),
}),
];
diff --git a/ui/mirage/factories/pushed-artifact.ts b/ui/mirage/factories/pushed-artifact.ts
new file mode 100644
index 00000000000..a8a2418e4f1
--- /dev/null
+++ b/ui/mirage/factories/pushed-artifact.ts
@@ -0,0 +1,48 @@
+import { Factory, association, trait } from 'ember-cli-mirage';
+import { fakeId } from '../utils';
+
+export default Factory.extend({
+ id: () => fakeId(),
+ sequence: (i) => i + 1,
+
+ afterCreate(pushedArtifact, server) {
+ if (!pushedArtifact.workspace) {
+ let workspace =
+ server.schema.workspaces.findBy({ name: 'default' }) || server.create('workspace', 'default');
+ pushedArtifact.update('workspace', workspace);
+ }
+ },
+
+ random: trait({
+ component: association('registry', 'with-random-name'),
+ status: association('random'),
+ }),
+
+ docker: trait({
+ component: association('registry', 'docker'),
+ }),
+
+ 'aws-ecr': trait({
+ component: association('registry', 'aws-ecr'),
+ }),
+
+ 'seconds-old-success': trait({
+ status: association('random', 'success', 'seconds-old'),
+ }),
+
+ 'seconds-old-error': trait({
+ status: association('random', 'error', 'seconds-old'),
+ }),
+
+ 'minutes-old-success': trait({
+ status: association('random', 'success', 'minutes-old'),
+ }),
+
+ 'hours-old-success': trait({
+ status: association('random', 'success', 'hours-old'),
+ }),
+
+ 'days-old-success': trait({
+ status: association('random', 'success', 'days-old'),
+ }),
+});
diff --git a/ui/mirage/factories/status.ts b/ui/mirage/factories/status.ts
index 54c4af6519e..c085a361692 100644
--- a/ui/mirage/factories/status.ts
+++ b/ui/mirage/factories/status.ts
@@ -20,6 +20,10 @@ export default Factory.extend({
state: 'SUCCESS',
}),
+ error: trait({
+ state: 'ERROR',
+ }),
+
'seconds-old': trait({
completeTime: () => new Date(),
}),
diff --git a/ui/mirage/models/build.ts b/ui/mirage/models/build.ts
index fc10da60172..24422d9d76c 100644
--- a/ui/mirage/models/build.ts
+++ b/ui/mirage/models/build.ts
@@ -7,6 +7,7 @@ export default Model.extend({
component: belongsTo({ inverse: 'owner' }),
status: belongsTo({ inverse: 'owner' }),
deployments: hasMany(),
+ pushedArtifact: belongsTo({ inverse: 'build' }),
toProtobuf(): Build {
let result = new Build();
diff --git a/ui/mirage/models/pushed-artifact.ts b/ui/mirage/models/pushed-artifact.ts
new file mode 100644
index 00000000000..6c36c9bfa35
--- /dev/null
+++ b/ui/mirage/models/pushed-artifact.ts
@@ -0,0 +1,28 @@
+import { Model, belongsTo } from 'miragejs';
+import { PushedArtifact } from 'waypoint-pb';
+
+export default Model.extend({
+ application: belongsTo(),
+ build: belongsTo({ inverse: 'pushedArtifact' }),
+ component: belongsTo({ inverse: 'owner' }),
+ status: belongsTo({ inverse: 'owner' }),
+ workspace: belongsTo(),
+
+ toProtobuf(): PushedArtifact {
+ let result = new PushedArtifact();
+
+ result.setApplication(this.application?.toProtobufRef());
+ // TODO: result.setArtifact
+ result.setBuild(this.build?.toProtobuf());
+ result.setBuildId(this.build?.id);
+ result.setComponent(this.component?.toProtobuf());
+ result.setId(this.id);
+ result.setJobId(this.jobId);
+ result.setSequence(this.sequence);
+ result.setStatus(this.status?.toProtobuf());
+ result.setTemplateData(this.templateData);
+ result.setWorkspace(this.workspace?.toProtobufRef());
+
+ return result;
+ },
+});
diff --git a/ui/mirage/services/pushed-artifact.ts b/ui/mirage/services/pushed-artifact.ts
new file mode 100644
index 00000000000..88e16377525
--- /dev/null
+++ b/ui/mirage/services/pushed-artifact.ts
@@ -0,0 +1,25 @@
+import { Request, Response } from 'miragejs';
+import { ListPushedArtifactsRequest, ListPushedArtifactsResponse } from 'waypoint-pb';
+import { decode } from '../helpers/protobufs';
+
+export function list(schema: any, request: Request): Response {
+ let requestMsg = decode(ListPushedArtifactsRequest, request.requestBody);
+ let projectName = requestMsg.getApplication().getProject();
+ let appName = requestMsg.getApplication().getApplication();
+ let workspaceName = requestMsg.getWorkspace().getWorkspace();
+ let project = schema.projects.findBy({ name: projectName });
+ let application = schema.applications.findBy({ name: appName, projectId: project.id });
+ let workspace = schema.workspaces.findBy({ name: workspaceName });
+ let pushedArtifacts = schema.pushedArtifacts.where({
+ applicationId: application?.id,
+ workspaceId: workspace?.id,
+ });
+ let pushedArtifactProtobufs = pushedArtifacts.models.map((b) => b.toProtobuf());
+ let resp = new ListPushedArtifactsResponse();
+
+ pushedArtifactProtobufs.sort((a, b) => b.getSequence() - a.getSequence());
+
+ resp.setArtifactsList(pushedArtifactProtobufs);
+
+ return this.serialize(resp, 'application');
+}
diff --git a/ui/tests/integration/components/app-item/build-test.ts b/ui/tests/integration/components/app-item/build-test.ts
new file mode 100644
index 00000000000..102b03c1acf
--- /dev/null
+++ b/ui/tests/integration/components/app-item/build-test.ts
@@ -0,0 +1,91 @@
+import { module, test } from 'qunit';
+import { setupRenderingTest } from 'ember-qunit';
+import { render } from '@ember/test-helpers';
+import hbs from 'htmlbars-inline-precompile';
+import { getUnixTime, subMinutes } from 'date-fns';
+import { a11yAudit } from 'ember-a11y-testing/test-support';
+import { Timestamp } from 'google-protobuf/google/protobuf/timestamp_pb';
+
+module('Integration | Component | app-item/build', function (hooks) {
+ setupRenderingTest(hooks);
+
+ test('with a build and a push', async function (assert) {
+ this.set('build', {
+ sequence: 3,
+ status: {
+ state: 2,
+ startTime: minutesAgo(3),
+ completeTime: minutesAgo(2),
+ },
+ component: {
+ type: 1,
+ name: 'docker',
+ },
+ pushedArtifact: {
+ component: {
+ type: 2,
+ name: 'docker',
+ },
+ status: {
+ state: 2,
+ startTime: minutesAgo(2),
+ completeTime: minutesAgo(1),
+ },
+ },
+ });
+
+ await render(hbs`
+
+ `);
+ await a11yAudit();
+
+ assert.dom('[data-test-app-item-build]').includesText('v3');
+ assert.dom('[data-test-icon-type="logo-docker-color"]').exists();
+ assert.dom('[data-test-app-item-build]').includesText('Pushed to Docker');
+ assert.dom('[data-test-operation-status-indicator="success"]').exists();
+ assert.dom('[data-test-app-item-build]').includesText('1 minute ago');
+ assert.dom('[data-test-app-item-build]').includesText('Built in 1 minute');
+ });
+
+ test('with no push', async function (assert) {
+ this.set('build', {
+ sequence: 3,
+ status: {
+ state: 2,
+ startTime: minutesAgo(3),
+ completeTime: minutesAgo(2),
+ },
+ component: {
+ type: 1,
+ name: 'docker',
+ },
+ pushedArtifact: null,
+ });
+
+ await render(hbs`
+
+ `);
+ await a11yAudit();
+
+ assert.dom('[data-test-app-item-build]').includesText('v3');
+ assert.dom('[data-test-icon-type="logo-docker-color"]').exists();
+ assert.dom('[data-test-app-item-build]').includesText('Built with Docker');
+ assert.dom('[data-test-operation-status-indicator="success"]').exists();
+ assert.dom('[data-test-app-item-build]').includesText('2 minutes ago');
+ });
+});
+
+function minutesAgo(n: number): Timestamp.AsObject {
+ let now = new Date();
+ let date = subMinutes(now, n);
+ let result = {
+ seconds: getUnixTime(date),
+ nanos: 0,
+ };
+
+ return result;
+}
diff --git a/ui/translations/en-us.yaml b/ui/translations/en-us.yaml
index d7ea44f6e3d..5d3c6ed1785 100644
--- a/ui/translations/en-us.yaml
+++ b/ui/translations/en-us.yaml
@@ -148,3 +148,18 @@ status_report_indicator:
checking_now: Checking now…
last_checked: Last checked
unknown: Status unknown
+
+app_item_build:
+ built_in: Built in {duration}
+
+build_status:
+ type-1: # BUILDER
+ state-0: Building with # UNKNOWN
+ state-1: Building with # RUNNING
+ state-2: Built with # SUCCESS
+ state-3: Failed to build with # ERROR
+ type-2: # REGISTRY
+ state-0: Pushing to # UNKNOWN
+ state-1: Pushing to # RUNNING
+ state-2: Pushed to # SUCCESS
+ state-3: Failed to push to # ERROR