@oicl/openbridge-webcomponents-full-bundle 2.0.0-next.153 → 2.0.0-next.154

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.
@@ -0,0 +1,188 @@
1
+ import {describe, it, expect} from 'vitest';
2
+ import {
3
+ FilterModes,
4
+ ObcAlertListCellSlotsChangeEvent,
5
+ ObcAlertListDetailsExperimental,
6
+ alertListCellSlotName,
7
+ getAlertRows,
8
+ statusColumn,
9
+ } from './alert-list-details-experimental.js';
10
+ import {Alert, AlertType} from '../../types.js';
11
+
12
+ function alert(id: string, overrides: Partial<Alert> = {}): Alert {
13
+ return {
14
+ id,
15
+ tagId: id,
16
+ source: 'Source',
17
+ text: id,
18
+ acknowledged: false,
19
+ active: true,
20
+ type: AlertType.Warning,
21
+ time: new Date('2024-01-15T14:32:15Z'),
22
+ ...overrides,
23
+ } as Alert;
24
+ }
25
+
26
+ const rowIds = (alerts: Alert[], filterMode = FilterModes.ALL) =>
27
+ getAlertRows(alerts, filterMode).map((row) => row.rowId);
28
+
29
+ describe('getAlertRows', () => {
30
+ it('gives an ungrouped alert its encoded id as row id', () => {
31
+ const rows = getAlertRows([alert('a/b')], FilterModes.ALL);
32
+ expect(rows).toEqual([
33
+ {
34
+ rowId: 'a%2Fb',
35
+ parentRowId: undefined,
36
+ alert: expect.objectContaining({id: 'a/b'}),
37
+ level: 0,
38
+ expandable: false,
39
+ },
40
+ ]);
41
+ });
42
+
43
+ it('nests members under their group path', () => {
44
+ const rows = getAlertRows(
45
+ [
46
+ alert('gyro'),
47
+ alert('sensor', {memberOf: ['gyro']}),
48
+ alert('drift', {memberOf: ['sensor']}),
49
+ ],
50
+ FilterModes.ALL
51
+ );
52
+ expect(
53
+ rows.map(({rowId, level, expandable}) => [rowId, level, expandable])
54
+ ).toEqual([
55
+ ['gyro', 0, true],
56
+ ['gyro/sensor', 1, true],
57
+ ['gyro/sensor/drift', 2, false],
58
+ ]);
59
+ });
60
+
61
+ it('gives an alert in two groups one row under each', () => {
62
+ expect(
63
+ rowIds([
64
+ alert('gyro'),
65
+ alert('radar'),
66
+ alert('power', {memberOf: ['gyro', 'radar']}),
67
+ ])
68
+ ).toEqual(['gyro', 'gyro/power', 'radar', 'radar/power']);
69
+ });
70
+
71
+ it('makes a member a root when the filter mode hides its group', () => {
72
+ const alerts = [
73
+ alert('group', {active: {rectifiedTime: new Date()}}),
74
+ alert('member', {memberOf: ['group']}),
75
+ ];
76
+ expect(rowIds(alerts, FilterModes.ALL)).toEqual(['member']);
77
+ expect(rowIds(alerts, FilterModes.RECTIFIED)).toEqual(['group']);
78
+ });
79
+
80
+ it('recovers a membership cycle with no root', () => {
81
+ expect(
82
+ rowIds([
83
+ alert('pump-a', {memberOf: ['pump-b']}),
84
+ alert('pump-b', {memberOf: ['pump-a']}),
85
+ ])
86
+ ).toEqual(['pump-a', 'pump-a/pump-b']);
87
+ });
88
+ });
89
+
90
+ describe('cellSlots', () => {
91
+ it('lists every row of each slot column and fires only when it changes', async () => {
92
+ const el = document.createElement('obc-alert-list-details-experimental');
93
+ const details: ObcAlertListCellSlotsChangeEvent['detail'][] = [];
94
+ el.addEventListener('cell-slots-change', (event) =>
95
+ details.push((event as ObcAlertListCellSlotsChangeEvent).detail)
96
+ );
97
+ el.columns = [statusColumn(), {key: 'ack', label: 'ACK', slot: true}];
98
+ el.alerts = [
99
+ alert('gyro'),
100
+ alert('radar'),
101
+ alert('power', {memberOf: ['gyro', 'radar']}),
102
+ ];
103
+ document.body.append(el);
104
+ await el.updateComplete;
105
+
106
+ expect(el.cellSlots.map((slot) => slot.name)).toEqual([
107
+ 'cell-ack:gyro',
108
+ 'cell-ack:gyro/power',
109
+ 'cell-ack:radar',
110
+ 'cell-ack:radar/power',
111
+ ]);
112
+ expect(details).toEqual([el.cellSlots]);
113
+
114
+ el.defaultExpanded = false;
115
+ el.alerts = [...el.alerts];
116
+ await el.updateComplete;
117
+ expect(details).toHaveLength(1);
118
+
119
+ el.columns = [statusColumn()];
120
+ await el.updateComplete;
121
+ expect(details[details.length - 1]).toEqual([]);
122
+ el.remove();
123
+ });
124
+ });
125
+
126
+ describe('selectedRowId', () => {
127
+ const groupedAlerts = () => [
128
+ alert('gyro'),
129
+ alert('sensor', {memberOf: ['gyro']}),
130
+ alert('radar'),
131
+ alert('power', {memberOf: ['gyro', 'radar']}),
132
+ ];
133
+
134
+ async function selectedTableRowIds(
135
+ configure: (el: ObcAlertListDetailsExperimental) => void
136
+ ) {
137
+ const el = document.createElement('obc-alert-list-details-experimental');
138
+ el.alerts = groupedAlerts();
139
+ configure(el);
140
+ document.body.append(el);
141
+ await el.updateComplete;
142
+ const table = el.shadowRoot!.querySelector('obc-table')!;
143
+ const selected = table.data
144
+ .filter((row) => row.selected)
145
+ .map((row) => row.id);
146
+ el.remove();
147
+ return selected;
148
+ }
149
+
150
+ it('highlights only the selected row of an alert listed under two groups', async () => {
151
+ expect(
152
+ await selectedTableRowIds((el) => (el.selectedRowId = 'radar/power'))
153
+ ).toEqual(['radar/power']);
154
+ });
155
+
156
+ it('highlights the group row when a collapsed group hides the selected row', async () => {
157
+ expect(
158
+ await selectedTableRowIds((el) => {
159
+ el.defaultExpanded = false;
160
+ el.selectedRowId = 'gyro/sensor';
161
+ })
162
+ ).toEqual(['gyro']);
163
+ });
164
+
165
+ it('highlights nothing when no row has the selected id', async () => {
166
+ expect(
167
+ await selectedTableRowIds((el) => (el.selectedRowId = 'gyro/missing'))
168
+ ).toEqual([]);
169
+ });
170
+ });
171
+
172
+ describe('alertListCellSlotName', () => {
173
+ it('namespaces the column key and row id', () => {
174
+ expect(alertListCellSlotName('ack', 'gyro/power')).toBe(
175
+ 'cell-ack:gyro/power'
176
+ );
177
+ });
178
+
179
+ it('keeps names apart when a key or row id contains a dash or colon', () => {
180
+ const [ackRowId, ackARowId] = rowIds([alert('a-b'), alert('b')]);
181
+ expect(alertListCellSlotName('ack', ackRowId)).not.toBe(
182
+ alertListCellSlotName('ack-a', ackARowId)
183
+ );
184
+ expect(alertListCellSlotName('ack:a', 'b')).not.toBe(
185
+ alertListCellSlotName('ack', rowIds([alert('a:b')])[0])
186
+ );
187
+ });
188
+ });
@@ -1,9 +1,17 @@
1
1
  import type {Meta, StoryObj} from '@storybook/web-components-vite';
