cozy-client 60.30.6 → 60.32.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.
- package/dist/CozyClient.js +1 -1
- package/dist/models/__fixtures__/io.cozy.banners.json +807 -0
- package/dist/models/banner.js +399 -0
- package/dist/models/banner.spec.js +381 -0
- package/dist/models/index.js +5 -1
- package/package.json +2 -2
- package/types/models/banner.d.ts +87 -0
- package/types/models/index.d.ts +2 -1
|
@@ -0,0 +1,381 @@
|
|
|
1
|
+
import {
|
|
2
|
+
BANNERS_DOCTYPE,
|
|
3
|
+
dismiss,
|
|
4
|
+
getActiveBanner,
|
|
5
|
+
getActiveBanners
|
|
6
|
+
} from './banner'
|
|
7
|
+
import vectors from './__fixtures__/io.cozy.banners.json'
|
|
8
|
+
|
|
9
|
+
/** A client that answers a collection read with the given documents. */
|
|
10
|
+
const clientListing = docs => ({
|
|
11
|
+
query: jest.fn().mockResolvedValue({ data: docs })
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* The fixtures are the contract vectors published with the doctype in
|
|
16
|
+
* cozy-doctypes. Every client implementing io.cozy.banners runs the same cases,
|
|
17
|
+
* so the implementations are checked against one specification rather than
|
|
18
|
+
* against each other.
|
|
19
|
+
*
|
|
20
|
+
* They run through getActiveBanners, the entry point a consumer calls, rather
|
|
21
|
+
* than against the internals: what matters is what an application ends up
|
|
22
|
+
* displaying.
|
|
23
|
+
*/
|
|
24
|
+
describe('io.cozy.banners contract vectors', () => {
|
|
25
|
+
const now = new Date(vectors.now)
|
|
26
|
+
|
|
27
|
+
it.each(vectors.cases.map(testCase => [testCase.name, testCase]))(
|
|
28
|
+
'%s',
|
|
29
|
+
async (name, testCase) => {
|
|
30
|
+
const banners = await getActiveBanners(clientListing(testCase.input), {
|
|
31
|
+
now
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
const rendered = banners.map(banner => ({
|
|
35
|
+
bannerId: banner.bannerId,
|
|
36
|
+
severity: banner.severity,
|
|
37
|
+
surface: banner.surface,
|
|
38
|
+
cta: Boolean(banner.cta),
|
|
39
|
+
secondaryCta: Boolean(banner.secondaryCta)
|
|
40
|
+
}))
|
|
41
|
+
|
|
42
|
+
expect(rendered).toEqual(testCase.expected)
|
|
43
|
+
}
|
|
44
|
+
)
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
describe('getActiveBanners', () => {
|
|
48
|
+
const stored = {
|
|
49
|
+
_id: 'abc',
|
|
50
|
+
bannerId: 'quota.almost-full',
|
|
51
|
+
category: 'quota',
|
|
52
|
+
severity: 'critical',
|
|
53
|
+
surface: 'inline',
|
|
54
|
+
text: 'hi',
|
|
55
|
+
lang: 'en',
|
|
56
|
+
cta: { label: 'Go', url: 'http://example.org' },
|
|
57
|
+
dismissible: true,
|
|
58
|
+
dismissedAt: null,
|
|
59
|
+
priority: 10,
|
|
60
|
+
startsAt: '2020-01-01T00:00:00Z',
|
|
61
|
+
endsAt: null,
|
|
62
|
+
cozyMetadata: { createdByApp: 'stack', doctypeVersion: '1' }
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
it('should return banners ready to render, with every fallback applied', async () => {
|
|
66
|
+
const banners = await getActiveBanners(clientListing([stored]))
|
|
67
|
+
|
|
68
|
+
expect(banners).toHaveLength(1)
|
|
69
|
+
// a severity or surface this client does not know is rendered with the
|
|
70
|
+
// fallback rather than dropped, so a new value needs no client release
|
|
71
|
+
expect(banners[0].severity).toBe('warning')
|
|
72
|
+
expect(banners[0].surface).toBe('banner')
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
it('should query the doctype, not a wrapper around it', async () => {
|
|
76
|
+
const client = clientListing([])
|
|
77
|
+
await getActiveBanners(client)
|
|
78
|
+
const [definition] = client.query.mock.calls[0]
|
|
79
|
+
expect(definition.doctype).toBe(BANNERS_DOCTYPE)
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
it('should cope with an empty or missing collection', async () => {
|
|
83
|
+
for (const data of [[], null, undefined]) {
|
|
84
|
+
await expect(getActiveBanners(clientListing(data))).resolves.toEqual([])
|
|
85
|
+
}
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
it('should not hide a failed read behind an empty list', async () => {
|
|
89
|
+
// client.query resolves undefined when the client carries an onError
|
|
90
|
+
// callback, which would otherwise read as "no banners apply"
|
|
91
|
+
const client = { query: jest.fn().mockResolvedValue(undefined) }
|
|
92
|
+
await expect(getActiveBanners(client)).rejects.toThrow(BANNERS_DOCTYPE)
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
it('should drop a call to action that is not an absolute https URL', async () => {
|
|
96
|
+
// the documents live in a database the applications can write, and a
|
|
97
|
+
// client renders this straight into an href
|
|
98
|
+
const urls = [
|
|
99
|
+
'javascript' + ':alert(document.cookie)',
|
|
100
|
+
'data:text/html,<script>alert(1)</script>',
|
|
101
|
+
'http://example.org/plans',
|
|
102
|
+
'/plans'
|
|
103
|
+
]
|
|
104
|
+
|
|
105
|
+
for (const url of urls) {
|
|
106
|
+
const [banner] = await getActiveBanners(
|
|
107
|
+
clientListing([{ ...stored, cta: { label: 'Go', url } }])
|
|
108
|
+
)
|
|
109
|
+
expect(banner.cta).toBeUndefined()
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const [kept] = await getActiveBanners(
|
|
113
|
+
clientListing([
|
|
114
|
+
{ ...stored, cta: { label: 'Go', url: 'https://example.org/plans' } }
|
|
115
|
+
])
|
|
116
|
+
)
|
|
117
|
+
expect(kept.cta.url).toBe('https://example.org/plans')
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
it('should hide a banner whose window bound is not the timestamp the doctype writes', async () => {
|
|
121
|
+
// Date.parse falls back to an engine specific parser for everything else,
|
|
122
|
+
// so the same document would be visible in one browser and hidden in
|
|
123
|
+
// another, and the contract vectors could no longer describe both
|
|
124
|
+
const bounds = ['2020-01-01', 'Jan 1 2020', '2020/01/01', 0, ['2020-01-01']]
|
|
125
|
+
|
|
126
|
+
for (const startsAt of bounds) {
|
|
127
|
+
await expect(
|
|
128
|
+
getActiveBanners(clientListing([{ ...stored, startsAt }]))
|
|
129
|
+
).resolves.toEqual([])
|
|
130
|
+
}
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
it('should rank a banner carrying no priority below one that does', async () => {
|
|
134
|
+
// b.priority - a.priority is NaN without this, and a NaN comparator makes
|
|
135
|
+
// sort keep insertion order and never reach the bannerId tiebreak
|
|
136
|
+
const ranked = { ...stored, _id: 'a', bannerId: 'a.ranked', priority: 50 }
|
|
137
|
+
const unranked = { ...stored, _id: 'b', bannerId: 'b.unranked' }
|
|
138
|
+
delete unranked.priority
|
|
139
|
+
|
|
140
|
+
const banners = await getActiveBanners(clientListing([unranked, ranked]))
|
|
141
|
+
|
|
142
|
+
expect(banners.map(banner => banner.bannerId)).toEqual([
|
|
143
|
+
'a.ranked',
|
|
144
|
+
'b.unranked'
|
|
145
|
+
])
|
|
146
|
+
})
|
|
147
|
+
})
|
|
148
|
+
|
|
149
|
+
describe('getActiveBanner', () => {
|
|
150
|
+
const base = {
|
|
151
|
+
bannerId: 'low',
|
|
152
|
+
severity: 'info',
|
|
153
|
+
surface: 'banner',
|
|
154
|
+
priority: 1,
|
|
155
|
+
dismissedAt: null,
|
|
156
|
+
startsAt: '2020-01-01T00:00:00Z',
|
|
157
|
+
endsAt: null,
|
|
158
|
+
cozyMetadata: { createdByApp: 'stack', doctypeVersion: '1' }
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
it('should return the highest priority banner', async () => {
|
|
162
|
+
const low = { ...base, _id: 'a' }
|
|
163
|
+
const high = { ...base, _id: 'b', bannerId: 'high', priority: 99 }
|
|
164
|
+
|
|
165
|
+
const banner = await getActiveBanner(clientListing([low, high]))
|
|
166
|
+
|
|
167
|
+
expect(banner.bannerId).toBe('high')
|
|
168
|
+
})
|
|
169
|
+
|
|
170
|
+
it('should return null when nothing applies', async () => {
|
|
171
|
+
await expect(getActiveBanner(clientListing([]))).resolves.toBeNull()
|
|
172
|
+
})
|
|
173
|
+
})
|
|
174
|
+
|
|
175
|
+
describe('dismiss', () => {
|
|
176
|
+
const stored = {
|
|
177
|
+
_id: 'abc',
|
|
178
|
+
_rev: '1-aaa',
|
|
179
|
+
bannerId: 'quota.almost-full',
|
|
180
|
+
severity: 'critical',
|
|
181
|
+
surface: 'inline',
|
|
182
|
+
cta: { label: 'Go', url: 'javascript:alert(1)' },
|
|
183
|
+
dismissedAt: null
|
|
184
|
+
}
|
|
185
|
+
const at = '2026-07-22T12:00:00Z'
|
|
186
|
+
|
|
187
|
+
// dismiss issues two shapes of query: the collection, to find every version
|
|
188
|
+
// of the bannerId, then each document by id.
|
|
189
|
+
const clientReturning = (doc, saveImpl) => ({
|
|
190
|
+
query: jest
|
|
191
|
+
.fn()
|
|
192
|
+
.mockImplementation(definition =>
|
|
193
|
+
Promise.resolve(
|
|
194
|
+
definition?.id ? { data: doc } : { data: doc ? [].concat(doc) : [] }
|
|
195
|
+
)
|
|
196
|
+
),
|
|
197
|
+
save: saveImpl ?? jest.fn().mockResolvedValue({ data: doc })
|
|
198
|
+
})
|
|
199
|
+
|
|
200
|
+
it('should record the timestamp on the stored document', async () => {
|
|
201
|
+
const client = clientReturning(stored)
|
|
202
|
+
|
|
203
|
+
await dismiss(client, stored, { dismissedAt: at })
|
|
204
|
+
|
|
205
|
+
expect(client.save).toHaveBeenCalledWith({
|
|
206
|
+
_type: BANNERS_DOCTYPE,
|
|
207
|
+
...stored,
|
|
208
|
+
dismissedAt: at
|
|
209
|
+
})
|
|
210
|
+
})
|
|
211
|
+
|
|
212
|
+
it('should write back what the stack stored, not what the caller renders', async () => {
|
|
213
|
+
// A caller holds a resolved banner, whose severity, surface and cta have
|
|
214
|
+
// had the client fallbacks applied. Persisting those would overwrite the
|
|
215
|
+
// values the stack wrote, and nothing server side prevents it.
|
|
216
|
+
const [resolved] = await getActiveBanners(
|
|
217
|
+
clientListing([
|
|
218
|
+
{
|
|
219
|
+
...stored,
|
|
220
|
+
category: 'quota',
|
|
221
|
+
text: 'hi',
|
|
222
|
+
lang: 'en',
|
|
223
|
+
dismissible: true,
|
|
224
|
+
priority: 1,
|
|
225
|
+
startsAt: '2020-01-01T00:00:00Z',
|
|
226
|
+
endsAt: null,
|
|
227
|
+
cozyMetadata: { createdByApp: 'stack', doctypeVersion: '1' }
|
|
228
|
+
}
|
|
229
|
+
])
|
|
230
|
+
)
|
|
231
|
+
const client = clientReturning(stored)
|
|
232
|
+
|
|
233
|
+
await dismiss(client, resolved, { dismissedAt: at })
|
|
234
|
+
|
|
235
|
+
const persisted = client.save.mock.calls[0][0]
|
|
236
|
+
expect(persisted.severity).toBe('critical')
|
|
237
|
+
expect(persisted.surface).toBe('inline')
|
|
238
|
+
expect(persisted.cta).toEqual(stored.cta)
|
|
239
|
+
})
|
|
240
|
+
|
|
241
|
+
it('should refuse a banner with no identifier', async () => {
|
|
242
|
+
const client = clientReturning(stored)
|
|
243
|
+
await expect(dismiss(client, { bannerId: 'x' })).rejects.toThrow('_id')
|
|
244
|
+
expect(client.save).not.toHaveBeenCalled()
|
|
245
|
+
})
|
|
246
|
+
|
|
247
|
+
it('should stop when the banner was already deleted', async () => {
|
|
248
|
+
for (const missing of [null, []]) {
|
|
249
|
+
const client = clientReturning(missing)
|
|
250
|
+
await expect(
|
|
251
|
+
dismiss(client, stored, { dismissedAt: at })
|
|
252
|
+
).resolves.toEqual({
|
|
253
|
+
data: null
|
|
254
|
+
})
|
|
255
|
+
expect(client.save).not.toHaveBeenCalled()
|
|
256
|
+
}
|
|
257
|
+
})
|
|
258
|
+
|
|
259
|
+
it('should not rewrite a dismissal another client already recorded', async () => {
|
|
260
|
+
const already = { ...stored, dismissedAt: '2026-07-20T08:00:00Z' }
|
|
261
|
+
const client = clientReturning(already)
|
|
262
|
+
|
|
263
|
+
await expect(dismiss(client, stored, { dismissedAt: at })).resolves.toEqual(
|
|
264
|
+
{
|
|
265
|
+
data: already
|
|
266
|
+
}
|
|
267
|
+
)
|
|
268
|
+
expect(client.save).not.toHaveBeenCalled()
|
|
269
|
+
})
|
|
270
|
+
|
|
271
|
+
it('should read the document back and write again on a conflict', async () => {
|
|
272
|
+
const conflict = Object.assign(new Error('Document update conflict'), {
|
|
273
|
+
status: 409
|
|
274
|
+
})
|
|
275
|
+
const fresh = { ...stored, _rev: '2-bbb' }
|
|
276
|
+
// The first read returns the revision that loses the race and the second
|
|
277
|
+
// the one that wins, so the assertion fails unless the document really was
|
|
278
|
+
// read again rather than the losing revision retried.
|
|
279
|
+
const client = {
|
|
280
|
+
query: jest
|
|
281
|
+
.fn()
|
|
282
|
+
// the read by id, then the re-read after the conflict
|
|
283
|
+
.mockResolvedValueOnce({ data: stored })
|
|
284
|
+
.mockResolvedValue({ data: fresh }),
|
|
285
|
+
save: jest
|
|
286
|
+
.fn()
|
|
287
|
+
.mockRejectedValueOnce(conflict)
|
|
288
|
+
.mockResolvedValue({})
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
await dismiss(client, stored, { dismissedAt: at })
|
|
292
|
+
|
|
293
|
+
// the read by id, then the re-read after the conflict
|
|
294
|
+
expect(client.query).toHaveBeenCalledTimes(2)
|
|
295
|
+
expect(client.save).toHaveBeenLastCalledWith({
|
|
296
|
+
...fresh,
|
|
297
|
+
_type: BANNERS_DOCTYPE,
|
|
298
|
+
dismissedAt: at
|
|
299
|
+
})
|
|
300
|
+
})
|
|
301
|
+
|
|
302
|
+
it('should give up rather than loop when the retry conflicts too', async () => {
|
|
303
|
+
const conflict = Object.assign(new Error('Document update conflict'), {
|
|
304
|
+
status: 409
|
|
305
|
+
})
|
|
306
|
+
const client = clientReturning(
|
|
307
|
+
stored,
|
|
308
|
+
jest.fn().mockRejectedValue(conflict)
|
|
309
|
+
)
|
|
310
|
+
|
|
311
|
+
await expect(dismiss(client, stored, { dismissedAt: at })).rejects.toThrow(
|
|
312
|
+
'Document update conflict'
|
|
313
|
+
)
|
|
314
|
+
expect(client.save).toHaveBeenCalledTimes(2)
|
|
315
|
+
})
|
|
316
|
+
|
|
317
|
+
it('should classify a conflict the pouch link rewrapped without a status', async () => {
|
|
318
|
+
// CozyPouchLink rebuilds the error and keeps only the message, so the
|
|
319
|
+
// status branch cannot be the one that recognises it.
|
|
320
|
+
const rewrapped = new Error(
|
|
321
|
+
'Coud not apply mutation: Document update conflict'
|
|
322
|
+
)
|
|
323
|
+
const client = clientReturning(
|
|
324
|
+
stored,
|
|
325
|
+
jest
|
|
326
|
+
.fn()
|
|
327
|
+
.mockRejectedValueOnce(rewrapped)
|
|
328
|
+
.mockResolvedValue({ data: stored })
|
|
329
|
+
)
|
|
330
|
+
|
|
331
|
+
await dismiss(client, stored, { dismissedAt: at })
|
|
332
|
+
|
|
333
|
+
expect(client.save).toHaveBeenCalledTimes(2)
|
|
334
|
+
})
|
|
335
|
+
|
|
336
|
+
it('should read the document the pouch link resolves as an array', async () => {
|
|
337
|
+
const client = clientReturning([stored])
|
|
338
|
+
|
|
339
|
+
await dismiss(client, stored, { dismissedAt: at })
|
|
340
|
+
|
|
341
|
+
expect(client.save).toHaveBeenCalledWith({
|
|
342
|
+
...stored,
|
|
343
|
+
_type: BANNERS_DOCTYPE,
|
|
344
|
+
dismissedAt: at
|
|
345
|
+
})
|
|
346
|
+
})
|
|
347
|
+
|
|
348
|
+
it('should write to the banners doctype whatever _type the document carries', async () => {
|
|
349
|
+
// Nothing server side stops an application from persisting a _type of its
|
|
350
|
+
// own on a stack authored banner, and the spread order decides where the
|
|
351
|
+
// write lands.
|
|
352
|
+
const poisoned = { ...stored, _type: 'io.cozy.settings' }
|
|
353
|
+
const client = clientReturning(poisoned)
|
|
354
|
+
|
|
355
|
+
await dismiss(client, poisoned, { dismissedAt: at })
|
|
356
|
+
|
|
357
|
+
expect(client.save.mock.calls[0][0]._type).toBe(BANNERS_DOCTYPE)
|
|
358
|
+
})
|
|
359
|
+
|
|
360
|
+
it('should not report a failed read as a deleted banner', async () => {
|
|
361
|
+
// client.query resolves undefined when the client swallows the rejection
|
|
362
|
+
// through its onError option. Reporting that as {data: null} would tell the
|
|
363
|
+
// caller the banner is gone while nothing was written.
|
|
364
|
+
const client = {
|
|
365
|
+
query: jest.fn().mockResolvedValue(undefined),
|
|
366
|
+
save: jest.fn()
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
await expect(dismiss(client, stored, { dismissedAt: at })).rejects.toThrow(
|
|
370
|
+
'resolved nothing'
|
|
371
|
+
)
|
|
372
|
+
expect(client.save).not.toHaveBeenCalled()
|
|
373
|
+
})
|
|
374
|
+
|
|
375
|
+
it('should not swallow any other error', async () => {
|
|
376
|
+
const boom = Object.assign(new Error('Server error'), { status: 500 })
|
|
377
|
+
const client = clientReturning(stored, jest.fn().mockRejectedValue(boom))
|
|
378
|
+
|
|
379
|
+
await expect(dismiss(client, stored)).rejects.toThrow('Server error')
|
|
380
|
+
})
|
|
381
|
+
})
|
package/dist/models/index.js
CHANGED
|
@@ -5,7 +5,7 @@ var _interopRequireWildcard = require("@babel/runtime/helpers/interopRequireWild
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", {
|
|
6
6
|
value: true
|
|
7
7
|
});
|
|
8
|
-
exports.assistant = exports.ai = exports.doctypes = exports.geo = exports.user = exports.paper = exports.dacc = exports.sharing = exports.timeseries = exports.document = exports.contact = exports.utils = exports.permission = exports.note = exports.account = exports.konnectorFolder = exports.folder = exports.file = exports.applications = exports.instance = exports.trigger = exports.accounts = exports.triggers = void 0;
|
|
8
|
+
exports.banner = exports.assistant = exports.ai = exports.doctypes = exports.geo = exports.user = exports.paper = exports.dacc = exports.sharing = exports.timeseries = exports.document = exports.contact = exports.utils = exports.permission = exports.note = exports.account = exports.konnectorFolder = exports.folder = exports.file = exports.applications = exports.instance = exports.trigger = exports.accounts = exports.triggers = void 0;
|
|
9
9
|
|
|
10
10
|
var trigger = _interopRequireWildcard(require("./trigger"));
|
|
11
11
|
|
|
@@ -90,6 +90,10 @@ exports.ai = ai;
|
|
|
90
90
|
var assistant = _interopRequireWildcard(require("./assistant"));
|
|
91
91
|
|
|
92
92
|
exports.assistant = assistant;
|
|
93
|
+
|
|
94
|
+
var banner = _interopRequireWildcard(require("./banner"));
|
|
95
|
+
|
|
96
|
+
exports.banner = banner;
|
|
93
97
|
// For backward compatibility before 9.0.0
|
|
94
98
|
var triggers = trigger;
|
|
95
99
|
exports.triggers = triggers;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cozy-client",
|
|
3
|
-
"version": "60.
|
|
3
|
+
"version": "60.32.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"main": "dist/index.node.js",
|
|
6
6
|
"browser": "dist/index.js",
|
|
@@ -74,5 +74,5 @@
|
|
|
74
74
|
"react-native-ios11-devicecheck": "^0.0.3"
|
|
75
75
|
},
|
|
76
76
|
"sideEffects": false,
|
|
77
|
-
"gitHead": "
|
|
77
|
+
"gitHead": "afc1cbe7a956b8abbb875e5436ed031f05207a24"
|
|
78
78
|
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
export const BANNERS_DOCTYPE: "io.cozy.banners";
|
|
2
|
+
export function getActiveBanners(client: object, { now }?: {
|
|
3
|
+
now: Date;
|
|
4
|
+
}): Promise<Banner[]>;
|
|
5
|
+
export function getActiveBanner(client: object, options?: {
|
|
6
|
+
now: Date;
|
|
7
|
+
}): Promise<Banner | null>;
|
|
8
|
+
export function dismiss(client: object, banner: Banner, { dismissedAt }?: {
|
|
9
|
+
dismissedAt: string;
|
|
10
|
+
}): Promise<{
|
|
11
|
+
data: Banner | null;
|
|
12
|
+
}>;
|
|
13
|
+
export type BannerCta = {
|
|
14
|
+
/**
|
|
15
|
+
* - The label, plain text
|
|
16
|
+
*/
|
|
17
|
+
label: string;
|
|
18
|
+
/**
|
|
19
|
+
* - Where the label points
|
|
20
|
+
*/
|
|
21
|
+
url: string;
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* An io.cozy.banners document
|
|
25
|
+
*/
|
|
26
|
+
export type Banner = {
|
|
27
|
+
/**
|
|
28
|
+
* - Identifier of the document
|
|
29
|
+
*/
|
|
30
|
+
_id?: string;
|
|
31
|
+
/**
|
|
32
|
+
* - Identifier of the banner itself
|
|
33
|
+
*/
|
|
34
|
+
bannerId: string;
|
|
35
|
+
/**
|
|
36
|
+
* - What the banner is about
|
|
37
|
+
*/
|
|
38
|
+
category: string;
|
|
39
|
+
/**
|
|
40
|
+
* - How loudly to render it
|
|
41
|
+
*/
|
|
42
|
+
severity: 'info' | 'warning' | 'error';
|
|
43
|
+
/**
|
|
44
|
+
* - Where to render it
|
|
45
|
+
*/
|
|
46
|
+
surface: 'banner' | 'modal';
|
|
47
|
+
/**
|
|
48
|
+
* - Optional heading, used on the modal surface
|
|
49
|
+
*/
|
|
50
|
+
title?: string;
|
|
51
|
+
/**
|
|
52
|
+
* - The message, already localized
|
|
53
|
+
*/
|
|
54
|
+
text: string;
|
|
55
|
+
/**
|
|
56
|
+
* - BCP 47 tag of the language `text` and `cta.label` are in
|
|
57
|
+
*/
|
|
58
|
+
lang: string;
|
|
59
|
+
/**
|
|
60
|
+
* - Optional call to action
|
|
61
|
+
*/
|
|
62
|
+
cta?: BannerCta;
|
|
63
|
+
/**
|
|
64
|
+
* - Optional lesser action, needs a cta
|
|
65
|
+
*/
|
|
66
|
+
secondaryCta?: BannerCta;
|
|
67
|
+
/**
|
|
68
|
+
* - Whether the client offers a dismiss control
|
|
69
|
+
*/
|
|
70
|
+
dismissible: boolean;
|
|
71
|
+
/**
|
|
72
|
+
* - When the user dismissed it
|
|
73
|
+
*/
|
|
74
|
+
dismissedAt?: string;
|
|
75
|
+
/**
|
|
76
|
+
* - Sort order, highest first
|
|
77
|
+
*/
|
|
78
|
+
priority: number;
|
|
79
|
+
/**
|
|
80
|
+
* - Start of the validity window, inclusive
|
|
81
|
+
*/
|
|
82
|
+
startsAt: string;
|
|
83
|
+
/**
|
|
84
|
+
* - End of the validity window, exclusive
|
|
85
|
+
*/
|
|
86
|
+
endsAt?: string;
|
|
87
|
+
};
|
package/types/models/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export const triggers: typeof trigger;
|
|
2
|
+
import * as banner from "./banner";
|
|
2
3
|
import * as trigger from "./trigger";
|
|
3
4
|
import * as instance from "./instance";
|
|
4
5
|
import * as applications from "./applications";
|
|
@@ -21,4 +22,4 @@ import * as geo from "./geo";
|
|
|
21
22
|
import * as doctypes from "./doctypes";
|
|
22
23
|
import * as ai from "./ai";
|
|
23
24
|
import * as assistant from "./assistant";
|
|
24
|
-
export { trigger, instance, applications, file, folder, konnectorFolder, note, account, permission, utils, contact, document, timeseries, sharing, dacc, paper, user, geo, doctypes, ai, assistant };
|
|
25
|
+
export { banner, trigger, instance, applications, file, folder, konnectorFolder, note, account, permission, utils, contact, document, timeseries, sharing, dacc, paper, user, geo, doctypes, ai, assistant };
|