Skip to content

Commit ab7f1fe

Browse files
committed
ui: introduce vuex store
1 parent c430ab6 commit ab7f1fe

38 files changed

+347
-135
lines changed

.editorconfig

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ root = true
55
[Makefile]
66
indent_style = tab
77

8-
[*.js,*.ts]
8+
[*.js,*.ts,*.json]
99
indent_style = space
1010
indent_size = 2
1111

Makefile

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -68,16 +68,14 @@ ui-gen:
6868

6969
ui-client:
7070
rm -rf $(CURDIR)/ui/src/client
71-
docker container run --rm --user $(shell id -u):$(shell id -g) --volume $(CURDIR):/local openapitools/openapi-generator-cli \
71+
docker container run --rm --user $(shell id -u):$(shell id -g) --volume $(CURDIR):/local openapitools/openapi-generator-cli:v4.2.1 \
7272
generate \
7373
--input-spec /local/internal/api/gen/http/openapi.json \
7474
--generator-name typescript-fetch \
75-
--output /local/ui/src/client \
75+
--output /local/ui/src/openapi-generator/ \
7676
-p "generateAliasAsModel=true" \
7777
-p "typescriptThreePlus=true" \
7878
-p "withInterfaces=true"
79-
rm $(CURDIR)/ui/src/client/tsconfig.json
80-
8179

8280
cadence-flush:
8381
docker-compose exec mysql mysql -hlocalhost -uroot -proot123 -e "DROP DATABASE IF EXISTS cadence;"

