WIP Adding measure summary views.
This commit is contained in:
parent
622fed8e2a
commit
7a118bd8e8
6
web/src/components/measure-summaries/ListSummary.vue
Normal file
6
web/src/components/measure-summaries/ListSummary.vue
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<template>
|
||||||
|
<ul>
|
||||||
|
<li v-for="m in top5">{{m.extData.entry}}</li>
|
||||||
|
</ul>
|
||||||
|
</template>
|
||||||
|
<script lang="ts" src="./list-summary.ts"></script>
|
@ -1,7 +1,10 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="measure-summary" :data-name="'measure-' + measure.slug">
|
<div class="measure-summary" :data-name="'measure-' + measure.slug">
|
||||||
<h2>{{measure.name}}</h2>
|
<h2>{{measure.name}}</h2>
|
||||||
<apex-chart type="line" :options=chartOptions :series=chartData />
|
<SimpleSummaryGraph v-if="measure.config.type === 'simple'"
|
||||||
|
:measure=measure :measurements=measurements />
|
||||||
|
<ListSummary v-if="measure.config.type === 'list'"
|
||||||
|
:measure=measure :measurements=measurements />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts" src="./measure-summary.ts"></script>
|
<script lang="ts" src="./measure-summary.ts"></script>
|
@ -0,0 +1,4 @@
|
|||||||
|
<template>
|
||||||
|
<apex-chart type="line" :options=chartOptions :series=measurementData />
|
||||||
|
</template>
|
||||||
|
<script lang="ts" src="./simple-summary-graph.ts"></script>
|
16
web/src/components/measure-summaries/list-summary.ts
Normal file
16
web/src/components/measure-summaries/list-summary.ts
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import { Component, Prop, Vue } from 'vue-property-decorator';
|
||||||
|
import { Measure, ListMeasureConfig, Measurement, ListMeasurementMeta } from '@/models';
|
||||||
|
|
||||||
|
@Component
|
||||||
|
export class ListSummary extends Vue {
|
||||||
|
@Prop() private measure!: Measure<ListMeasureConfig>;
|
||||||
|
@Prop() private measurements!: Array<Measurement<ListMeasurementMeta>>;
|
||||||
|
|
||||||
|
private top5(): Array<Measurement<ListMeasurementMeta>> {
|
||||||
|
return this.measurements
|
||||||
|
.sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime())
|
||||||
|
.slice(0, 5);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ListSummary;
|
26
web/src/components/measure-summaries/measure-summary.ts
Normal file
26
web/src/components/measure-summaries/measure-summary.ts
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import { Component, Prop, Vue } from 'vue-property-decorator';
|
||||||
|
import { Measure, MeasureConfig, MeasureType, Measurement, MeasurementMeta } from '@/models';
|
||||||
|
import { measurementStore } from '@/store';
|
||||||
|
import ListSummary from './ListSummary.vue';
|
||||||
|
import SimpleSummaryGraph from './SimpleSummaryGraph.vue';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
components: {
|
||||||
|
ListSummary,
|
||||||
|
SimpleSummaryGraph
|
||||||
|
}
|
||||||
|
})
|
||||||
|
export class MeasureSummary extends Vue {
|
||||||
|
@Prop() private measure!: Measure<MeasureConfig>;
|
||||||
|
|
||||||
|
private get measurements() {
|
||||||
|
return measurementStore.measurements[this.measure.id] || [];
|
||||||
|
}
|
||||||
|
|
||||||
|
private async mounted() {
|
||||||
|
await measurementStore.fetchMeasurements(this.measure);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default MeasureSummary;
|
28
web/src/components/measure-summaries/simple-summary-graph.ts
Normal file
28
web/src/components/measure-summaries/simple-summary-graph.ts
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import { Component, Prop, Vue } from 'vue-property-decorator';
|
||||||
|
import { Measure, MeasureConfig, Measurement, MeasurementMeta } from '@/models';
|
||||||
|
|
||||||
|
@Component
|
||||||
|
export class SimpleSummaryGraph extends Vue {
|
||||||
|
@Prop() private measure!: Measure<MeasureConfig>;
|
||||||
|
@Prop() private measurements!: Array<Measurement<MeasurementMeta>>;
|
||||||
|
|
||||||
|
private chartOptions = {
|
||||||
|
chart: { sparkline: { enabled: true } },
|
||||||
|
grid: { padding: { top: 20 }},
|
||||||
|
stroke: { curve: 'smooth' },
|
||||||
|
noData: { text: 'no data',
|
||||||
|
style: { fontSize: '18px' } },
|
||||||
|
xaxis: { type: 'datetime' }
|
||||||
|
};
|
||||||
|
|
||||||
|
private get measurementData(): ApexAxisChartSeries {
|
||||||
|
const measurementData = this.measurements || [];
|
||||||
|
|
||||||
|
return [{
|
||||||
|
name: this.measure.name,
|
||||||
|
data: measurementData.map((m) => ({ x: m.timestamp.toISOString(), y: m.value }))
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default SimpleSummaryGraph;
|
@ -1,27 +0,0 @@
|
|||||||
import { Component, Prop, Vue } from 'vue-property-decorator';
|
|
||||||
import { Measure, MeasureConfig, Measurement, MeasurementMeta } from '@/models';
|
|
||||||
import { measurementStore } from '@/store';
|
|
||||||
|
|
||||||
@Component
|
|
||||||
export class MeasureSummary extends Vue {
|
|
||||||
@Prop() private measure!: Measure<MeasureConfig>;
|
|
||||||
|
|
||||||
private chartOptions = {
|
|
||||||
chart: { sparkline: { enabled: true } },
|
|
||||||
grid: { padding: { top: 20 }},
|
|
||||||
stroke: { curve: 'smooth' }
|
|
||||||
};
|
|
||||||
|
|
||||||
private chartData = [
|
|
||||||
{ name: 'Test', data: [1, 10, 4, 6, 2] }
|
|
||||||
];
|
|
||||||
|
|
||||||
private measurements: Array<Measurement<MeasurementMeta>> = [];
|
|
||||||
|
|
||||||
private mounted() {
|
|
||||||
this.measurements =
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export default MeasureSummary;
|
|
14
web/src/models.d.ts
vendored
14
web/src/models.d.ts
vendored
@ -1,4 +1,4 @@
|
|||||||
export enum MeasureType { Simple }
|
export enum MeasureType { List = 'list', Simple = 'simple' }
|
||||||
|
|
||||||
export interface ApiToken {
|
export interface ApiToken {
|
||||||
id: string;
|
id: string;
|
||||||
@ -15,9 +15,11 @@ export interface LoginSubmit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface MeasureConfig {
|
export interface MeasureConfig {
|
||||||
type: string;
|
type: MeasureType;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ListMeasureConfig extends MeasureConfig { }
|
||||||
|
|
||||||
export interface Measure<C extends MeasureConfig> {
|
export interface Measure<C extends MeasureConfig> {
|
||||||
id: string;
|
id: string;
|
||||||
userId: string;
|
userId: string;
|
||||||
@ -27,7 +29,13 @@ export interface Measure<C extends MeasureConfig> {
|
|||||||
config: C;
|
config: C;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MeasurementMeta {}
|
export interface MeasurementMeta {
|
||||||
|
measureType: MeasureType;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ListMeasurementMeta extends MeasurementMeta {
|
||||||
|
entry: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface Measurement<M extends MeasurementMeta> {
|
export interface Measurement<M extends MeasurementMeta> {
|
||||||
id: string;
|
id: string;
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { default as Axios, AxiosInstance } from 'axios';
|
import { default as Axios, AxiosInstance } from 'axios';
|
||||||
|
import assign from 'lodash.assign';
|
||||||
import { ApiToken, LoginSubmit, Measure, MeasureConfig, Measurement, MeasurementMeta, User } from '@/models';
|
import { ApiToken, LoginSubmit, Measure, MeasureConfig, Measurement, MeasurementMeta, User } from '@/models';
|
||||||
import { Logger, logService } from '@/services/logging';
|
import { Logger, logService } from '@/services/logging';
|
||||||
import merge from 'lodash.merge';
|
|
||||||
|
|
||||||
export class PmApiClient {
|
export class PmApiClient {
|
||||||
private http: AxiosInstance;
|
private http: AxiosInstance;
|
||||||
@ -132,16 +132,18 @@ export class PmApiClient {
|
|||||||
: Promise<Array<Measurement<MeasurementMeta>>> {
|
: Promise<Array<Measurement<MeasurementMeta>>> {
|
||||||
|
|
||||||
const resp = await this.http.get(`/measure/${measureSlug}`);
|
const resp = await this.http.get(`/measure/${measureSlug}`);
|
||||||
return resp.data;
|
return resp.data.map(this.fromMeasurementDTO);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async createMeasurement<T extends MeasurementMeta>(
|
public async createMeasurement(
|
||||||
measureSlug: string,
|
measureSlug: string,
|
||||||
measurement: Measurement<MeasureConfig>)
|
measurement: Measurement<MeasurementMeta>)
|
||||||
: Promise<Measurement<T>> {
|
: Promise<Measurement<MeasurementMeta>> {
|
||||||
|
|
||||||
const resp = await this.http.post(`/measure/${measureSlug}`, measurement);
|
const resp = await this.http.post(
|
||||||
return resp.data;
|
`/measure/${measureSlug}`,
|
||||||
|
this.toMeasurementDTO(measurement));
|
||||||
|
return this.fromMeasurementDTO(resp.data);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getMeasurement(
|
public async getMeasurement(
|
||||||
@ -151,7 +153,7 @@ export class PmApiClient {
|
|||||||
|
|
||||||
const resp = await this.http
|
const resp = await this.http
|
||||||
.get(`/measure/${measureSlug}/${measurementId}`);
|
.get(`/measure/${measureSlug}/${measurementId}`);
|
||||||
return resp.data;
|
return this.fromMeasurementDTO(resp.data);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async updateMeasurement(
|
public async updateMeasurement(
|
||||||
@ -159,9 +161,10 @@ export class PmApiClient {
|
|||||||
measurement: Measurement<MeasurementMeta>)
|
measurement: Measurement<MeasurementMeta>)
|
||||||
: Promise<Measurement<MeasurementMeta>> {
|
: Promise<Measurement<MeasurementMeta>> {
|
||||||
|
|
||||||
const resp = await this.http
|
const resp = await this.http.put(
|
||||||
.put(`/measure/${measureSlug}/${measurement.id}`, measurement);
|
`/measure/${measureSlug}/${measurement.id}`,
|
||||||
return resp.data;
|
this.toMeasurementDTO(measurement));
|
||||||
|
return this.fromMeasurementDTO(resp.data);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async deleteMeasurement(
|
public async deleteMeasurement(
|
||||||
@ -173,6 +176,15 @@ export class PmApiClient {
|
|||||||
.delete(`/measure/${measureSlug}/${measurementId}`);
|
.delete(`/measure/${measureSlug}/${measurementId}`);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fromMeasurementDTO(dto: any): Measurement<MeasurementMeta> {
|
||||||
|
return assign({}, dto, { timestamp: new Date(dto.timestamp) });
|
||||||
|
}
|
||||||
|
|
||||||
|
private toMeasurementDTO(measurement: Measurement<MeasurementMeta>): object {
|
||||||
|
return assign({}, measurement, { timestamp: measurement.timestamp.toISOString() });
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const api = new PmApiClient(process.env.VUE_APP_PM_API_BASE);
|
export const api = new PmApiClient(process.env.VUE_APP_PM_API_BASE);
|
||||||
|
@ -9,23 +9,21 @@ import {
|
|||||||
import { keyBy } from 'lodash';
|
import { keyBy } from 'lodash';
|
||||||
import { User, Measure, MeasureConfig } from '@/models';
|
import { User, Measure, MeasureConfig } from '@/models';
|
||||||
import api from '@/services/pm-api-client';
|
import api from '@/services/pm-api-client';
|
||||||
import { logService } from '@/services/logging';
|
|
||||||
|
|
||||||
@Module({ namespaced: true, name: 'measure' })
|
@Module({ namespaced: true, name: 'measure' })
|
||||||
export class MeasureStoreModule extends VuexModule {
|
export class MeasureStoreModule extends VuexModule {
|
||||||
public measures: { [key: string]: Measure<MeasureConfig> } = {};
|
public measures: { [key: string]: Measure<MeasureConfig> } = {};
|
||||||
|
|
||||||
private log = logService.getLogger('/store-modules/measure');
|
|
||||||
|
|
||||||
@MutationAction({ mutate: ['measures'], rawError: true })
|
@MutationAction({ mutate: ['measures'], rawError: true })
|
||||||
public async fetchAllMeasures() {
|
public async fetchAllMeasures() {
|
||||||
const measures = await api.getAllMeasures();
|
const measures = await api.getAllMeasures();
|
||||||
return { measures: keyBy(measures, 'slug') };
|
return { measures: keyBy(measures, 'slug') };
|
||||||
}
|
}
|
||||||
|
|
||||||
@Action({ commit: 'SET_MEASURE', rawError: true })
|
@Action({ rawError: true })
|
||||||
public async fetchMeasure(slug: string) {
|
public async fetchMeasure(slug: string) {
|
||||||
return await api.getMeasure(slug);
|
const measure = api.getMeasure(slug);
|
||||||
|
this.context.commit('SET_MEASURE', measure);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Mutation private SET_MEASURE<T extends MeasureConfig>(measure: Measure<T>) {
|
@Mutation private SET_MEASURE<T extends MeasureConfig>(measure: Measure<T>) {
|
||||||
|
@ -3,19 +3,37 @@ import {
|
|||||||
getModule,
|
getModule,
|
||||||
Module,
|
Module,
|
||||||
Mutation,
|
Mutation,
|
||||||
|
MutationAction,
|
||||||
VuexModule
|
VuexModule
|
||||||
} from 'vuex-module-decorators';
|
} from 'vuex-module-decorators';
|
||||||
import { Measurement, MeasurementMeta } from '@/models';
|
import findIndex from 'lodash.findindex';
|
||||||
|
import { Measure, MeasureConfig, Measurement, MeasurementMeta } from '@/models';
|
||||||
|
import assign from 'lodash.assign';
|
||||||
import api from '@/services/pm-api-client';
|
import api from '@/services/pm-api-client';
|
||||||
|
import { logService } from '@/services/logging';
|
||||||
|
|
||||||
|
export interface MeasurementStore { [key: string]: Array<Measurement<MeasurementMeta>>; }
|
||||||
|
export interface SetMeasurementsParameters {
|
||||||
|
measure: Measure<MeasureConfig>;
|
||||||
|
measurements: Array<Measurement<MeasurementMeta>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const logger = logService.getLogger('/store-modules/measurement');
|
||||||
|
|
||||||
@Module({ namespaced: true, name: 'measurement' })
|
@Module({ namespaced: true, name: 'measurement' })
|
||||||
export class MeasurementStoreModule extends VuexModule {
|
export class MeasurementStoreModule extends VuexModule {
|
||||||
public measurements: { [key: string]: Array<Measurement<MeasurementMeta>> } = {};
|
public measurements: MeasurementStore = {};
|
||||||
|
|
||||||
@Action({ commit: 'SET_MEASUREMENTS', rawError: true })
|
@Action({ rawError: true })
|
||||||
public async getchMeasurements(measureSlug: string) {
|
public async fetchMeasurements(measure: Measure<MeasureConfig>) {
|
||||||
return await api.getMeasurements(measureSlug);
|
logger.debug('Fetching measurements for measure ' + measure.id);
|
||||||
|
const measurements = await api.getMeasurements(measure.slug); // assumption: always returns at least []
|
||||||
|
this.context.commit('SET_MEASUREMENTS', { measure, measurements });
|
||||||
|
}
|
||||||
|
|
||||||
|
@Mutation
|
||||||
|
public SET_MEASUREMENTS({ measure: measure, measurements: measurements }: SetMeasurementsParameters) {
|
||||||
|
this.measurements = assign({}, this.measurements, { [measure.id]: measurements });
|
||||||
}
|
}
|
||||||
|
|
||||||
// @Mutation private SET_MEASUREMENTS<T extends MeasurementMeta>(
|
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,7 @@ export const store = new Vuex.Store({
|
|||||||
apiToken: ApiTokenStoreModule,
|
apiToken: ApiTokenStoreModule,
|
||||||
auth: AuthStoreModule,
|
auth: AuthStoreModule,
|
||||||
measure: MeasureStoreModule,
|
measure: MeasureStoreModule,
|
||||||
measurements: MeasurementStoreModule,
|
measurement: MeasurementStoreModule,
|
||||||
user: UserStoreModule
|
user: UserStoreModule
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
7
web/src/types/apexcharts-inner-types.d.ts
vendored
Normal file
7
web/src/types/apexcharts-inner-types.d.ts
vendored
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
declare module 'apexcharts-types' {
|
||||||
|
export interface ApexAxisChartOneSeries {
|
||||||
|
name: string;
|
||||||
|
data: number[] | Array<{ x: string; y: number }>;
|
||||||
|
}
|
||||||
|
export type ApexAxisChartSeries = ApexAxisChartOneSeries[];
|
||||||
|
}
|
@ -1,5 +1,5 @@
|
|||||||
import { Component, Vue } from 'vue-property-decorator';
|
import { Component, Vue } from 'vue-property-decorator';
|
||||||
import MeasureSummary from '@/components/MeasureSummary.vue';
|
import MeasureSummary from '@/components/measure-summaries/MeasureSummary.vue';
|
||||||
import Test from '@/components/Test.vue';
|
import Test from '@/components/Test.vue';
|
||||||
import { measureStore } from '@/store';
|
import { measureStore } from '@/store';
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user