Skip to content
This repository was archived by the owner on Jul 30, 2025. It is now read-only.

Commit 6bfb255

Browse files
myan9starpit
authored andcommitted
feat(packages/core): support updating Table from push notifications
Fixes #3295
1 parent 2e1b2f8 commit 6bfb255

File tree

10 files changed

+514
-270
lines changed

10 files changed

+514
-270
lines changed

clients/test/plugins/plugin-test/src/lib/cmds/table.ts

Lines changed: 70 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,16 +20,84 @@
2020
*/
2121

2222
// Notes: this is part of the Kui core API
23-
import { Commands } from '@kui-shell/core'
23+
import { Commands, Tables } from '@kui-shell/core'
24+
import { Watchable } from '@kui-shell/core/api/models'
2425

2526
import tableContent from './content/table-with-drilldown'
2627

28+
interface Options extends Commands.ParsedOptions {
29+
watch: 'push' | 'poll'
30+
'final-state': string
31+
}
32+
33+
// generateNewPush generates new `Row` with badge and message
34+
const generateNewPush = (name: string, onlineLike: boolean, message: string): Tables.Row => {
35+
const status = onlineLike ? 'Running' : 'Terminating'
36+
const css = onlineLike ? 'green-background' : 'yellow-background'
37+
return { name, attributes: [{ key: 'STATUS', tag: 'badge', value: status, css }, { key: 'MESSAGE', value: message }] }
38+
}
39+
40+
const doTable = (): ((args: Commands.Arguments<Options>) => Tables.Table & Partial<Watchable>) => {
41+
return (args: Commands.Arguments<Options>) => {
42+
const table = tableContent()
43+
const emptyRows: Tables.Row[] = []
44+
45+
const tableWithoutRows: Tables.Table = {
46+
noSort: true,
47+
header: { name: 'name', attributes: [{ value: 'status' }, { value: 'message' }] },
48+
body: emptyRows
49+
}
50+
51+
const watch = args.parsedOptions.watch
52+
const finalState = args.parsedOptions['final-state']
53+
54+
if (watch && watch === 'push' && finalState) {
55+
const watch: Watchable = {
56+
watch: {
57+
init: (update, offline) => {
58+
const name1 = 'foo1'
59+
const name2 = 'foo2'
60+
61+
update(generateNewPush(name1, true, 'should create a new row'))
62+
if (finalState === 'createRow1') return
63+
64+
setTimeout(() => update(generateNewPush(name1, false, 'should terminate the row')), 1000)
65+
if (finalState === 'terminateRow1') return
66+
67+
setTimeout(() => offline(name1), 1500)
68+
if (finalState === 'deleteRow1') return
69+
70+
setTimeout(() => update(generateNewPush(name1, true, 'should activate the deleted row')), 2000)
71+
if (finalState === 'activateRow1') return
72+
73+
setTimeout(() => update(generateNewPush(name1, false, 'should terminate the row again')), 2500)
74+
if (finalState === 'terminateRow1Again') return
75+
76+
setTimeout(() => offline(name1), 3000)
77+
if (finalState === 'deleteRow1Again') return
78+
79+
setTimeout(() => update(generateNewPush(name2, true, 'should create the second row')), 3500)
80+
if (finalState === 'createRow2') return
81+
82+
if (finalState === 'activeRow1Again')
83+
setTimeout(() => update(generateNewPush(name1, true, 'should activate the first row again')), 4000)
84+
}
85+
}
86+
}
87+
88+
return Object.assign({}, tableWithoutRows, watch)
89+
} else {
90+
return table
91+
}
92+
}
93+
}
94+
2795
/**
2896
* Here is where we register our command.
2997
*
3098
*/
3199
export default (commandTree: Commands.Registrar) => {
32-
commandTree.listen('/test/table', () => tableContent(), {
100+
commandTree.listen('/test/table', doTable(), {
33101
usage: {
34102
docs: 'A showcase of the Table view'
35103
}

clients/test/plugins/plugin-test/src/test/response/table.ts

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,3 +44,74 @@ test.drilldownFromREPL({
4444
header: expectHeaderText,
4545
body: expectRow
4646
})
47+
48+
const testPush = new TestTable({
49+
command: 'test table --watch=push'
50+
})
51+
52+
testPush.statusBadge([
53+
{
54+
testName: 'should create a new row',
55+
command: 'test table --watch=push --final-state=createRow1',
56+
expectRow: [
57+
{ name: 'foo1', badgeCss: 'green-background', badgeText: 'Running', message: 'should create a new row' }
58+
]
59+
},
60+
{
61+
testName: 'should terminate the row',
62+
command: 'test table --watch=push --final-state=terminateRow1',
63+
expectRow: [
64+
{ name: 'foo1', badgeCss: 'yellow-background', badgeText: 'Terminating', message: 'should terminate the row' }
65+
]
66+
},
67+
{
68+
testName: 'should delete the row',
69+
command: 'test table --watch=push --final-state=deleteRow1',
70+
expectRow: [{ name: 'foo1', badgeCss: 'red-background', badgeText: 'Offline', message: 'should terminate the row' }]
71+
},
72+
{
73+
testName: 'should activate the deleted row',
74+
command: 'test table --watch=push --final-state=activateRow1',
75+
expectRow: [
76+
{ name: 'foo1', badgeCss: 'green-background', badgeText: 'Running', message: 'should activate the deleted row' }
77+
]
78+
},
79+
{
80+
testName: 'should terminate the row again',
81+
command: 'test table --watch=push --final-state=terminateRow1Again',
82+
expectRow: [
83+
{
84+
name: 'foo1',
85+
badgeCss: 'yellow-background',
86+
badgeText: 'Terminating',
87+
message: 'should terminate the row again'
88+
}
89+
]
90+
},
91+
{
92+
testName: 'should delete the row again',
93+
command: 'test table --watch=push --final-state=deleteRow1Again',
94+
expectRow: [
95+
{ name: 'foo1', badgeCss: 'red-background', badgeText: 'Offline', message: 'should terminate the row again' }
96+
]
97+
},
98+
{
99+
testName: 'should create the second row',
100+
command: 'test table --watch=push --final-state=createRow2',
101+
expectRow: [
102+
{ name: 'foo2', badgeCss: 'green-background', badgeText: 'Running', message: 'should create the second row' }
103+
]
104+
},
105+
{
106+
testName: 'should activate the first row again',
107+
command: 'test table --watch=push --final-state=activeRow1Again',
108+
expectRow: [
109+
{
110+
name: 'foo1',
111+
badgeCss: 'green-background',
112+
badgeText: 'Running',
113+
message: 'should activate the first row again'
114+
}
115+
]
116+
}
117+
])

packages/core/src/api/models.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,4 +53,6 @@ export namespace Models {
5353
// see https://github.com/IBM/kui/issues/3222
5454
export { MetadataBearing as ResourceWithMetadata } from '../models/entity'
5555

56+
export { Watchable } from '../webapp/models/watch'
57+
5658
export default Models

packages/core/src/webapp/models/basicModels.ts

Lines changed: 0 additions & 27 deletions
This file was deleted.

packages/core/src/webapp/models/table.ts

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616

1717
/* eslint-disable @typescript-eslint/explicit-member-accessibility */
1818

19-
import { Watchable } from './basicModels'
19+
import { Watchable, Poller } from './watch'
2020
import { MetadataBearing, Entity } from '../../models/entity'
2121
import { SidecarMode } from '../bottom-stripe'
2222

@@ -159,8 +159,6 @@ export class Table<RowType extends Row = Row> {
159159
}
160160
}
161161

162-
export interface WatchableTable extends Table, Watchable {}
163-
164162
export function isTable<C>(model: SidecarMode | MetadataBearing<C> | Entity): model is Table {
165163
return (
166164
model !== undefined && (model instanceof Table || ((model as Table).body && Array.isArray((model as Table).body)))
@@ -183,7 +181,8 @@ export function isMultiTable<C>(model: SidecarMode | MetadataBearing<C> | Entity
183181

184182
export type WatchableMultiTable = MultiTable & Watchable
185183

186-
export function formatWatchableTable<T extends Table | MultiTable>(model: T, watch: Watchable): T & Watchable {
184+
export function formatWatchableTable<T extends Table | MultiTable>(model: T, poller: Poller): T & Watchable {
185+
const watch: Watchable = { watch: poller }
187186
if (isTable(model) || isMultiTable(model)) {
188187
return Object.assign(model, watch)
189188
} else {
@@ -236,7 +235,7 @@ interface RowDeletion {
236235
deleteIndex: number
237236
}
238237

239-
export interface RowDiff {
238+
interface RowDiff {
240239
rowUpdate: RowUpdate[]
241240
rowDeletion: RowDeletion[]
242241
rowInsertion: RowInsertion[]
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
/*
2+
* Copyright 2019 IBM Corporation
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import { Entity } from '../../models/entity'
18+
import { Row } from '../models/table'
19+
20+
export interface Watchable {
21+
watch: Poller | Pusher
22+
}
23+
24+
export type Poller = Partial<Toggleable> & {
25+
refreshCommand: string
26+
watchInterval?: number
27+
watchLimit?: number
28+
}
29+
30+
/** NOTE: Toggleable is not implemented */
31+
interface Toggleable {
32+
watchByDefault: boolean
33+
}
34+
35+
export interface Pusher {
36+
/**
37+
* Contract: the table renderer will call this function when the DOM
38+
* is ready to accept updates. when you have updates, please call
39+
* one or the other of the provided functions
40+
*/
41+
init: (update: WatchedRowHasUpdate, offline: WactchedRowisOffline) => void
42+
}
43+
44+
/** callbacks to indicate state changes */
45+
type WatchedRowHasUpdate = (response: Row) => void
46+
type WactchedRowisOffline = (rowKey: string) => void
47+
48+
function isPoller(model: Poller | Pusher): model is Poller {
49+
return (model as Poller).refreshCommand !== undefined
50+
}
51+
52+
export function isPusher(model: Poller | Pusher): model is Pusher {
53+
return (model as Pusher).init !== undefined
54+
}
55+
56+
export function isWatchable(model: Entity & Partial<Watchable>): model is Entity & Watchable {
57+
return model && model.watch && (isPoller(model.watch) || isPusher(model.watch))
58+
}

packages/core/src/webapp/print.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ import presentAs from './views/sidecar-present'
3333
import { isHTML } from '../util/types'
3434
import { promiseEach } from '../util/async'
3535

36-
import { isWatchable } from './models/basicModels'
36+
import { isWatchable } from './models/watch'
3737
import { Streamable, Stream } from '../models/streamable'
3838
import { CommandHandlerWithEvents, ExecType, KResponse, ParsedOptions } from '../models/command'
3939
import { Table, isTable, isMultiTable } from './models/table'

packages/core/src/webapp/views/diffTable.ts

Lines changed: 0 additions & 80 deletions
This file was deleted.

0 commit comments

Comments
 (0)