ui/package.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,17 @@
99
"test:unit": "vue-cli-service test:unit"
1010
},
1111
"dependencies": {
12+
"@types/vue-moment": "^4.0.0",
1213
"bootstrap": "^4.3.1",
1314
"bootstrap-vue": "^2.0.3",
1415
"core-js": "^2.6.5",
1516
"vue": "^2.6.10",
1617
"vue-class-component": "^7.0.2",
18+
"vue-moment": "^4.0.0",
1719
"vue-property-decorator": "^8.1.0",
1820
"vue-router": "^3.0.3",
19-
"vuex": "^3.0.1"
21+
"vuex": "^3.0.1",
22+
"vuex-class": "^0.3.2"
2023
},
2124
"devDependencies": {
2225
"@types/jest": "^23.1.4",

ui/src/client.ts

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
import * as client from './client/src';
2-
1+
import * as api from './openapi-generator';
32

43
declare global {
54
interface Window {
@@ -20,9 +19,9 @@ function apiPath(): string {
2019
return path.replace(/\/$/, '');
2120
}
2221

23-
export let EnduroCollectionClient: client.CollectionApi;
22+
let EnduroCollectionClient: api.CollectionApi;
2423

25-
export function setUpEnduroClient() {
24+
function setUpEnduroClient() {
2625
let path = apiPath();
2726

2827
// path seems to be wrong when Enduro is deployed behind FortiNet SSLVPN.
@@ -32,8 +31,8 @@ export function setUpEnduroClient() {
3231
path = window.fgt_sslvpn.url_rewrite(path);
3332
}
3433

35-
const config: client.Configuration = new client.Configuration({basePath: path});
36-
EnduroCollectionClient = new client.CollectionApi(config);
34+
const config: api.Configuration = new api.Configuration({basePath: path});
35+
EnduroCollectionClient = new api.CollectionApi(config);
3736

3837
// tslint:disable-next-line:no-console
3938
console.log('Enduro client created', path);
@@ -45,3 +44,9 @@ window.enduro = {
4544
setUpEnduroClient();
4645
},
4746
};
47+
48+
export {
49+
EnduroCollectionClient,
50+
setUpEnduroClient,
51+
api,
52+
};

ui/src/client/.gitignore

Lines changed: 0 additions & 4 deletions
This file was deleted.

ui/src/client/.openapi-generator/VERSION

Lines changed: 0 additions & 1 deletion
This file was deleted.

ui/src/components/CollectionDetail.vue

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,14 @@
1515
<dd>
1616
<CollectionStatusBadge :status="collection.status"/>
1717
</dd>
18-
<template v-if="collection.original_id">
18+
<template v-if="collection.originalId">
1919
<dt>OriginalID</dt>
2020
<dd>{{ collection.originalId }}</dd>
2121
</template>
2222
<dt>Created</dt>
23-
<dd>{{ collection.createdAt | formatDateTime }}</dd>
23+
<dd>
24+
{{ collection.createdAt | formatDateTime }}
25+
</dd>
2426
<template v-if="collection.transferId">
2527
<dt>Transfer</dt>
2628
<dd>{{ collection.transferId }}</dd>
@@ -34,7 +36,10 @@
3436
<dt>AIP</dt>
3537
<dd>{{ collection.aipId }}</dd>
3638
<dt>Completed</dt>
37-
<dd>{{ collection.completedAt | formatDateTime }}</dd>
39+
<dd>
40+
{{ collection.completedAt | formatDateTime }}
41+
(took {{ took(collection.createdAt, collection.completedAt) }})
42+
</dd>
3843
</template>
3944
</dl>
4045
<hr />
@@ -76,32 +81,31 @@
7681
</template>
7782

7883
<script lang="ts">
79-
import { CollectionShowResponseBody } from '../client/src';
80-
import { EnduroCollectionClient } from '../client';
84+
import { api, EnduroCollectionClient } from '../client';
8185
import { Component, Inject, Prop, Provide, Vue } from 'vue-property-decorator';
8286
import CollectionStatusBadge from '@/components/CollectionStatusBadge.vue';
87+
import moment from 'moment';
8388
8489
@Component({
8590
components: {
8691
CollectionStatusBadge,
8792
},
8893
})
8994
export default class CollectionDetail extends Vue {
90-
9195
private interval: number = 0;
9296
private collection: any = {};
9397
94-
private created() {
98+
private mounted() {
9599
this.interval = setInterval(() => this.populate(), 500);
96-
return this.populate();
100+
this.populate();
97101
}
98102
99103
private beforeDestroy() {
100104
clearInterval(this.interval);
101105
}
102106
103107
private populate() {
104-
EnduroCollectionClient.collectionShow({id: +this.$route.params.id}).then((response: CollectionShowResponseBody) => {
108+
EnduroCollectionClient.collectionShow({id: +this.$route.params.id}).then((response: api.CollectionShowResponseBody) => {
105109
this.collection = response;
106110
});
107111
}
@@ -114,6 +118,11 @@ export default class CollectionDetail extends Vue {
114118
return ret;
115119
}
116120
121+
private took(created: Date, completed: Date): string {
122+
const diff = moment(completed).diff(created);
123+
return moment.duration(diff).humanize();
124+
}
125+
117126
private retry(id: string): Promise<any> {
118127
return EnduroCollectionClient.collectionRetry({id: +id});
119128
}

ui/src/components/CollectionList.vue

Lines changed: 131 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,72 @@
11
<template>
22
<div class="collection-list">
3-
<table class="table table-bordered table-hover table-sm">
4-
<thead class="thead">
5-
<tr>
6-
<th scope="col">ID</th>
7-
<th scope="col">Name</th>
8-
<th scope="col">Created</th>
9-
<th scope="col">Completed</th>
10-
<th scope="col">Status</th>
11-
</tr>
12-
</thead>
13-
<tbody>
14-
<tr v-for="item in collections" v-bind:key="item.id" @click="view(item.id)">
15-
<th scope="row">{{ item.id }}</th>
16-
<td class="collection-name">{{ item.name }}</td>
17-
<td>{{ item.created_at | formatDateTime }}</td>
18-
<td>{{ item.completed_at | formatDateTime }}</td>
19-
<td>
20-
<CollectionStatusBadge :status="item.status"/>
21-
</td>
22-
</tr>
23-
</tbody>
24-
</table>
25-
<nav>
26-
<ul class="pagination">
27-
<li class="page-item">
28-
<button class="page-link" @click="next('')">Reload</button>
29-
</li>
30-
<li class="page-item" v-if="nextCursor">
31-
<button class="page-link" @click="next(nextCursor)">Next</button>
32-
</li>
33-
</ul>
34-
</nav>
3+
<template v-if="error">
4+
<b-alert show dismissible variant="warning">
5+
<h4 class="alert-heading">Search error</h4>
6+
We couldn't connect to the API server. You may want to try again in a few seconds.
7+
<hr />
8+
<b-button @click="retryButtonClicked" class="m-1">Retry</b-button>
9+
</b-alert>
10+
</template>
11+
<template v-else>
12+
<div>
13+
<b-form inline @submit="onSubmit" @reset="onReset" class="py-3">
14+
<b-input-group size="sm" class="w-100">
15+
<b-form-input autofocus v-model="query" type="text" placeholder="E.g.: a23bd138-4225-4cf7-845d-07a8983cf32f"></b-form-input>
16+
<b-input-group-append>
17+
<b-button type="submit" variant="info">Search</b-button>
18+
<b-button type="reset">Reset</b-button>
19+
</b-input-group-append>
20+
</b-input-group>
21+
</b-form>
22+
</div>
23+
<template v-if="results.length > 0">
24+
<table class="table table-bordered table-hover table-sm">
25+
<thead class="thead">
26+
<tr>
27+
<th scope="col">ID</th>
28+
<th scope="col">Name</th>
29+
<th scope="col">Created</th>
30+
<th scope="col">Completed</th>
31+
<th scope="col">Status</th>
32+
</tr>
33+
</thead>
34+
<tbody>
35+
<tr v-for="item in results" v-bind:key="item.id" @click="rowClicked(item.id)">
36+
<th scope="row">{{ item.id }}</th>
37+
<td class="collection-name">{{ item.name }}</td>
38+
<td>{{ item.createdAt | formatDateTime }}</td>
39+
<td>{{ item.completedAt | formatDateTime }}</td>
40+
<td><CollectionStatusBadge :status="item.status"/></td>
41+
</tr>
42+
</tbody>
43+
</table>
44+
<nav>
45+
<ul class="pagination">
46+
<li class="page-item">
47+
<button class="page-link" @click="reloadButtonClicked">Reload</button>
48+
</li>
49+
<li class="page-item" v-if="nextCursor">
50+
<button class="page-link" @click="nextButtonClicked(nextCursor)">Next</button>
51+
</li>
52+
</ul>
53+
</nav>
54+
</template>
55+
<div v-if="results.length === 0">
56+
No results.
57+
</div>
58+
</template>
3559
</div>
3660
</template>
3761

3862
<script lang="ts">
39-
import { Component, Prop, Provide, Vue } from 'vue-property-decorator';
40-
import { EnduroCollectionClient } from '../client';
63+
import { Component, Vue } from 'vue-property-decorator';
64+
import { namespace } from 'vuex-class';
65+
import { api, EnduroCollectionClient } from '../client';
4166
import CollectionStatusBadge from '@/components/CollectionStatusBadge.vue';
42-
import { CollectionListRequest, CollectionListResponseBody, EnduroStoredCollectionResponseBodyCollection } from '../client/src';
67+
import * as CollectionStore from '../store/collection';
68+
69+
const collectionStoreNs = namespace('collection');
4370
4471
@Component({
4572
components: {
@@ -48,36 +75,85 @@ import { CollectionListRequest, CollectionListResponseBody, EnduroStoredCollecti
4875
})
4976
export default class CollectionList extends Vue {
5077
51-
private interval: number = 0;
52-
private collections: EnduroStoredCollectionResponseBodyCollection = [];
53-
private nextCursor?: string = '';
78+
@collectionStoreNs.Getter(CollectionStore.GET_SEARCH_ERROR)
79+
private error?: boolean;
80+
81+
@collectionStoreNs.Getter(CollectionStore.GET_SEARCH_RESULTS)
82+
private results: any;
83+
84+
@collectionStoreNs.Getter(CollectionStore.GET_SEARCH_NEXT_CURSOR)
85+
private nextCursor: any;
86+
87+
@collectionStoreNs.Action(CollectionStore.SEARCH_COLLECTIONS)
88+
private search: any;
89+
90+
private query: string | null = null;
5491
5592
private created() {
56-
this.next();
93+
this.search();
94+
}
95+
96+
/**
97+
* Performs search action.
98+
*
99+
* @remarks
100+
* Search method for CollectionList. By default, it uses the cursor member of
101+
* the class.
102+
*
103+
* @param cursor - Optional cursor. Set to null to reset the cursor.
104+
*/
105+
private doSearch(cursor?: string | null) {
106+
const attrs: any = {
107+
query: this.query,
108+
cursor: typeof(cursor) === 'undefined' ? this.nextCursor : cursor,
109+
};
110+
this.search(attrs);
111+
}
112+
113+
/**
114+
* Perform same search re-using all existing state.
115+
*/
116+
private retryButtonClicked() {
117+
this.doSearch();
57118
}
58119
59-
private load(cursor?: string) {
60-
const request: CollectionListRequest = {};
61-
if (cursor) {
62-
request.cursor = cursor;
63-
}
64-
return EnduroCollectionClient.collectionList(request);
120+
/**
121+
* Perform search with the cursor reset.
122+
*/
123+
private reloadButtonClicked() {
124+
this.doSearch(null);
65125
}
66126
67-
private next(cursor?: string) {
68-
this.load(cursor).then((response: CollectionListResponseBody) => {
69-
this.collections = response.items;
70-
this.nextCursor = response.nextCursor;
71-
});
127+
/*/
128+
* Perform search with a new cursor.
129+
*/
130+
private nextButtonClicked(cursor: string) {
131+
this.doSearch(cursor);
72132
}
73133
74-
private view(id: string) {
75-
this.$router.push({
76-
name: 'collection',
77-
params: {id},
78-
});
134+
/**
135+
* Perform search with the cursor reset.
136+
*/
137+
private onSubmit(event: Event) {
138+
event.preventDefault();
139+
this.doSearch(null);
79140
}
80141
142+
/**
143+
* Perform search with both the query and cursor reset.
144+
*/
145+
private onReset(event: Event) {
146+
event.preventDefault();
147+
this.query = null;
148+
this.doSearch(null);
149+
}
150+
151+
/**
152+
* Forward user to the collection route.
153+
*/
154+
private rowClicked(id: string) {
155+
this.$router.push({ name: 'collection', params: {id} });
156+
}
81157
}
82158
</script>
83159

0 commit comments

Comments
 (0)