Skip to content

Commit ff642d9

Browse files
committed
feat(trigger): support registration and deletion triggers in dashboard
Signed-off-by: Alioune Gaye <alioune.gaye@secomind.com>
1 parent 2513277 commit ff642d9

File tree

10 files changed

+221
-20
lines changed

10 files changed

+221
-20
lines changed

cypress/e2e/trigger_builder.cy.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
const triggerConditionToLabel = {
22
device_connected: 'Device Connected',
33
device_disconnected: 'Device Disconnected',
4+
device_registered: 'Device Registered',
5+
device_deletion_finished: 'Device Deletion Finished',
6+
device_deletion_started: 'Device Deletion Started',
47
device_error: 'Device Error',
58
device_empty_cache_received: 'Empty Cache Received',
69
incoming_data: 'Incoming Data',

src/DeviceStatusPage/DeviceLiveEventsCard.tsx

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@ import AstarteClient, {
2424
AstarteDeviceEvent,
2525
AstarteDeviceConnectedEvent,
2626
AstarteDeviceDisconnectedEvent,
27+
AstarteDeviceRegistrationEvent,
28+
AstarteDeviceDeletionFinishedEvent,
29+
AstarteDeviceDeletionStartedEvent,
2730
AstarteDeviceErrorEvent,
2831
AstarteDeviceIncomingDataEvent,
2932
AstarteDeviceUnsetPropertyEvent,
@@ -234,6 +237,39 @@ const astarteDeviceEventBody = (event: AstarteDeviceEvent) => {
234237
</>
235238
);
236239
}
240+
if (event instanceof AstarteDeviceRegistrationEvent) {
241+
return (
242+
<>
243+
<Badge bg="success" className="me-2">
244+
device registered
245+
</Badge>
246+
<span>Device registered</span>
247+
</>
248+
);
249+
}
250+
251+
if (event instanceof AstarteDeviceDeletionStartedEvent) {
252+
return (
253+
<>
254+
<Badge bg="success" className="me-2">
255+
device deletion started
256+
</Badge>
257+
<span>Started Device deletion</span>
258+
</>
259+
);
260+
}
261+
262+
if (event instanceof AstarteDeviceDeletionFinishedEvent) {
263+
return (
264+
<>
265+
<Badge bg="success" className="me-2">
266+
device deletion finished
267+
</Badge>
268+
<span>Device deleted</span>
269+
</>
270+
);
271+
}
272+
237273
if (event instanceof AstarteDeviceIncomingDataEvent) {
238274
return (
239275
<>

src/astarte-client/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,9 @@ export {
4545
AstarteDeviceEvent,
4646
AstarteDeviceConnectedEvent,
4747
AstarteDeviceDisconnectedEvent,
48+
AstarteDeviceRegistrationEvent,
49+
AstarteDeviceDeletionFinishedEvent,
50+
AstarteDeviceDeletionStartedEvent,
4851
AstarteDeviceErrorEvent,
4952
AstarteDeviceIncomingDataEvent,
5053
AstarteDeviceUnsetPropertyEvent,

src/astarte-client/models/Trigger/index.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,14 @@ interface AstarteTriggerAMQPActionObject {
5151

5252
interface AstarteSimpleDeviceTriggerObject {
5353
type: 'device_trigger';
54-
on: 'device_disconnected' | 'device_connected' | 'device_error' | 'device_empty_cache_received';
54+
on:
55+
| 'device_disconnected'
56+
| 'device_connected'
57+
| 'device_registered'
58+
| 'device_deletion_finished'
59+
| 'device_deletion_started'
60+
| 'device_error'
61+
| 'device_empty_cache_received';
5562
deviceId?: string;
5663
groupName?: string;
5764
}
@@ -208,6 +215,9 @@ const astarteSimpleDeviceTriggerObjectSchema: yup.ObjectSchema<AstarteSimpleDevi
208215
.oneOf([
209216
'device_disconnected',
210217
'device_connected',
218+
'device_registered',
219+
'device_deletion_finished',
220+
'device_deletion_started',
211221
'device_error',
212222
'device_empty_cache_received',
213223
])

src/astarte-client/types/dto/trigger.d.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,14 @@
1919

2020
interface AstarteSimpleDeviceTriggerDTO {
2121
type: 'device_trigger';
22-
on: 'device_disconnected' | 'device_connected' | 'device_error' | 'device_empty_cache_received';
22+
on:
23+
| 'device_disconnected'
24+
| 'device_connected'
25+
| 'device_registered'
26+
| 'device_deletion_finished'
27+
| 'device_deletion_started'
28+
| 'device_error'
29+
| 'device_empty_cache_received';
2330
device_id?: string;
2431
group_name?: string;
2532
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import _ from 'lodash';
2+
import * as yup from 'yup';
3+
4+
import { AstarteDeviceEvent, AstarteDeviceEventDTO } from './AstarteDeviceEvent';
5+
6+
type AstarteDeviceDeletionStartedEventDTO = AstarteDeviceEventDTO & {
7+
event: {
8+
type: 'device_deletion_started';
9+
};
10+
};
11+
12+
const validationSchema: yup.ObjectSchema<AstarteDeviceDeletionStartedEventDTO['event']> = yup
13+
.object({
14+
type: yup.string().oneOf(['device_deletion_started']).required(),
15+
})
16+
.required();
17+
18+
export class AstarteDeviceDeletionStartedEvent extends AstarteDeviceEvent {
19+
private constructor(arg: unknown) {
20+
super(arg);
21+
validationSchema.validateSync(_.get(arg, 'event'));
22+
}
23+
24+
static fromJSON(arg: unknown): AstarteDeviceDeletionStartedEvent {
25+
return new AstarteDeviceDeletionStartedEvent(arg);
26+
}
27+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import _ from 'lodash';
2+
import * as yup from 'yup';
3+
4+
import { AstarteDeviceEvent, AstarteDeviceEventDTO } from './AstarteDeviceEvent';
5+
6+
type AstarteDeviceRegistrationEventDTO = AstarteDeviceEventDTO & {
7+
event: {
8+
type: 'device_registered';
9+
};
10+
};
11+
12+
const validationSchema: yup.ObjectSchema<AstarteDeviceRegistrationEventDTO['event']> = yup
13+
.object({
14+
type: yup.string().oneOf(['device_registered']).required(),
15+
})
16+
.required();
17+
18+
export class AstarteDeviceRegistrationEvent extends AstarteDeviceEvent {
19+
private constructor(arg: unknown) {
20+
super(arg);
21+
validationSchema.validateSync(_.get(arg, 'event'));
22+
}
23+
24+
static fromJSON(arg: unknown): AstarteDeviceRegistrationEvent {
25+
return new AstarteDeviceRegistrationEvent(arg);
26+
}
27+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import _ from 'lodash';
2+
import * as yup from 'yup';
3+
4+
import { AstarteDeviceEvent, AstarteDeviceEventDTO } from './AstarteDeviceEvent';
5+
6+
type AstarteDeviceDeletionFinishedEventDTO = AstarteDeviceEventDTO & {
7+
event: {
8+
type: 'device_deletion_finished';
9+
};
10+
};
11+
12+
const validationSchema: yup.ObjectSchema<AstarteDeviceDeletionFinishedEventDTO['event']> = yup
13+
.object({
14+
type: yup.string().oneOf(['device_deletion_finished']).required(),
15+
})
16+
.required();
17+
18+
export class AstarteDeviceDeletionFinishedEvent extends AstarteDeviceEvent {
19+
private constructor(arg: unknown) {
20+
super(arg);
21+
validationSchema.validateSync(_.get(arg, 'event'));
22+
}
23+
24+
static fromJSON(arg: unknown): AstarteDeviceDeletionFinishedEvent {
25+
return new AstarteDeviceDeletionFinishedEvent(arg);
26+
}
27+
}

src/astarte-client/types/events/index.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@ import { AstarteDeviceDisconnectedEvent } from './AstarteDeviceDisconnectedEvent
2222
import { AstarteDeviceErrorEvent } from './AstarteDeviceErrorEvent';
2323
import { AstarteDeviceIncomingDataEvent } from './AstarteDeviceIncomingDataEvent';
2424
import { AstarteDeviceUnsetPropertyEvent } from './AstarteDeviceUnsetPropertyEvent';
25+
import { AstarteDeviceRegistrationEvent } from './AstarteDeviceRegistrationEvent';
26+
import { AstarteDeviceDeletionFinishedEvent } from './AstateDeviceDeletionFinishedEvent';
27+
import { AstarteDeviceDeletionStartedEvent } from './AstarteDeviceDeletionStartedEvent';
2528

2629
function decodeEvent(arg: unknown): AstarteDeviceEvent | null {
2730
return decodeAnyOf(
@@ -31,6 +34,9 @@ function decodeEvent(arg: unknown): AstarteDeviceEvent | null {
3134
AstarteDeviceErrorEvent.fromJSON,
3235
AstarteDeviceUnsetPropertyEvent.fromJSON,
3336
AstarteDeviceIncomingDataEvent.fromJSON,
37+
AstarteDeviceRegistrationEvent.fromJSON,
38+
AstarteDeviceDeletionFinishedEvent.fromJSON,
39+
AstarteDeviceDeletionStartedEvent.fromJSON,
3440
],
3541
arg,
3642
);
@@ -56,6 +62,9 @@ export {
5662
AstarteDeviceEvent,
5763
AstarteDeviceConnectedEvent,
5864
AstarteDeviceDisconnectedEvent,
65+
AstarteDeviceRegistrationEvent,
66+
AstarteDeviceDeletionFinishedEvent,
67+
AstarteDeviceDeletionStartedEvent,
5968
AstarteDeviceErrorEvent,
6069
AstarteDeviceIncomingDataEvent,
6170
AstarteDeviceUnsetPropertyEvent,

src/components/TriggerEditor/SimpleTriggerForm.tsx

Lines changed: 70 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,9 @@ import _ from 'lodash';
3030
const triggerConditionToLabel = {
3131
device_disconnected: 'Device Disconnected',
3232
device_connected: 'Device Connected',
33+
device_registered: 'Device Registered',
34+
device_deletion_finished: 'Device Deletion Finished',
35+
device_deletion_started: 'Device Deletion Started',
3336
device_error: 'Device Error',
3437
device_empty_cache_received: 'Empty Cache Received',
3538
incoming_data: 'Incoming Data',
@@ -99,30 +102,39 @@ const SimpleTriggerForm = ({
99102
}: SimpleTriggerFormProps): React.ReactElement => {
100103
const endpointList =
101104
simpleTriggerInterface?.mappings.map((mapping: AstarteMapping) => mapping.endpoint) || [];
105+
102106
const isDeviceTrigger = _.get(simpleTrigger, 'type') === 'device_trigger';
103107
const isDataTrigger = _.get(simpleTrigger, 'type') === 'data_trigger';
104108
const hasTargetDevice = _.get(simpleTrigger, 'deviceId') != null;
105109
const hasTargetGroup = _.get(simpleTrigger, 'groupName') != null;
110+
106111
// eslint-disable-next-line no-nested-ternary
107112
const triggerTargetType = hasTargetDevice ? 'device' : hasTargetGroup ? 'group' : 'all_devices';
113+
108114
const triggerInterfaceName = _.get(simpleTrigger, 'interfaceName') as string | undefined;
109115
const hasSelectedInterface = triggerInterfaceName != null && triggerInterfaceName !== '*';
116+
110117
const triggerValueMatchOperator: AstarteSimpleDataTrigger['valueMatchOperator'] | undefined =
111118
_.get(simpleTrigger, 'valueMatchOperator');
119+
112120
const hasSelectedOperator =
113121
triggerValueMatchOperator != null && triggerValueMatchOperator !== '*';
122+
114123
const triggerMatchPath: AstarteSimpleDataTrigger['matchPath'] | undefined = _.get(
115124
simpleTrigger,
116125
'matchPath',
117126
);
127+
118128
const triggerInterfaceType = useMemo(
119129
() => (simpleTriggerInterface ? simpleTriggerInterface.type : null),
120130
[simpleTriggerInterface],
121131
);
132+
122133
const triggerInterfaceAggregation = useMemo(
123134
() => (simpleTriggerInterface ? simpleTriggerInterface.aggregation : null),
124135
[simpleTriggerInterface],
125136
);
137+
126138
const triggerInterfacePathType = useMemo(() => {
127139
if (!simpleTriggerInterface || !triggerMatchPath) {
128140
return null;
@@ -291,6 +303,9 @@ const SimpleTriggerForm = ({
291303
options = [
292304
'device_connected',
293305
'device_disconnected',
306+
'device_registered',
307+
'device_deletion_finished',
308+
'device_deletion_started',
294309
'device_error',
295310
'device_empty_cache_received',
296311
];
@@ -369,23 +384,68 @@ const SimpleTriggerForm = ({
369384
<Stack gap={3}>
370385
<Row>
371386
<Col sm={12}>
372-
<Form.Group controlId="triggerSimpleTriggerType">
373-
<Form.Label>Trigger type</Form.Label>
387+
<Form.Group controlId="triggerPath">
388+
<Form.Label>Path</Form.Label>
389+
<Form.Control
390+
type={hasNumericKnownValue ? 'number' : 'text'}
391+
autoComplete="off"
392+
required
393+
readOnly={isReadOnly || isLoadingInterfaceMajors || isLoadingInterface}
394+
value={_.get(simpleTrigger, 'matchPath') || ''}
395+
isInvalid={_.get(validationErrors, 'matchPath') != null}
396+
onChange={handleTriggerInterfacePathChange}
397+
list="endpoints-list"
398+
/>
399+
<Form.Control.Feedback type="invalid">
400+
{_.get(validationErrors, 'matchPath')}
401+
</Form.Control.Feedback>
402+
<datalist id="endpoints-list">
403+
{endpointList.map((endpoint, index) => (
404+
<option key={index} value={endpoint}>
405+
{endpoint}
406+
</option>
407+
))}
408+
</datalist>
409+
</Form.Group>
410+
</Col>
411+
</Row>
412+
<Row>
413+
<Col sm={4}>
414+
<Form.Group controlId="triggerOperator">
415+
<Form.Label>Operator</Form.Label>
374416
<Form.Select
375-
name="triggerSimpleTriggerType"
376-
disabled={isReadOnly}
377-
value={_.get(simpleTrigger, 'type')}
378-
onChange={handleTriggerTypeChange}
379-
isInvalid={_.get(validationErrors, 'type') != null}
417+
name="triggerOperator"
418+
disabled={isReadOnly || isLoadingInterfaceMajors || isLoadingInterface}
419+
value={triggerValueMatchOperator || '*'}
420+
onChange={handleTriggerInterfaceOperatorChange}
421+
isInvalid={_.get(validationErrors, 'valueMatchOperator') != null}
380422
>
381-
<option value="device_trigger">Device Trigger</option>
382-
<option value="data_trigger">Data Trigger</option>
423+
{renderTriggerOperatorOptions()}
383424
</Form.Select>
384425
<Form.Control.Feedback type="invalid">
385-
{_.get(validationErrors, 'type')}
426+
{_.get(validationErrors, 'valueMatchOperator')}
386427
</Form.Control.Feedback>
387428
</Form.Group>
388429
</Col>
430+
{hasSelectedOperator && (
431+
<Col sm={8}>
432+
<Form.Group controlId="triggerKnownValue">
433+
<Form.Label>Value</Form.Label>
434+
<Form.Control
435+
type="text"
436+
autoComplete="off"
437+
required
438+
readOnly={isReadOnly || isLoadingInterfaceMajors || isLoadingInterface}
439+
value={String(_.get(simpleTrigger, 'knownValue') ?? '')}
440+
onChange={handleTriggerInterfaceKnownValueChange}
441+
isInvalid={_.get(validationErrors, 'knownValue') != null}
442+
/>
443+
<Form.Control.Feedback type="invalid">
444+
{_.get(validationErrors, 'knownValue')}
445+
</Form.Control.Feedback>
446+
</Form.Group>
447+
</Col>
448+
)}
389449
</Row>
390450
<Row>
391451
<Col sm={hasTargetDevice || hasTargetGroup ? 4 : 12}>
@@ -515,18 +575,10 @@ const SimpleTriggerForm = ({
515575
value={_.get(simpleTrigger, 'matchPath') || ''}
516576
isInvalid={_.get(validationErrors, 'matchPath') != null}
517577
onChange={handleTriggerInterfacePathChange}
518-
list="endpoints-list"
519578
/>
520579
<Form.Control.Feedback type="invalid">
521580
{_.get(validationErrors, 'matchPath')}
522581
</Form.Control.Feedback>
523-
<datalist id="endpoints-list">
524-
{endpointList.map((endpoint, index) => (
525-
<option key={index} value={endpoint}>
526-
{endpoint}
527-
</option>
528-
))}
529-
</datalist>
530582
</Form.Group>
531583
</Col>
532584
</Row>

0 commit comments

Comments
 (0)