@startsimpli/integrations 0.2.31 → 0.2.33
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 +1 -1
- package/src/__tests__/corpus-narrowed-reads.test.ts +337 -0
- package/src/__tests__/corpus-source-strip.test.tsx +225 -0
- package/src/__tests__/corpus-storage.test.ts +20 -9
- package/src/components/pages/LibraryPage.tsx +26 -2
- package/src/corpus/client.ts +294 -65
- package/src/corpus/entities.ts +41 -0
- package/src/corpus/fixtures.ts +108 -10
- package/src/corpus/runs.ts +10 -1
- package/src/corpus/types.ts +16 -2
package/src/corpus/fixtures.ts
CHANGED
|
@@ -42,6 +42,7 @@ import {
|
|
|
42
42
|
toParty,
|
|
43
43
|
toSourceDocument,
|
|
44
44
|
} from './entities';
|
|
45
|
+
import { MAX_ID_LIST, RELATIONSHIP_FILTERS, RESERVED_PARAMS } from './entities';
|
|
45
46
|
import type { TenantEntity, TenantRelationship } from './entities';
|
|
46
47
|
import { splitSections, stem } from './text';
|
|
47
48
|
import type {
|
|
@@ -869,11 +870,28 @@ export function createFixtureCorpusApi(options: FixtureCorpusOptions = {}): Corp
|
|
|
869
870
|
* search since this was written (bd startsim-768w.38.12) and a retrieval
|
|
870
871
|
* test must be written against the real semantics, but the suites that
|
|
871
872
|
* predate it are still testing what they were written to test. `source` and
|
|
872
|
-
* `party` are
|
|
873
|
-
*
|
|
874
|
-
* `
|
|
875
|
-
*
|
|
876
|
-
*
|
|
873
|
+
* `party` are accepted and ignored HERE, and this fixture no longer claims
|
|
874
|
+
* that is what `EntityViewSet` does: 768w.38.12 shipped both, along with the
|
|
875
|
+
* `applied_filters` report that names what the server narrowed by. So this
|
|
876
|
+
* stands in for an OLDER fork — which is a useful thing to have a stand-in
|
|
877
|
+
* for, since that is the fork `listSources` refuses to print a count for
|
|
878
|
+
* (bd startsim-jevtu), but it is a lagging stub and not the contract;
|
|
879
|
+
* • `page_size` IS honoured, capped at 200. It was written here as ignored,
|
|
880
|
+
* citing a `PageNumberPagination` with no `page_size_query_param`, and that
|
|
881
|
+
* stopped being true with `TenantPageNumberPagination` (bd
|
|
882
|
+
* startsim-768w.38.12): "a client asking for one row to read a count got
|
|
883
|
+
* fifty, and a client asking for two hundred to walk a graph got fifty and
|
|
884
|
+
* no indication that its request had been overruled". A fixture that keeps
|
|
885
|
+
* ignoring it manufactures evidence for a cap the wire does not impose;
|
|
886
|
+
* • `/api/v1/relationships/` FILTERS — `rel_type`, `source`, `target`,
|
|
887
|
+
* `entity` and the `__in` forms of the last three, bounded at
|
|
888
|
+
* `MAX_ID_LIST` ids and reported in `applied_filters`. This too was written
|
|
889
|
+
* here as "every edge in the org, unfiltered, because the tenant has no
|
|
890
|
+
* `?source=` on it either", and bd startsim-te3r shipped `RelationshipQuery`
|
|
891
|
+
* for exactly the read that comment was excusing. An UNRECOGNISED parameter
|
|
892
|
+
* is still ignored and the page comes back as wide as it was — that part is
|
|
893
|
+
* real, it is what makes an unconfirmed filter dangerous, and it is what
|
|
894
|
+
* `applied_filters` exists to let a client detect.
|
|
877
895
|
*
|
|
878
896
|
* Every request is recorded, so "did the browser filter it" and "how many round
|
|
879
897
|
* trips did one screen cost" are both assertable.
|
|
@@ -881,7 +899,7 @@ export function createFixtureCorpusApi(options: FixtureCorpusOptions = {}): Corp
|
|
|
881
899
|
export function createFixtureCorpusHttp(options: {
|
|
882
900
|
entities?: TenantEntity[];
|
|
883
901
|
relationships?: TenantRelationship[];
|
|
884
|
-
/** Rows per page, matching the tenant's
|
|
902
|
+
/** Rows per page when the caller does not ask, matching the tenant's `PAGE_SIZE`. */
|
|
885
903
|
pageSize?: number;
|
|
886
904
|
/** Endpoints to fail, so the fail-soft paths are exercised. */
|
|
887
905
|
missing?: string[];
|
|
@@ -901,15 +919,90 @@ export function createFixtureCorpusHttp(options: {
|
|
|
901
919
|
|
|
902
920
|
function slice<T>(rows: T[], params: Record<string, unknown>) {
|
|
903
921
|
const page = Number(params.page ?? 1) || 1;
|
|
904
|
-
|
|
922
|
+
// `TenantPageNumberPagination.page_size_query_param` is `page_size` and its
|
|
923
|
+
// `max_page_size` is 200 — an OVERRULED request is served at the cap, not
|
|
924
|
+
// refused, which is the behaviour a client that walks a page has to survive.
|
|
925
|
+
const asked = Number(params.page_size);
|
|
926
|
+
const size = Number.isFinite(asked) && asked > 0 ? Math.min(asked, MAX_ID_LIST) : pageSize;
|
|
927
|
+
const start = (page - 1) * size;
|
|
905
928
|
return {
|
|
906
929
|
count: rows.length,
|
|
907
|
-
next: start +
|
|
930
|
+
next: start + size < rows.length ? `?page=${page + 1}` : null,
|
|
908
931
|
previous: page > 1 ? `?page=${page - 1}` : null,
|
|
909
|
-
results: rows.slice(start, start +
|
|
932
|
+
results: rows.slice(start, start + size),
|
|
910
933
|
};
|
|
911
934
|
}
|
|
912
935
|
|
|
936
|
+
/**
|
|
937
|
+
* `/api/v1/relationships/`, as `RelationshipQuery.apply` really answers it.
|
|
938
|
+
*
|
|
939
|
+
* THE SHAPE THAT MATTERS IS THE ONE IT REFUSES TO NARROW. A parameter this
|
|
940
|
+
* endpoint does not recognise is swept into `ignored_filters` and the page
|
|
941
|
+
* comes back exactly as wide as it was — so a fixture that returned `[]` for
|
|
942
|
+
* an unsupported filter would let a client pass here and read the whole edge
|
|
943
|
+
* table in production. Every filter below is one the server really applies,
|
|
944
|
+
* and everything else is really ignored.
|
|
945
|
+
*/
|
|
946
|
+
function edgeQuery(rows: TenantRelationship[], params: Record<string, unknown>) {
|
|
947
|
+
const applied: string[] = [];
|
|
948
|
+
const ignored: string[] = [];
|
|
949
|
+
const idList = (key: string): Set<string> => {
|
|
950
|
+
const parts = String(params[key]).split(',').map((part) => part.trim()).filter(Boolean);
|
|
951
|
+
// `_uuid_list` refuses a longer list with a 400 rather than truncating it,
|
|
952
|
+
// which is what keeps `__in` from becoming an unpaginated read by another
|
|
953
|
+
// name — and what a client that chunks its ids has to be tested against.
|
|
954
|
+
if (parts.length > MAX_ID_LIST) {
|
|
955
|
+
throw Object.assign(new Error(`${key}: at most ${MAX_ID_LIST} ids`), { status: 400 });
|
|
956
|
+
}
|
|
957
|
+
return new Set(parts);
|
|
958
|
+
};
|
|
959
|
+
const typeOf = (id: unknown): string =>
|
|
960
|
+
String(entities.find((entity) => String(entity.id) === String(id))?.entity_type ?? '');
|
|
961
|
+
let out = rows;
|
|
962
|
+
|
|
963
|
+
for (const [key, field] of [['source', 'source'], ['target', 'target']] as const) {
|
|
964
|
+
if (key in params) {
|
|
965
|
+
const wanted = String(params[key]);
|
|
966
|
+
out = out.filter((edge) => String(edge[field]) === wanted);
|
|
967
|
+
applied.push(key);
|
|
968
|
+
}
|
|
969
|
+
const listed = `${key}__in`;
|
|
970
|
+
if (listed in params) {
|
|
971
|
+
const wanted = idList(listed);
|
|
972
|
+
out = out.filter((edge) => wanted.has(String(edge[field])));
|
|
973
|
+
applied.push(listed);
|
|
974
|
+
}
|
|
975
|
+
}
|
|
976
|
+
if ('entity' in params) {
|
|
977
|
+
const wanted = String(params.entity);
|
|
978
|
+
out = out.filter((edge) => String(edge.source) === wanted || String(edge.target) === wanted);
|
|
979
|
+
applied.push('entity');
|
|
980
|
+
}
|
|
981
|
+
if ('entity__in' in params) {
|
|
982
|
+
const wanted = idList('entity__in');
|
|
983
|
+
out = out.filter((edge) => wanted.has(String(edge.source)) || wanted.has(String(edge.target)));
|
|
984
|
+
applied.push('entity__in');
|
|
985
|
+
}
|
|
986
|
+
if ('rel_type' in params) {
|
|
987
|
+
const wanted = new Set(
|
|
988
|
+
String(params.rel_type).split(',').map((part) => part.trim()).filter(Boolean),
|
|
989
|
+
);
|
|
990
|
+
out = out.filter((edge) => wanted.has(String(edge.rel_type ?? edge.relType ?? '')));
|
|
991
|
+
applied.push('rel_type');
|
|
992
|
+
}
|
|
993
|
+
for (const [key, end] of [['source_type', 'source'], ['target_type', 'target']] as const) {
|
|
994
|
+
if (key in params) {
|
|
995
|
+
const wanted = String(params[key]);
|
|
996
|
+
out = out.filter((edge) => typeOf(edge[end]) === wanted);
|
|
997
|
+
applied.push(key);
|
|
998
|
+
}
|
|
999
|
+
}
|
|
1000
|
+
for (const key of Object.keys(params)) {
|
|
1001
|
+
if (!RESERVED_PARAMS.has(key) && !RELATIONSHIP_FILTERS.has(key)) ignored.push(key);
|
|
1002
|
+
}
|
|
1003
|
+
return { rows: out, applied, ignored };
|
|
1004
|
+
}
|
|
1005
|
+
|
|
913
1006
|
const http = {
|
|
914
1007
|
async get<T>(path: string, init?: { params?: Record<string, unknown> }): Promise<T> {
|
|
915
1008
|
const params = init?.params ?? {};
|
|
@@ -920,7 +1013,12 @@ export function createFixtureCorpusHttp(options: {
|
|
|
920
1013
|
throw Object.assign(new Error(`${path} responded 404`), { status: 404 });
|
|
921
1014
|
}
|
|
922
1015
|
if (path.includes('relationships')) {
|
|
923
|
-
|
|
1016
|
+
const query = edgeQuery(relationships, params);
|
|
1017
|
+
return {
|
|
1018
|
+
...slice(query.rows, params),
|
|
1019
|
+
applied_filters: query.applied.slice().sort(),
|
|
1020
|
+
ignored_filters: query.ignored.slice().sort(),
|
|
1021
|
+
} as T;
|
|
924
1022
|
}
|
|
925
1023
|
if (path.includes('entities')) {
|
|
926
1024
|
const id = /entities\/(.+)$/.exec(path)?.[1];
|
package/src/corpus/runs.ts
CHANGED
|
@@ -303,7 +303,16 @@ export async function startExtractionRun(
|
|
|
303
303
|
const at = now();
|
|
304
304
|
const driver = request.driverId ?? options.driverId ?? defaultDriverId();
|
|
305
305
|
|
|
306
|
-
|
|
306
|
+
// NOT `.catch(() => null)`. A store that could not answer is not a store that
|
|
307
|
+
// said "nothing has ever run", and this is the one place where confusing them
|
|
308
|
+
// is expensive: the branch below reads `null` as "start a new sweep", so a
|
|
309
|
+
// transient failure — or a read that could not see far enough (bd
|
|
310
|
+
// startsim-o0o6o.3) — put a SECOND driver on a live cursor, and two model
|
|
311
|
+
// calls over one document produce two items identity resolution cannot see are
|
|
312
|
+
// the same. `readRun` reports absence as `null` and everything else as a
|
|
313
|
+
// throw, precisely so this caller can tell them apart; swallowing the throw
|
|
314
|
+
// here threw that distinction away one line after it was made.
|
|
315
|
+
const existing = await deps.runs.readRun();
|
|
307
316
|
|
|
308
317
|
if (existing && !COMPLETE.has(existing.status) && !request.restart) {
|
|
309
318
|
if (isLeaseLive(existing, at) && existing.leaseOwner !== driver) {
|
package/src/corpus/types.ts
CHANGED
|
@@ -172,8 +172,22 @@ export interface MoneyEvent {
|
|
|
172
172
|
export interface SourceFreshness {
|
|
173
173
|
source: SourceKey;
|
|
174
174
|
label: string;
|
|
175
|
-
/**
|
|
176
|
-
|
|
175
|
+
/**
|
|
176
|
+
* How many documents this source has contributed to the corpus — counted OUT
|
|
177
|
+
* OF THE CORPUS, per source, and the only fact on a freshness card that is
|
|
178
|
+
* not central's connection bookkeeping (bd startsim-jevtu). That is what
|
|
179
|
+
* makes it worth its round trip: a source central reports as freshly synced
|
|
180
|
+
* that holds nothing here is the delivery hop failing, and no timestamp on
|
|
181
|
+
* the connection can say so.
|
|
182
|
+
*
|
|
183
|
+
* `null` means NOT COUNTED — the server did not confirm it narrowed by
|
|
184
|
+
* source, so the number it returned describes the whole corpus rather than
|
|
185
|
+
* this card. A screen renders nothing at all for `null`. `0` therefore means
|
|
186
|
+
* exactly what it says, which it did not before: the field was read off a
|
|
187
|
+
* connections payload that has never carried it, so every card everywhere
|
|
188
|
+
* reported `0` by accident.
|
|
189
|
+
*/
|
|
190
|
+
documentCount: number | null;
|
|
177
191
|
/**
|
|
178
192
|
* THREE FACTS, AND CONFLATING ANY TWO OF THEM IS HOW A FRESHNESS STRIP LIES
|
|
179
193
|
* (bd startsim-1cow). Central's `SubtreeConnectionsView` computes them
|