diff --git a/web/src/components/measure-summaries/ListSummary.vue b/web/src/components/measure-summaries/ListSummary.vue
new file mode 100644
index 0000000..525c279
--- /dev/null
+++ b/web/src/components/measure-summaries/ListSummary.vue
@@ -0,0 +1,6 @@
+
+
+
+
diff --git a/web/src/components/MeasureSummary.vue b/web/src/components/measure-summaries/MeasureSummary.vue
similarity index 52%
rename from web/src/components/MeasureSummary.vue
rename to web/src/components/measure-summaries/MeasureSummary.vue
index d21d044..1d1836e 100644
--- a/web/src/components/MeasureSummary.vue
+++ b/web/src/components/measure-summaries/MeasureSummary.vue
@@ -1,7 +1,10 @@
diff --git a/web/src/components/measure-summaries/SimpleSummaryGraph.vue b/web/src/components/measure-summaries/SimpleSummaryGraph.vue
new file mode 100644
index 0000000..778f513
--- /dev/null
+++ b/web/src/components/measure-summaries/SimpleSummaryGraph.vue
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/web/src/components/measure-summaries/list-summary.ts b/web/src/components/measure-summaries/list-summary.ts
new file mode 100644
index 0000000..9b46fbf
--- /dev/null
+++ b/web/src/components/measure-summaries/list-summary.ts
@@ -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;
+ @Prop() private measurements!: Array>;
+
+ private top5(): Array> {
+ return this.measurements
+ .sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime())
+ .slice(0, 5);
+ }
+}
+
+export default ListSummary;
diff --git a/web/src/components/measure-summary.scss b/web/src/components/measure-summaries/measure-summary.scss
similarity index 100%
rename from web/src/components/measure-summary.scss
rename to web/src/components/measure-summaries/measure-summary.scss
diff --git a/web/src/components/measure-summaries/measure-summary.ts b/web/src/components/measure-summaries/measure-summary.ts
new file mode 100644
index 0000000..85aad98
--- /dev/null
+++ b/web/src/components/measure-summaries/measure-summary.ts
@@ -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;
+
+ private get measurements() {
+ return measurementStore.measurements[this.measure.id] || [];
+ }
+
+ private async mounted() {
+ await measurementStore.fetchMeasurements(this.measure);
+ }
+
+}
+
+export default MeasureSummary;
diff --git a/web/src/components/measure-summaries/simple-summary-graph.ts b/web/src/components/measure-summaries/simple-summary-graph.ts
new file mode 100644
index 0000000..bb9a170
--- /dev/null
+++ b/web/src/components/measure-summaries/simple-summary-graph.ts
@@ -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;
+ @Prop() private measurements!: Array>;
+
+ 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;
diff --git a/web/src/components/measure-summary.ts b/web/src/components/measure-summary.ts
deleted file mode 100644
index 1b7f5ba..0000000
--- a/web/src/components/measure-summary.ts
+++ /dev/null
@@ -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;
-
- 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> = [];
-
- private mounted() {
- this.measurements =
- }
-
-}
-
-export default MeasureSummary;
diff --git a/web/src/models.d.ts b/web/src/models.d.ts
index 1a93c3c..937a9bf 100644
--- a/web/src/models.d.ts
+++ b/web/src/models.d.ts
@@ -1,4 +1,4 @@
-export enum MeasureType { Simple }
+export enum MeasureType { List = 'list', Simple = 'simple' }
export interface ApiToken {
id: string;
@@ -15,9 +15,11 @@ export interface LoginSubmit {
}
export interface MeasureConfig {
- type: string;
+ type: MeasureType;
}
+export interface ListMeasureConfig extends MeasureConfig { }
+
export interface Measure {
id: string;
userId: string;
@@ -27,7 +29,13 @@ export interface Measure {
config: C;
}
-export interface MeasurementMeta {}
+export interface MeasurementMeta {
+ measureType: MeasureType;
+}
+
+export interface ListMeasurementMeta extends MeasurementMeta {
+ entry: string;
+}
export interface Measurement {
id: string;
diff --git a/web/src/services/pm-api-client.ts b/web/src/services/pm-api-client.ts
index 9f0abec..169741a 100644
--- a/web/src/services/pm-api-client.ts
+++ b/web/src/services/pm-api-client.ts
@@ -1,7 +1,7 @@
import { default as Axios, AxiosInstance } from 'axios';
+import assign from 'lodash.assign';
import { ApiToken, LoginSubmit, Measure, MeasureConfig, Measurement, MeasurementMeta, User } from '@/models';
import { Logger, logService } from '@/services/logging';
-import merge from 'lodash.merge';
export class PmApiClient {
private http: AxiosInstance;
@@ -132,16 +132,18 @@ export class PmApiClient {
: Promise>> {
const resp = await this.http.get(`/measure/${measureSlug}`);
- return resp.data;
+ return resp.data.map(this.fromMeasurementDTO);
}
- public async createMeasurement(
+ public async createMeasurement(
measureSlug: string,
- measurement: Measurement)
- : Promise> {
+ measurement: Measurement)
+ : Promise> {
- const resp = await this.http.post(`/measure/${measureSlug}`, measurement);
- return resp.data;
+ const resp = await this.http.post(
+ `/measure/${measureSlug}`,
+ this.toMeasurementDTO(measurement));
+ return this.fromMeasurementDTO(resp.data);
}
public async getMeasurement(
@@ -151,7 +153,7 @@ export class PmApiClient {
const resp = await this.http
.get(`/measure/${measureSlug}/${measurementId}`);
- return resp.data;
+ return this.fromMeasurementDTO(resp.data);
}
public async updateMeasurement(
@@ -159,9 +161,10 @@ export class PmApiClient {
measurement: Measurement)
: Promise> {
- const resp = await this.http
- .put(`/measure/${measureSlug}/${measurement.id}`, measurement);
- return resp.data;
+ const resp = await this.http.put(
+ `/measure/${measureSlug}/${measurement.id}`,
+ this.toMeasurementDTO(measurement));
+ return this.fromMeasurementDTO(resp.data);
}
public async deleteMeasurement(
@@ -173,6 +176,15 @@ export class PmApiClient {
.delete(`/measure/${measureSlug}/${measurementId}`);
return true;
}
+
+ private fromMeasurementDTO(dto: any): Measurement {
+ return assign({}, dto, { timestamp: new Date(dto.timestamp) });
+ }
+
+ private toMeasurementDTO(measurement: Measurement): object {
+ return assign({}, measurement, { timestamp: measurement.timestamp.toISOString() });
+ }
+
}
export const api = new PmApiClient(process.env.VUE_APP_PM_API_BASE);
diff --git a/web/src/store-modules/measure.ts b/web/src/store-modules/measure.ts
index a8a8caa..7afeaed 100644
--- a/web/src/store-modules/measure.ts
+++ b/web/src/store-modules/measure.ts
@@ -9,23 +9,21 @@ import {
import { keyBy } from 'lodash';
import { User, Measure, MeasureConfig } from '@/models';
import api from '@/services/pm-api-client';
-import { logService } from '@/services/logging';
@Module({ namespaced: true, name: 'measure' })
export class MeasureStoreModule extends VuexModule {
public measures: { [key: string]: Measure } = {};
- private log = logService.getLogger('/store-modules/measure');
-
@MutationAction({ mutate: ['measures'], rawError: true })
public async fetchAllMeasures() {
const measures = await api.getAllMeasures();
return { measures: keyBy(measures, 'slug') };
}
- @Action({ commit: 'SET_MEASURE', rawError: true })
+ @Action({ rawError: true })
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(measure: Measure) {
diff --git a/web/src/store-modules/measurement.ts b/web/src/store-modules/measurement.ts
index 5531e58..7bdea4b 100644
--- a/web/src/store-modules/measurement.ts
+++ b/web/src/store-modules/measurement.ts
@@ -3,19 +3,37 @@ import {
getModule,
Module,
Mutation,
+ MutationAction,
VuexModule
} 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 { logService } from '@/services/logging';
+
+export interface MeasurementStore { [key: string]: Array>; }
+export interface SetMeasurementsParameters {
+ measure: Measure;
+ measurements: Array>;
+}
+
+const logger = logService.getLogger('/store-modules/measurement');
@Module({ namespaced: true, name: 'measurement' })
export class MeasurementStoreModule extends VuexModule {
- public measurements: { [key: string]: Array> } = {};
+ public measurements: MeasurementStore = {};
- @Action({ commit: 'SET_MEASUREMENTS', rawError: true })
- public async getchMeasurements(measureSlug: string) {
- return await api.getMeasurements(measureSlug);
+ @Action({ rawError: true })
+ public async fetchMeasurements(measure: Measure) {
+ 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(
}
diff --git a/web/src/store.ts b/web/src/store.ts
index 27993f6..02543a4 100644
--- a/web/src/store.ts
+++ b/web/src/store.ts
@@ -17,7 +17,7 @@ export const store = new Vuex.Store({
apiToken: ApiTokenStoreModule,
auth: AuthStoreModule,
measure: MeasureStoreModule,
- measurements: MeasurementStoreModule,
+ measurement: MeasurementStoreModule,
user: UserStoreModule
}
});
diff --git a/web/src/types/apexcharts-inner-types.d.ts b/web/src/types/apexcharts-inner-types.d.ts
new file mode 100644
index 0000000..7cf989a
--- /dev/null
+++ b/web/src/types/apexcharts-inner-types.d.ts
@@ -0,0 +1,7 @@
+declare module 'apexcharts-types' {
+ export interface ApexAxisChartOneSeries {
+ name: string;
+ data: number[] | Array<{ x: string; y: number }>;
+ }
+ export type ApexAxisChartSeries = ApexAxisChartOneSeries[];
+}
diff --git a/web/src/views/measures.ts b/web/src/views/measures.ts
index 747476b..e391136 100644
--- a/web/src/views/measures.ts
+++ b/web/src/views/measures.ts
@@ -1,5 +1,5 @@
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 { measureStore } from '@/store';