@c8y/tutorial 1021.25.5 → 1021.26.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/cumulocity.config.ts +7 -0
- package/package.json +7 -7
- package/src/__mocks/mock.module.ts +11 -0
- package/src/__mocks/scoped-mocks/measurement-series.ts +112 -0
- package/src/__mocks/utils/generators/measurement.ts +11 -6
- package/src/__mocks/utils/generators/series.ts +41 -0
- package/src/selector/data-points-export-selector-example/datapoints-export-selector.component.ts +73 -0
- package/src/selector/data-points-export-selector-example/datapoints-export-selector.module.ts +23 -0
package/cumulocity.config.ts
CHANGED
|
@@ -632,6 +632,13 @@ export default {
|
|
|
632
632
|
description: 'An introduction to alarm event selector example.',
|
|
633
633
|
scope: 'self'
|
|
634
634
|
},
|
|
635
|
+
{
|
|
636
|
+
name: 'Introduction to data points export selector',
|
|
637
|
+
module: 'DatapointsExportSelectorExampleModule',
|
|
638
|
+
path: './src/selector/data-points-export-selector-example/datapoints-export-selector.module.ts',
|
|
639
|
+
description: 'An introduction to data points export selector example.',
|
|
640
|
+
scope: 'self'
|
|
641
|
+
},
|
|
635
642
|
{
|
|
636
643
|
name: 'Introduction to asset selector device child',
|
|
637
644
|
module: 'AssetSingleSelectModule',
|
package/package.json
CHANGED
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@c8y/tutorial",
|
|
3
|
-
"version": "1021.
|
|
3
|
+
"version": "1021.26.2",
|
|
4
4
|
"description": "This package is used to scaffold a tutorial for Cumulocity IoT Web SDK which explains a lot of concepts.",
|
|
5
5
|
"dependencies": {
|
|
6
|
-
"@c8y/style": "1021.
|
|
7
|
-
"@c8y/ngx-components": "1021.
|
|
8
|
-
"@c8y/client": "1021.
|
|
9
|
-
"@c8y/bootstrap": "1021.
|
|
6
|
+
"@c8y/style": "1021.26.2",
|
|
7
|
+
"@c8y/ngx-components": "1021.26.2",
|
|
8
|
+
"@c8y/client": "1021.26.2",
|
|
9
|
+
"@c8y/bootstrap": "1021.26.2",
|
|
10
10
|
"@angular/cdk": "^18.2.10",
|
|
11
11
|
"ngx-bootstrap": "18.0.0",
|
|
12
12
|
"leaflet": "1.9.4",
|
|
13
13
|
"rxjs": "^7.8.1"
|
|
14
14
|
},
|
|
15
15
|
"devDependencies": {
|
|
16
|
-
"@c8y/options": "1021.
|
|
17
|
-
"@c8y/devkit": "1021.
|
|
16
|
+
"@c8y/options": "1021.26.2",
|
|
17
|
+
"@c8y/devkit": "1021.26.2"
|
|
18
18
|
},
|
|
19
19
|
"peerDependencies": {
|
|
20
20
|
"@angular/common": ">=18 <19"
|
|
@@ -20,6 +20,7 @@ import { ServerSideDataGridInterceptor } from './scoped-mocks/server-side-data-g
|
|
|
20
20
|
import { ServiceDashboardInterceptor } from './scoped-mocks/service-dashboard';
|
|
21
21
|
import { TypeaheadInterceptor } from './scoped-mocks/typeahead';
|
|
22
22
|
// import { BoilerplateInterceptor } from './scoped-mocks/boilerplate';
|
|
23
|
+
import { MeasurementsSeriesInterceptor } from './scoped-mocks/measurement-series';
|
|
23
24
|
@NgModule({
|
|
24
25
|
imports: [CoreModule, CommonModule],
|
|
25
26
|
providers: [
|
|
@@ -34,6 +35,16 @@ import { TypeaheadInterceptor } from './scoped-mocks/typeahead';
|
|
|
34
35
|
// } as ApiMockConfig,
|
|
35
36
|
// multi: true
|
|
36
37
|
// },
|
|
38
|
+
{
|
|
39
|
+
provide: API_MOCK_CONFIG,
|
|
40
|
+
useValue: {
|
|
41
|
+
id: 'a-datapoints-export-selector-interceptor',
|
|
42
|
+
path: 'datapoints-export-selector-example',
|
|
43
|
+
mockService: MeasurementsSeriesInterceptor,
|
|
44
|
+
debug: true
|
|
45
|
+
} as ApiMockConfig,
|
|
46
|
+
multi: true
|
|
47
|
+
},
|
|
37
48
|
{
|
|
38
49
|
provide: API_MOCK_CONFIG,
|
|
39
50
|
useValue: {
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { IFetchResponse, IMeasurement } from '@c8y/client';
|
|
2
|
+
import { ApiCall, HttpHandler, HttpInterceptor } from '@c8y/ngx-components/api';
|
|
3
|
+
import { Observable } from 'rxjs';
|
|
4
|
+
import { generateResponse, handleRequest } from '../utils/common';
|
|
5
|
+
import { getFakeMeasurement } from '../utils/generators/measurement';
|
|
6
|
+
import { generateFakeSeriesValues as getFakeSeriesData } from '../utils/generators/series';
|
|
7
|
+
|
|
8
|
+
export class MeasurementsSeriesInterceptor implements HttpInterceptor {
|
|
9
|
+
private headers: string;
|
|
10
|
+
|
|
11
|
+
intercept(req: ApiCall, next: HttpHandler): Observable<IFetchResponse> {
|
|
12
|
+
this.headers = req.options.headers;
|
|
13
|
+
|
|
14
|
+
return handleRequest(req, next, 'measurement/measurements', {
|
|
15
|
+
POST: this.mockPOST.bind(this),
|
|
16
|
+
PUT: this.mockPUT.bind(this),
|
|
17
|
+
GET: this.mockGET.bind(this)
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
private mockPOST(_requestDescriptor) {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
private mockPUT(_requestDescriptor) {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
private async mockGET(
|
|
30
|
+
requestDescriptor: string
|
|
31
|
+
): Promise<Response | { measurements: IMeasurement[] }> {
|
|
32
|
+
if (requestDescriptor.includes('series')) {
|
|
33
|
+
return this.generateSeriesResponse();
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (requestDescriptor.includes('c8y_Battery') && requestDescriptor.includes('Battery')) {
|
|
37
|
+
const sourceValue = this.getSourceValue(requestDescriptor);
|
|
38
|
+
|
|
39
|
+
return this.handleMeasurementRequest(sourceValue);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
private generateSeriesResponse(): Response {
|
|
44
|
+
return generateResponse(() => getFakeSeriesData('%', 'Battery', 'c8y_Battery'));
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
private handleMeasurementRequest(sourceValue: string): Response {
|
|
48
|
+
const contentType = this.headers['accept'];
|
|
49
|
+
|
|
50
|
+
if (contentType === 'text/csv' || contentType === 'application/vnd.ms-excel') {
|
|
51
|
+
// in case of excel, a simplified version of a file will be generated (csv with excel extension)
|
|
52
|
+
return this.generateFakeFileResponse(contentType, sourceValue);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return generateResponse(() => this.getFakedMeasurementData('Battery', 'c8y_Battery'));
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
private generateFakeFileResponse(contentType: string, sourceValue: string): Response {
|
|
59
|
+
return this.generateResponseWithFakeFile(
|
|
60
|
+
() =>
|
|
61
|
+
this.getFakeRawFile(
|
|
62
|
+
sourceValue ? sourceValue : '123',
|
|
63
|
+
'Some_fake_device_name',
|
|
64
|
+
'c8y_Battery.Battery',
|
|
65
|
+
Math.random() * 100,
|
|
66
|
+
'%'
|
|
67
|
+
),
|
|
68
|
+
contentType
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
private getFakeRawFile(
|
|
73
|
+
source: string,
|
|
74
|
+
deviceName: string,
|
|
75
|
+
fragmentSeries: string,
|
|
76
|
+
value: number,
|
|
77
|
+
unit: string
|
|
78
|
+
): string {
|
|
79
|
+
const currentDate = new Date().toISOString();
|
|
80
|
+
return `time,source,device_name,fragment.series,value,unit
|
|
81
|
+
${currentDate},${source},${deviceName},${fragmentSeries},${value},${unit}`;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
private generateResponseWithFakeFile(bodyGenerator: () => string, contentType: string): Response {
|
|
85
|
+
const response = bodyGenerator();
|
|
86
|
+
|
|
87
|
+
return new Response(response, {
|
|
88
|
+
status: 200,
|
|
89
|
+
headers: {
|
|
90
|
+
'Content-Type': contentType
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
private getFakedMeasurementData(
|
|
96
|
+
fragment: string,
|
|
97
|
+
type: string
|
|
98
|
+
): { measurements: IMeasurement[] } {
|
|
99
|
+
return {
|
|
100
|
+
measurements: [getFakeMeasurement(fragment, type)]
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
private getSourceValue(params: string) {
|
|
105
|
+
const match = params.match(/"source":"(\d+)"/);
|
|
106
|
+
if (match) {
|
|
107
|
+
return match[1];
|
|
108
|
+
} else {
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
@@ -1,22 +1,27 @@
|
|
|
1
1
|
import { IMeasurement, IMeasurementValue } from '@c8y/client';
|
|
2
2
|
import { generateId, getFakeSource } from '../common';
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
const MEASUREMENT_UNITS: Record<string, string> = {
|
|
5
|
+
c8y_Temperature: 'ºC',
|
|
6
|
+
c8y_Battery: '%'
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export function getFakeMeasurement(fragment = 'T', type = 'c8y_Temperature'): IMeasurement {
|
|
5
10
|
return {
|
|
6
11
|
id: generateId(),
|
|
7
|
-
type:
|
|
12
|
+
type: type,
|
|
8
13
|
time: new Date().toISOString(),
|
|
9
14
|
self: 'https://example.com/measurement/measurements/...',
|
|
10
15
|
source: getFakeSource(),
|
|
11
|
-
|
|
12
|
-
|
|
16
|
+
[type]: {
|
|
17
|
+
[fragment]: getFakeMeasurementValue(type)
|
|
13
18
|
}
|
|
14
19
|
};
|
|
15
20
|
}
|
|
16
21
|
|
|
17
|
-
function getFakeMeasurementValue(): IMeasurementValue {
|
|
22
|
+
function getFakeMeasurementValue(type: string): IMeasurementValue {
|
|
18
23
|
return {
|
|
19
24
|
value: Math.random() * 100,
|
|
20
|
-
unit:
|
|
25
|
+
unit: MEASUREMENT_UNITS[type] || ''
|
|
21
26
|
};
|
|
22
27
|
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { ISeries } from '@c8y/client';
|
|
2
|
+
|
|
3
|
+
function createSeriesObject(
|
|
4
|
+
timestamp: string,
|
|
5
|
+
minValue: number,
|
|
6
|
+
maxValue: number,
|
|
7
|
+
unit: string,
|
|
8
|
+
name: string,
|
|
9
|
+
type: string
|
|
10
|
+
): ISeries {
|
|
11
|
+
return {
|
|
12
|
+
values: {
|
|
13
|
+
[timestamp]: [
|
|
14
|
+
{
|
|
15
|
+
min: minValue,
|
|
16
|
+
max: maxValue
|
|
17
|
+
}
|
|
18
|
+
]
|
|
19
|
+
},
|
|
20
|
+
series: [
|
|
21
|
+
{
|
|
22
|
+
unit: unit,
|
|
23
|
+
name: name,
|
|
24
|
+
type: type
|
|
25
|
+
}
|
|
26
|
+
],
|
|
27
|
+
truncated: false
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function generateFakeSeriesValues(
|
|
32
|
+
unit = '\u00baC',
|
|
33
|
+
name = 'T',
|
|
34
|
+
type = 'c8y_Temperature'
|
|
35
|
+
): ISeries {
|
|
36
|
+
const fakeTimestamp = new Date().toISOString();
|
|
37
|
+
const fakeMinValue = parseFloat((Math.random() * 10).toFixed(10));
|
|
38
|
+
const fakeMaxValue = fakeMinValue + parseFloat((Math.random() * 5).toFixed(10));
|
|
39
|
+
|
|
40
|
+
return createSeriesObject(fakeTimestamp, fakeMinValue, fakeMaxValue, unit, name, type);
|
|
41
|
+
}
|
package/src/selector/data-points-export-selector-example/datapoints-export-selector.component.ts
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { CommonModule } from '@angular/common';
|
|
2
|
+
import { Component } from '@angular/core';
|
|
3
|
+
import { CoreModule } from '@c8y/ngx-components';
|
|
4
|
+
import { DatapointSelectorModule, KPIDetails } from '@c8y/ngx-components/datapoint-selector';
|
|
5
|
+
import {
|
|
6
|
+
DatapointDetails,
|
|
7
|
+
DatapointsExportSelectorComponent,
|
|
8
|
+
ExportConfig
|
|
9
|
+
} from '@c8y/ngx-components/datapoints-export-selector';
|
|
10
|
+
|
|
11
|
+
@Component({
|
|
12
|
+
selector: 'tut-datapoints-export-selector-example',
|
|
13
|
+
template: `
|
|
14
|
+
<c8y-title>Data points export selector</c8y-title>
|
|
15
|
+
<div class="container-fluid p-t-24">
|
|
16
|
+
<div class="row">
|
|
17
|
+
<c8y-datapoints-export-selector [exportConfig]="config"></c8y-datapoints-export-selector>
|
|
18
|
+
</div>
|
|
19
|
+
<div class="row">
|
|
20
|
+
<div class="col-xs-12 col-sm-8 col-md-9">
|
|
21
|
+
<div class="card">
|
|
22
|
+
<div class="card-block d-flex d-col p-0" style="height: 300px">
|
|
23
|
+
<c8y-datapoint-selector
|
|
24
|
+
class="d-contents"
|
|
25
|
+
[allowDatapointsFromMultipleAssets]="false"
|
|
26
|
+
[(ngModel)]="datapoints"
|
|
27
|
+
(ngModelChange)="updateExportConfig()"
|
|
28
|
+
>
|
|
29
|
+
</c8y-datapoint-selector>
|
|
30
|
+
</div>
|
|
31
|
+
</div>
|
|
32
|
+
</div>
|
|
33
|
+
</div>
|
|
34
|
+
</div>
|
|
35
|
+
`,
|
|
36
|
+
standalone: true,
|
|
37
|
+
imports: [CommonModule, CoreModule, DatapointsExportSelectorComponent, DatapointSelectorModule]
|
|
38
|
+
})
|
|
39
|
+
export class DatapointsExportSelectorExampleComponent {
|
|
40
|
+
config: ExportConfig;
|
|
41
|
+
datapoint: DatapointDetails;
|
|
42
|
+
datapointDetails: DatapointDetails[];
|
|
43
|
+
datapoints: KPIDetails[] = [];
|
|
44
|
+
|
|
45
|
+
updateExportConfig(): void {
|
|
46
|
+
if (this.datapoints.length === 0) {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
this.datapointDetails = this.datapoints.map(datapoint => ({
|
|
51
|
+
deviceName: datapoint.__target.name,
|
|
52
|
+
source: datapoint.__target.id,
|
|
53
|
+
valueFragmentSeries: datapoint.series,
|
|
54
|
+
valueFragmentType: datapoint.fragment
|
|
55
|
+
}));
|
|
56
|
+
|
|
57
|
+
const { dateFrom, dateTo } = this.getDateRange();
|
|
58
|
+
|
|
59
|
+
this.config = {
|
|
60
|
+
datapointDetails: this.datapointDetails,
|
|
61
|
+
dateFrom,
|
|
62
|
+
dateTo
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
private getDateRange(): { dateFrom: string; dateTo: string } {
|
|
67
|
+
const baseDate = Date.now();
|
|
68
|
+
const dateFrom = new Date(baseDate).toISOString();
|
|
69
|
+
const dateTo = new Date(baseDate + 1).toISOString();
|
|
70
|
+
|
|
71
|
+
return { dateFrom, dateTo };
|
|
72
|
+
}
|
|
73
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { NgModule } from '@angular/core';
|
|
2
|
+
import { NavigatorNode, hookNavigator, hookRoute } from '@c8y/ngx-components';
|
|
3
|
+
|
|
4
|
+
@NgModule({
|
|
5
|
+
providers: [
|
|
6
|
+
hookRoute({
|
|
7
|
+
path: 'selector/datapoints-export-selector-example',
|
|
8
|
+
loadComponent: () =>
|
|
9
|
+
import('./datapoints-export-selector.component').then(
|
|
10
|
+
m => m.DatapointsExportSelectorExampleComponent
|
|
11
|
+
)
|
|
12
|
+
}),
|
|
13
|
+
hookNavigator(
|
|
14
|
+
new NavigatorNode({
|
|
15
|
+
label: 'Data points export selector example',
|
|
16
|
+
path: '/selector/datapoints-export-selector-example',
|
|
17
|
+
icon: 'th-list',
|
|
18
|
+
priority: 15
|
|
19
|
+
})
|
|
20
|
+
)
|
|
21
|
+
]
|
|
22
|
+
})
|
|
23
|
+
export class DatapointsExportSelectorExampleModule {}
|