Unverified Commit 38a37599 authored by Jai's avatar Jai Committed by GitHub
Browse files

Merge pull request #11590 from hashicorp/e-ui/breadcrumbs-service

Refactor:  Breadcrumbs Service
parents 0ff0fa1f 3bdf6613
Showing with 365 additions and 17 deletions
+365 -17
```release-note:improvement
ui: Add titles to breadcrumb labels in app navigation bar
```
import Component from '@ember/component';
import { inject as service } from '@ember/service';
import { reads } from '@ember/object/computed';
import { tagName } from '@ember-decorators/component';
import classic from 'ember-classic-decorator';
@classic
@tagName('')
export default class AppBreadcrumbs extends Component {
@service('breadcrumbs') breadcrumbsService;
@reads('breadcrumbsService.breadcrumbs') breadcrumbs;
}
import { assert } from '@ember/debug';
import { action } from '@ember/object';
import { inject as service } from '@ember/service';
import Component from '@glimmer/component';
export default class Breadcrumb extends Component {
@service breadcrumbs;
constructor() {
super(...arguments);
assert('Provide a valid breadcrumb argument', this.args.crumb);
this.register();
}
@action register() {
this.breadcrumbs.registerBreadcrumb(this);
}
@action deregister() {
this.breadcrumbs.deregisterBreadcrumb(this);
}
willDestroy() {
super.willDestroy();
this.deregister();
}
}
{{yield this.crumbs}}
\ No newline at end of file
import Component from '@glimmer/component';
import { inject as service } from '@ember/service';
export default class Breadcrumbs extends Component {
@service breadcrumbs;
get crumbs() {
return this.breadcrumbs.crumbs;
}
}
<li data-test-breadcrumb-default>
<LinkTo @params={{@crumb.args}} data-test-breadcrumb={{@crumb.args.firstObject}}>
{{#if @crumb.title}}
<dl>
<dt>
{{@crumb.title}}
</dt>
<dd>
{{@crumb.label}}
</dd>
</dl>
{{else}}
{{@crumb.label}}
{{/if}}
</LinkTo>
</li>
\ No newline at end of file
<Trigger @onError={{action this.onError}} @do={{this.fetchParent}} as |trigger|>
{{did-insert trigger.fns.do}}
{{#if trigger.data.isBusy}}
<li>
<a href="#" aria-label="loading" data-test-breadcrumb="loading">
</a>
</li>
{{/if}}
{{#if trigger.data.isSuccess}}
{{#if trigger.data.result}}
<li>
<LinkTo
@route="jobs.job.index"
@model={{trigger.data.result.plainId}}
@query={{hash namespace=(or trigger.data.result.namespace.name "default")}}
data-test-breadcrumb={{"jobs.job.index"}}
>
<dl>
<dt>
Parent Job
</dt>
<dd>
{{trigger.data.result.trimmedName}}
</dd>
</dl>
</LinkTo>
</li>
{{/if}}
<li>
<LinkTo
@route="jobs.job.index"
@model={{this.job.plainId}}
@query={{hash namespace=(or this.job.namespace.name "default")}}
data-test-breadcrumb={{"jobs.job.index"}}
data-test-job-breadcrumb
>
<dl>
<dt>
{{if this.job.hasChildren "Parent Job" "Job"}}
</dt>
<dd>
{{this.job.trimmedName}}
</dd>
</dl>
</LinkTo>
</li>
{{/if}}
</Trigger>
\ No newline at end of file
import { assert } from '@ember/debug';
import { action } from '@ember/object';
import Component from '@glimmer/component';
export default class BreadcrumbsJob extends Component {
get job() {
return this.args.crumb.job;
}
@action
onError(err) {
assert(`Error: ${err.message}`);
}
@action
fetchParent() {
const hasParent = !!this.job.belongsTo('parent').id();
if (hasParent) {
return this.job.get('parent');
}
}
}
{{yield (hash data=this.data fns=this.fns)}}
\ No newline at end of file
import { action } from '@ember/object';
import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';
import { task } from 'ember-concurrency';
const noOp = () => undefined;
export default class Trigger extends Component {
@tracked error = null;
@tracked result = null;
get isBusy() {
return this.triggerTask.isRunning;
}
get isIdle() {
return this.triggerTask.isIdle;
}
get isSuccess() {
return this.triggerTask.last?.isSuccessful;
}
get isError() {
return !!this.error;
}
get fns() {
return {
do: this.onTrigger,
};
}
get onError() {
return this.args.onError ?? noOp;
}
get onSuccess() {
return this.args.onSuccess ?? noOp;
}
get data() {
const { isBusy, isIdle, isSuccess, isError, result } = this;
return { isBusy, isIdle, isSuccess, isError, result };
}
_reset() {
this.result = null;
this.error = null;
}
@task(function*() {
this._reset();
try {
this.result = yield this.args.do();
this.onSuccess(this.result);
} catch (e) {
this.error = { Error: e };
this.onError(this.error);
}
})
triggerTask;
@action
onTrigger() {
this.triggerTask.perform();
}
}
import Controller from '@ember/controller';
import { inject as service } from '@ember/service';
import { qpBuilder } from 'nomad-ui/utils/classes/query-params';
export default class AllocationsAllocationController extends Controller {
@service store;
get allocation() {
return this.model;
}
get job() {
const allocation = this.model;
const jobId = allocation.belongsTo('job').id();
const job = this.store.peekRecord('job', jobId);
return job;
}
get jobNamespace() {
const jobNamespaceId = this.job.belongsTo('namespace').id();
return jobNamespaceId || 'default';
}
// Allocation breadcrumbs extend from job / task group breadcrumbs
// even though the route structure does not.
get breadcrumbs() {
const { allocation, job, jobNamespace } = this;
const jobQueryParams = qpBuilder({
jobNamespace,
});
return [
{ label: 'Jobs', args: ['jobs.index', jobQueryParams] },
{ type: 'job', job: job },
{
title: 'Task Group',
label: allocation.taskGroupName,
args: ['jobs.job.task-group', job.plainId, allocation.taskGroupName, jobQueryParams],
},
{
title: 'Allocation',
label: allocation.shortId,
args: ['allocations.allocation', allocation],
},
];
}
}
import Controller from '@ember/controller';
export default class AllocationsAllocationTaskController extends Controller {
get task() {
return this.model;
}
get breadcrumb() {
return {
title: 'Task',
label: this.task.get('name'),
args: ['allocations.allocation.task', this.task.get('allocation'), this.task],
};
}
}
import Controller from '@ember/controller';
export default class ClientsClientController extends Controller {
get client() {
return this.model;
}
get breadcrumb() {
return {
title: 'Client',
label: this.client.get('shortId'),
args: ['clients.client', this.client.get('id')],
};
}
}
import Controller from '@ember/controller';
export default class CsiPluginsPluginController extends Controller {
get plugin() {
return this.model;
}
get breadcrumbs() {
const { plainId } = this.plugin;
return [
{
label: 'Plugins',
args: ['csi.plugins'],
},
{
label: plainId,
args: ['csi.plugins.plugin', plainId],
},
];
}
}
import Controller from '@ember/controller';
import { inject as service } from '@ember/service';
import { action, computed } from '@ember/object';
import { qpBuilder } from 'nomad-ui/utils/classes/query-params';
export default class VolumeController extends Controller {
// Used in the template
......@@ -13,6 +14,31 @@ export default class VolumeController extends Controller {
];
volumeNamespace = 'default';
get volume() {
return this.model;
}
get breadcrumbs() {
const volume = this.volume;
return [
{
label: 'Volumes',
args: [
'csi.volumes',
qpBuilder({ volumeNamespace: volume.get('namespace.name') || 'default' }),
],
},
{
label: volume.name,
args: [
'csi.volumes.volume',
volume.plainId,
qpBuilder({ volumeNamespace: volume.get('namespace.name') || 'default' }),
],
},
];
}
@computed('model.readAllocations.@each.modifyIndex')
get sortedReadAllocations() {
return this.model.readAllocations.sortBy('modifyIndex').reverse();
......
import Controller from '@ember/controller';
// The WithNamespaceResetting Mixin uses Controller Injection and requires us to keep this controller around
export default class JobsController extends Controller {}
......@@ -7,4 +7,8 @@ export default class JobController extends Controller {
},
];
jobNamespace = 'default';
get job() {
return this.model;
}
}
import Controller from '@ember/controller';
// This may be safe to remove but we can't be sure, some route may try access this directly using this.controllerFor
export default class JobsJobDispatchController extends Controller {}
......@@ -5,6 +5,7 @@ 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';
......@@ -13,10 +14,10 @@ import classic from 'ember-classic-decorator';
@classic
export default class TaskGroupController extends Controller.extend(
Sortable,
Searchable,
WithNamespaceResetting
) {
Sortable,
Searchable,
WithNamespaceResetting
) {
@service userSettings;
@service can;
......@@ -141,4 +142,22 @@ export default class TaskGroupController extends Controller.extend(
setFacetQueryParam(queryParam, selection) {
this.set(queryParam, serialize(selection));
}
get taskGroup() {
return this.model;
}
get breadcrumb() {
const { job, name } = this.taskGroup;
return {
title: 'Task Group',
label: name,
args: [
'jobs.job.task-group',
job,
name,
qpBuilder({ jobNamespace: job.get('namespace.name') || 'default' }),
],
};
}
}
......@@ -9,4 +9,16 @@ export default class OptimizeSummaryController extends Controller {
jobNamespace: 'namespace',
},
];
get summary() {
return this.model;
}
get breadcrumb() {
const { slug } = this.summary;
return {
label: slug.replace('/', ' / '),
args: ['optimize.summary', slug],
};
}
}
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment