@c8y/tutorial 1021.25.5 → 1021.29.0

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.
@@ -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.25.5",
3
+ "version": "1021.29.0",
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.25.5",
7
- "@c8y/ngx-components": "1021.25.5",
8
- "@c8y/client": "1021.25.5",
9
- "@c8y/bootstrap": "1021.25.5",
6
+ "@c8y/style": "1021.29.0",
7
+ "@c8y/ngx-components": "1021.29.0",
8
+ "@c8y/client": "1021.29.0",
9
+ "@c8y/bootstrap": "1021.29.0",
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.25.5",
17
- "@c8y/devkit": "1021.25.5"
16
+ "@c8y/options": "1021.29.0",
17
+ "@c8y/devkit": "1021.29.0"
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
- export function getFakeMeasurement(): IMeasurement {
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: 'c8y_Temperature',
12
+ type: type,
8
13
  time: new Date().toISOString(),
9
14
  self: 'https://example.com/measurement/measurements/...',
10
15
  source: getFakeSource(),
11
- c8y_Temperature: {
12
- T: getFakeMeasurementValue()
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: `ºC`
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
+ }
@@ -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 {}