2
2
  import {
3
- AlertListMode,
4
- ObcAckClickEvent,
3
+ FilterModes,
4
+ ObcAlertListCellClickEvent,
5
5
  ObcAlertListDetailsExperimental,
6
+ ObcRowClickEvent,
7
+ ackColumn,
8
+ alertListCellSlotName,
9
+ getAlertRows,
10
+ statusColumn,
11
+ tagIdColumn,
12
+ timeColumn,
6
13
  } from './alert-list-details-experimental.js';
14
+ import type {ObcButton} from '../button/button.js';
7
15
  import '../alert-icon/alert-icon.js';
8
16
  import '../../icons/icon-alarm-unacknowledged-iec.js';
9
17
  import '../../icons/icon-warning-unacknowledged-iec.js';
@@ -11,13 +19,14 @@ import '../../icons/icon-caution-color-iec.js';
11
19
  import '../../icons/icon-alarm-acknowledged-iec.js';
12
20
 
13
21
  import {html} from 'lit';
14
- import {Alert, AlertType} from '../../types.js';
22
+ import {Alert, AlertType, isAcknowledged} from '../../types.js';
15
23
 
16
- // Handler for ack-click events, this is a demo solution for the storybook
17
- // Normally the ack-click is handled by the backend and the component is updated
18
- const handleAck = (e: ObcAckClickEvent) => {
19
- const item = e.detail.alert;
20
- ack(item);
24
+ // Handler for ACK clicks, this is a demo solution for the storybook
25
+ // Normally the click is handled by the backend and the component is updated
26
+ const handleAck = (e: ObcAlertListCellClickEvent) => {
27
+ if (e.detail.columnKey === 'ack') {
28
+ ack(e.detail.alert);
29
+ }
21
30
  };
22
31
 
23
32
  const ack = (item: Alert) => {
@@ -43,8 +52,14 @@ const meta: Meta<typeof ObcAlertListDetailsExperimental> = {
43
52
  tags: ['6.0', 'experimental'],
44
53
  component: 'obc-alert-list-details-experimental',
45
54
  args: {
46
- selectedMode: AlertListMode.ALL,
47
- showTime: true,
55
+ filterMode: FilterModes.ALL,
56
+ showHeader: true,
57
+ columns: [
58
+ statusColumn(),
59
+ ackColumn({dividerRight: true}),
60
+ timeColumn(),
61
+ tagIdColumn(),
62
+ ],
48
63
  alerts: [
49
64
  {
50
65
  id: '1',
@@ -129,14 +144,20 @@ const meta: Meta<typeof ObcAlertListDetailsExperimental> = {
129
144
  parameters: {
130
145
  layout: 'fullscreen',
131
146
  },
132
- argTypes: {},
147
+ argTypes: {
148
+ columns: {control: false},
149
+ filterMode: {
150
+ control: {type: 'select'},
151
+ options: Object.values(FilterModes),
152
+ },
153
+ },
133
154
  render: (args) => {
134
155
  return html` <obc-alert-list-details-experimental
135
156
  data-testid="alert-menu"
136
- .selectedMode=${args.selectedMode}
137
- .showTime=${args.showTime}
138
- .small=${args.small}
139
- @ack-click=${handleAck}
157
+ .filterMode=${args.filterMode}
158
+ .columns=${args.columns}
159
+ .showHeader=${args.showHeader}
160
+ @cell-click=${handleAck}
140
161
  .alerts=${args.alerts}
141
162
  style="height: 100vh; display: block; max-height: 100%;"
142
163
  >
@@ -153,7 +174,15 @@ export const Regular: Story = {
153
174
 
154
175
  export const Small: Story = {
155
176
  args: {
156
- small: true,
177
+ showHeader: false,
178
+ columns: [
179
+ statusColumn(),
180
+ timeColumn(),
181
+ ackColumn({
182
+ width:
183
+ 'var(--app-components-alert-table-row-ack-container-width-small)',
184
+ }),
185
+ ],
157
186
  },
158
187
  };
159
188
 
@@ -182,8 +211,8 @@ export const OneItem: Story = {
182
211
  },
183
212
  render: (args) => {
184
213
  return html` <obc-alert-list-details-experimental
185
- @ack-click=${handleAck}
186
- .selectedMode=${args.selectedMode}
214
+ @cell-click=${handleAck}
215
+ .filterMode=${args.filterMode}
187
216
  .alerts=${args.alerts}
188
217
  style="height: 100vh; display: block;"
189
218
  >
@@ -248,10 +277,10 @@ export const LevelCategories: Story = {
248
277
  },
249
278
  render: (args) => {
250
279
  return html` <obc-alert-list-details-experimental
251
- @ack-click=${handleAck}
252
- .selectedMode=${args.selectedMode}
280
+ @cell-click=${handleAck}
281
+ .filterMode=${args.filterMode}
253
282
  .alerts=${args.alerts}
254
- .showTime=${args.showTime}
283
+ .columns=${args.columns}
255
284
  style="height: 100vh; display: block;"
256
285
  >
257
286
  </obc-alert-list-details-experimental>`;
@@ -260,7 +289,6 @@ export const LevelCategories: Story = {
260
289
 
261
290
  export const GroupedAlerts: Story = {
262
291
  args: {
263
- showTime: true,
264
292
  alerts: [
265
293
  {
266
294
  id: 'gyro',
@@ -349,10 +377,44 @@ export const GroupedAlerts: Story = {
349
377
  },
350
378
  render: (args) => {
351
379
  return html` <obc-alert-list-details-experimental
352
- @ack-click=${handleAck}
353
- .selectedMode=${args.selectedMode}
380
+ @cell-click=${handleAck}
381
+ .filterMode=${args.filterMode}
354
382
  .alerts=${args.alerts}
355
- .showTime=${args.showTime}
383
+ .columns=${args.columns}
384
+ style="height: 100vh; display: block;"
385
+ >
386
+ </obc-alert-list-details-experimental>`;
387
+ },
388
+ };
389
+
390
+ export const SelectedRow: Story = {
391
+ args: {
392
+ alerts: GroupedAlerts.args?.alerts as Alert[],
393
+ selectedRowId: 'gyro/sensor',
394
+ },
395
+ parameters: {
396
+ docs: {
397
+ description: {
398
+ story:
399
+ 'The consumer owns the selection. Clicking a row sets `selectedRowId` to its row id, and clicking it again sets it back to `undefined`. Collapse the gyroscope group and the group row is highlighted instead of the hidden sensor row. `PWR-01` has a row under each group, and only the clicked one is highlighted.',
400
+ },
401
+ },
402
+ },
403
+ render: (args) => {
404
+ const toggleSelection = (event: ObcRowClickEvent) => {
405
+ const list = event.currentTarget as ObcAlertListDetailsExperimental;
406
+ list.selectedRowId =
407
+ list.selectedRowId === event.detail.rowId
408
+ ? undefined
409
+ : event.detail.rowId;
410
+ };
411
+ return html` <obc-alert-list-details-experimental
412
+ @cell-click=${handleAck}
413
+ @row-click=${toggleSelection}
414
+ .filterMode=${args.filterMode}
415
+ .alerts=${args.alerts}
416
+ .columns=${args.columns}
417
+ .selectedRowId=${args.selectedRowId}
356
418
  style="height: 100vh; display: block;"
357
419
  >
358
420
  </obc-alert-list-details-experimental>`;
@@ -361,7 +423,6 @@ export const GroupedAlerts: Story = {
361
423
 
362
424
  export const CyclicGrouping: Story = {
363
425
  args: {
364
- showTime: true,
365
426
  alerts: [
366
427
  {
367
428
  id: 'standalone',
@@ -407,10 +468,10 @@ export const CyclicGrouping: Story = {
407
468
  },
408
469
  render: (args) => {
409
470
  return html` <obc-alert-list-details-experimental
410
- @ack-click=${handleAck}
411
- .selectedMode=${args.selectedMode}
471
+ @cell-click=${handleAck}
472
+ .filterMode=${args.filterMode}
412
473
  .alerts=${args.alerts}
413
- .showTime=${args.showTime}
474
+ .columns=${args.columns}
414
475
  style="height: 100vh; display: block;"
415
476
  >
416
477
  </obc-alert-list-details-experimental>`;
@@ -419,7 +480,6 @@ export const CyclicGrouping: Story = {
419
480
 
420
481
  export const CycleWithDescendants: Story = {
421
482
  args: {
422
- showTime: true,
423
483
  alerts: [
424
484
  {
425
485
  id: 'reachable-group',
@@ -499,12 +559,61 @@ export const CycleWithDescendants: Story = {
499
559
  },
500
560
  render: (args) => {
501
561
  return html` <obc-alert-list-details-experimental
502
- @ack-click=${handleAck}
503
- .selectedMode=${args.selectedMode}
562
+ @cell-click=${handleAck}
563
+ .filterMode=${args.filterMode}
564
+ .alerts=${args.alerts}
565
+ .columns=${args.columns}
566
+ style="height: 100vh; display: block;"
567
+ >
568
+ </obc-alert-list-details-experimental>`;
569
+ },
570
+ };
571
+
572
+ export const SlottedAckButtons: Story = {
573
+ args: {
574
+ alerts: GroupedAlerts.args?.alerts as Alert[],
575
+ columns: [
576
+ statusColumn(),
577
+ {key: 'ack', label: 'ACK-status', slot: true, dividerRight: true},
578
+ timeColumn(),
579
+ tagIdColumn(),
580
+ ],
581
+ },
582
+ parameters: {
583
+ docs: {
584
+ description: {
585
+ story:
586
+ 'The ACK column is a slot column. The consumer creates each button, gives it its own `id`, and places it in slot `cell-ack:<rowId>`, with the row ids from `getAlertRows()`. `PWR-01` is a member of two groups, so it has two rows and two buttons. Clicking ACK looks up every button for that alert in the DOM and disables it.',
587
+ },
588
+ },
589
+ },
590
+ render: (args) => {
591
+ const disableAckButtons = (alert: Alert) => {
592
+ document
593
+ .querySelectorAll<ObcButton>(
594
+ `obc-button[data-alert-id="${CSS.escape(alert.id)}"]`
595
+ )
596
+ .forEach((button) => (button.disabled = true));
597
+ };
598
+ return html` <obc-alert-list-details-experimental
599
+ .filterMode=${args.filterMode}
504
600
  .alerts=${args.alerts}
505
- .showTime=${args.showTime}
601
+ .columns=${args.columns}
506
602
  style="height: 100vh; display: block;"
507
603
  >
604
+ ${getAlertRows(args.alerts, args.filterMode)
605
+ .filter(({alert}) => !isAcknowledged(alert) && !alert.noAck)
606
+ .map(
607
+ (row) =>
608
+ html`<obc-button
609
+ slot=${alertListCellSlotName('ack', row.rowId)}
610
+ id=${`ack-${row.rowId}`}
611
+ data-alert-id=${row.alert.id}
612
+ fullWidth
613
+ @click=${() => disableAckButtons(row.alert)}
614
+ >ACK</obc-button
615
+ >`
616
+ )}
508
617
  </obc-alert-list-details-experimental>`;
509
618
  },
510
619
  };