@startsimpli/integrations 0.2.9 → 0.2.11
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@startsimpli/integrations",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.11",
|
|
4
4
|
"description": "Provider-agnostic types, API clients, hooks, and UI primitives for email + calendar integrations across StartSimpli apps.",
|
|
5
5
|
"main": "./src/index.ts",
|
|
6
6
|
"types": "./src/index.ts",
|
|
@@ -367,6 +367,29 @@ describe('listCommitments speaks the tenant data API’s own filter language', (
|
|
|
367
367
|
expect(page.appliedFilters).not.toContain('owner');
|
|
368
368
|
});
|
|
369
369
|
|
|
370
|
+
it('believes the server through the CAMELCASE client too', async () => {
|
|
371
|
+
// `@startsimpli/api` converts every response key snake_case -> camelCase, so
|
|
372
|
+
// in a browser the tenant's `applied_filters` arrives as `appliedFilters`.
|
|
373
|
+
// Reading only one spelling put "this workspace's data API did not filter by
|
|
374
|
+
// status" on a deployed screen while the server was demonstrably filtering
|
|
375
|
+
// by status. Same lesson as accepting `document_id` in a citation.
|
|
376
|
+
const seen: Array<Record<string, unknown>> = [];
|
|
377
|
+
const api = createCorpusApi({
|
|
378
|
+
http: {
|
|
379
|
+
async get<T>(_path: string, options?: { params?: Record<string, unknown> }): Promise<T> {
|
|
380
|
+
seen.push(options?.params ?? {});
|
|
381
|
+
return { count: 0, next: null, previous: null, results: [],
|
|
382
|
+
appliedFilters: ['attr.status__in'], ignoredFilters: [] } as T;
|
|
383
|
+
},
|
|
384
|
+
},
|
|
385
|
+
scope: { orgId: 'tandem' },
|
|
386
|
+
supportedFilters: [],
|
|
387
|
+
});
|
|
388
|
+
const page = await api.listCommitments({ status: ['open', 'blocked'] });
|
|
389
|
+
expect(page.appliedFilters).toEqual(['status']);
|
|
390
|
+
expect(seen[0]['attr.status__in']).toBe('open,blocked');
|
|
391
|
+
});
|
|
392
|
+
|
|
370
393
|
it('falls back to the declared list on a server that does not report', async () => {
|
|
371
394
|
const { api } = client(undefined);
|
|
372
395
|
const page = await api.listCommitments({ status: 'open' });
|
package/src/corpus/client.ts
CHANGED
|
@@ -125,7 +125,22 @@ interface DrfPage<T> {
|
|
|
125
125
|
*/
|
|
126
126
|
interface ServerFilterReport {
|
|
127
127
|
applied_filters?: string[];
|
|
128
|
+
/**
|
|
129
|
+
* BOTH SPELLINGS, and this is not defensive padding — it is the difference
|
|
130
|
+
* between the banner telling the truth and telling a lie.
|
|
131
|
+
*
|
|
132
|
+
* `@startsimpli/api`'s fetch wrapper converts every response key from
|
|
133
|
+
* snake_case to camelCase by default (`transformKeys`), so the tenant's
|
|
134
|
+
* `applied_filters` reaches this adapter as `appliedFilters` in the browser
|
|
135
|
+
* and as `applied_filters` from a plain server-side `fetch`. Reading only one
|
|
136
|
+
* made the deployed Tracker report "this workspace's data API did not filter
|
|
137
|
+
* by status" while the server was, verifiably, filtering by status. Same
|
|
138
|
+
* lesson as `resolveCitations` accepting `document_id`: the tolerance belongs
|
|
139
|
+
* at the seam.
|
|
140
|
+
*/
|
|
141
|
+
appliedFilters?: string[];
|
|
128
142
|
ignored_filters?: string[];
|
|
143
|
+
ignoredFilters?: string[];
|
|
129
144
|
search_backend?: string;
|
|
130
145
|
}
|
|
131
146
|
|
|
@@ -170,6 +185,22 @@ const FILTER_PARAMS: Array<[keyof CommitmentQuery, string[]]> = [
|
|
|
170
185
|
['search', ['search']],
|
|
171
186
|
];
|
|
172
187
|
|
|
188
|
+
/** Which server parameter carries which `DocumentQuery` key. */
|
|
189
|
+
const DOCUMENT_FILTER_PARAMS: Array<[keyof DocumentQuery, string[]]> = [
|
|
190
|
+
['search', ['search']],
|
|
191
|
+
['source', ['source', 'attr.source']],
|
|
192
|
+
['partyId', ['party', 'related_to']],
|
|
193
|
+
['occurredAfter', ['occurred_after', 'attr.occurred_at__gte']],
|
|
194
|
+
['occurredBefore', ['occurred_before', 'attr.occurred_at__lte']],
|
|
195
|
+
];
|
|
196
|
+
|
|
197
|
+
function reportedDocumentFilters(query: DocumentQuery, applied: string[]): string[] {
|
|
198
|
+
const server = new Set(applied);
|
|
199
|
+
return DOCUMENT_FILTER_PARAMS.filter(
|
|
200
|
+
([key, names]) => Boolean(query[key]) && names.some((name) => server.has(name)),
|
|
201
|
+
).map(([key]) => key);
|
|
202
|
+
}
|
|
203
|
+
|
|
173
204
|
/** Translate the server's report back into the composer's vocabulary. */
|
|
174
205
|
function reportedFilters(query: CommitmentQuery, applied: string[]): string[] {
|
|
175
206
|
const server = new Set(applied);
|
|
@@ -227,15 +258,20 @@ export function createCorpusApi(options: CreateCorpusApiOptions): CorpusApi & Co
|
|
|
227
258
|
query: DocumentQuery,
|
|
228
259
|
map: (entity: TenantEntity) => T,
|
|
229
260
|
): Promise<CorpusPage<T>> {
|
|
230
|
-
const response = await http.get<DrfPage<TenantEntity
|
|
261
|
+
const response = await http.get<DrfPage<TenantEntity> & ServerFilterReport>(entities, {
|
|
231
262
|
params: params(query, type),
|
|
232
263
|
});
|
|
264
|
+
// The SERVER's own report wins here too — the Library was reading a
|
|
265
|
+
// hardcoded list while the tenant had learned to filter.
|
|
266
|
+
const reported = response.applied_filters ?? response.appliedFilters;
|
|
233
267
|
return {
|
|
234
268
|
count: response.count ?? response.results?.length ?? 0,
|
|
235
269
|
next: response.next ?? null,
|
|
236
270
|
previous: response.previous ?? null,
|
|
237
271
|
results: (response.results ?? []).map(map),
|
|
238
|
-
appliedFilters:
|
|
272
|
+
appliedFilters: reported
|
|
273
|
+
? reportedDocumentFilters(query, reported)
|
|
274
|
+
: appliedFilters(query, supportedFilters as string[]),
|
|
239
275
|
};
|
|
240
276
|
}
|
|
241
277
|
|
|
@@ -344,6 +380,7 @@ export function createCorpusApi(options: CreateCorpusApiOptions): CorpusApi & Co
|
|
|
344
380
|
async listCommitments(query: CommitmentQuery = {}): Promise<CorpusPage<Commitment>> {
|
|
345
381
|
const params = commitmentParams(query, types.commitment);
|
|
346
382
|
const response = await http.get<DrfPage<TenantEntity> & ServerFilterReport>(entities, { params });
|
|
383
|
+
const reported = response.applied_filters ?? response.appliedFilters;
|
|
347
384
|
return {
|
|
348
385
|
count: response.count ?? response.results?.length ?? 0,
|
|
349
386
|
next: response.next ?? null,
|
|
@@ -355,8 +392,8 @@ export function createCorpusApi(options: CreateCorpusApiOptions): CorpusApi & Co
|
|
|
355
392
|
// do — and cannot claim a gap that has since been closed. The static
|
|
356
393
|
// `supportedFilters` list is only the fallback for a deployment old
|
|
357
394
|
// enough not to report.
|
|
358
|
-
appliedFilters:
|
|
359
|
-
? reportedFilters(query,
|
|
395
|
+
appliedFilters: reported
|
|
396
|
+
? reportedFilters(query, reported)
|
|
360
397
|
: appliedFilters(query as DocumentQuery, supportedFilters as string[]),
|
|
361
398
|
};
|
|
362
399
|
},
|