@izumisy-tailor/tailor-data-viewer 0.3.0-preview.0 → 0.3.1
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/README.md +24 -17
- package/dist/generator/index.d.mts +0 -2
- package/dist/generator/index.mjs +0 -22
- package/package.json +1 -1
- package/src/component/collection/collection-provider.tsx +19 -29
- package/src/component/collection/use-collection.test.ts +27 -28
- package/src/component/collection/use-collection.ts +7 -7
- package/src/component/collection/use-collection.typetest.ts +6 -3
- package/src/component/data-table/data-table.tsx +8 -6
- package/src/component/data-table/pagination.tsx +3 -3
- package/src/component/data-table/search-filter-form.tsx +3 -3
- package/src/component/data-table/use-data-table.ts +2 -2
- package/src/component/field-helpers.test.ts +0 -4
- package/src/component/index.ts +3 -4
- package/src/component/types.ts +24 -7
- package/src/generator/metadata-generator.ts +0 -63
- package/src/tests/helpers.tsx +4 -4
package/README.md
CHANGED
|
@@ -5,8 +5,8 @@ A low-level React component library for building data table interfaces with Tail
|
|
|
5
5
|
## Features
|
|
6
6
|
|
|
7
7
|
- **Separation of Concerns**: Data fetching, query parameter management, and UI are fully decoupled
|
|
8
|
-
- **`
|
|
9
|
-
- **`
|
|
8
|
+
- **`useCollectionVariables` Hook**: Manages filter, sort, and pagination state; outputs Tailor Platform-compatible GraphQL variables
|
|
9
|
+
- **`CollectionVariablesProvider`**: Shares query parameters via React Context across sibling components
|
|
10
10
|
- **`Table.*` Compound Components**: Static, unstyled table primitives (`<table>`, `<thead>`, `<tbody>`, `<tr>`, `<th>`, `<td>`)
|
|
11
11
|
- **`DataTable.*` Compound Components**: Data-bound table with sort indicators, cell renderers, and `useDataTable` integration
|
|
12
12
|
- **`useDataTable` Hook**: Integrates data, column visibility, row operations (optimistic updates), and props generators
|
|
@@ -15,7 +15,7 @@ A low-level React component library for building data table interfaces with Tail
|
|
|
15
15
|
- **Utility Components**: `ColumnSelector`, `CsvButton`, `SearchFilterForm`, `Pagination` — all props-based, spreadable from hooks
|
|
16
16
|
- **Multi-sort Support**: Multiple simultaneous sort fields
|
|
17
17
|
- **Optimistic Updates**: `updateRow`, `deleteRow`, `insertRow` with rollback
|
|
18
|
-
- **Presentation Agnostic**: Same `
|
|
18
|
+
- **Presentation Agnostic**: Same `useCollectionVariables` can drive tables, kanbans, calendars, etc.
|
|
19
19
|
|
|
20
20
|
## Installation
|
|
21
21
|
|
|
@@ -47,7 +47,7 @@ npm install react react-dom
|
|
|
47
47
|
|
|
48
48
|
```tsx
|
|
49
49
|
import {
|
|
50
|
-
|
|
50
|
+
useCollectionVariables,
|
|
51
51
|
useDataTable,
|
|
52
52
|
DataTable,
|
|
53
53
|
Pagination,
|
|
@@ -95,8 +95,15 @@ const columns = [
|
|
|
95
95
|
|
|
96
96
|
// 2. Build a page
|
|
97
97
|
function OrdersPage() {
|
|
98
|
-
const
|
|
99
|
-
const [result] = useQuery({
|
|
98
|
+
const { variables } = useCollectionVariables({ params: { pageSize: 20 } });
|
|
99
|
+
const [result] = useQuery({
|
|
100
|
+
query: GET_ORDERS,
|
|
101
|
+
variables: {
|
|
102
|
+
...variables.pagination,
|
|
103
|
+
query: variables.query,
|
|
104
|
+
order: variables.order
|
|
105
|
+
}
|
|
106
|
+
});
|
|
100
107
|
|
|
101
108
|
const table = useDataTable<Order>({
|
|
102
109
|
columns,
|
|
@@ -119,12 +126,12 @@ function OrdersPage() {
|
|
|
119
126
|
|
|
120
127
|
## API Overview
|
|
121
128
|
|
|
122
|
-
### `
|
|
129
|
+
### `useCollectionVariables(options)`
|
|
123
130
|
|
|
124
131
|
Manages filter, sort, and pagination state. Returns `variables` containing `query`, `order`, and `pagination` sub-properties in Tailor Platform-compatible format.
|
|
125
132
|
|
|
126
133
|
```tsx
|
|
127
|
-
const collection =
|
|
134
|
+
const { variables, ...collection } = useCollectionVariables({
|
|
128
135
|
params: {
|
|
129
136
|
pageSize: 20,
|
|
130
137
|
initialSort: [{ field: "createdAt", direction: "Desc" }],
|
|
@@ -135,9 +142,9 @@ const collection = useCollection({
|
|
|
135
142
|
const [result] = useQuery({
|
|
136
143
|
query: GET_ORDERS,
|
|
137
144
|
variables: {
|
|
138
|
-
...
|
|
139
|
-
query:
|
|
140
|
-
order:
|
|
145
|
+
...variables.pagination,
|
|
146
|
+
query: variables.query,
|
|
147
|
+
order: variables.order,
|
|
141
148
|
},
|
|
142
149
|
});
|
|
143
150
|
|
|
@@ -163,15 +170,15 @@ collection.hasPrevPage; // boolean
|
|
|
163
170
|
collection.hasNextPage; // boolean
|
|
164
171
|
```
|
|
165
172
|
|
|
166
|
-
### `DataTable.Provider` / `useDataTableContext()` / `
|
|
173
|
+
### `DataTable.Provider` / `useDataTableContext()` / `useCollectionVariablesContext()`
|
|
167
174
|
|
|
168
175
|
`DataTable.Provider` wraps the table UI and provides both data table and collection context. All utility components (`Pagination`, `ColumnSelector`, `CsvButton`, `SearchFilterForm`) read from this context — no prop spreading needed.
|
|
169
176
|
|
|
170
|
-
When `collection` is passed to `useDataTable`, `DataTable.Provider` automatically wraps a `
|
|
177
|
+
When `collection` is passed to `useDataTable`, `DataTable.Provider` automatically wraps a `CollectionVariablesProvider` so child components can use `useCollectionVariablesContext()`.
|
|
171
178
|
|
|
172
179
|
```tsx
|
|
173
180
|
<DataTable.Provider value={table}>
|
|
174
|
-
<StatusFilter /> {/*
|
|
181
|
+
<StatusFilter /> {/* useCollectionVariablesContext() inside */}
|
|
175
182
|
<DataTable.Root>
|
|
176
183
|
<DataTable.Headers />
|
|
177
184
|
<DataTable.Body />
|
|
@@ -180,13 +187,13 @@ When `collection` is passed to `useDataTable`, `DataTable.Provider` automaticall
|
|
|
180
187
|
</DataTable.Provider>
|
|
181
188
|
```
|
|
182
189
|
|
|
183
|
-
For cases where you need `
|
|
190
|
+
For cases where you need `CollectionVariablesProvider` without `DataTable.Provider` (e.g., non-table UIs), you can use it standalone:
|
|
184
191
|
|
|
185
192
|
```tsx
|
|
186
|
-
<
|
|
193
|
+
<CollectionVariablesProvider value={collection}>
|
|
187
194
|
<StatusFilter />
|
|
188
195
|
<CustomKanbanBoard />
|
|
189
|
-
</
|
|
196
|
+
</CollectionVariablesProvider>
|
|
190
197
|
```
|
|
191
198
|
|
|
192
199
|
### Column Definition Helper
|
|
@@ -64,7 +64,6 @@ interface TableMetadata {
|
|
|
64
64
|
readonly name: string;
|
|
65
65
|
readonly pluralForm: string;
|
|
66
66
|
readonly description?: string;
|
|
67
|
-
readonly readAllowedRoles: readonly string[];
|
|
68
67
|
readonly fields: readonly FieldMetadata[];
|
|
69
68
|
/** Relations (manyToOne, oneToOne, and oneToMany) */
|
|
70
69
|
readonly relations?: readonly RelationMetadata[];
|
|
@@ -89,7 +88,6 @@ interface ProcessedTable {
|
|
|
89
88
|
pluralForm: string;
|
|
90
89
|
originalName: string;
|
|
91
90
|
description?: string;
|
|
92
|
-
readAllowedRoles: string[];
|
|
93
91
|
fields: FieldMetadata[];
|
|
94
92
|
relations: RelationMetadata[];
|
|
95
93
|
}
|
package/dist/generator/index.mjs
CHANGED
|
@@ -29,26 +29,6 @@ function toCamelCase(str) {
|
|
|
29
29
|
return str.charAt(0).toLowerCase() + str.slice(1);
|
|
30
30
|
}
|
|
31
31
|
/**
|
|
32
|
-
* Extract allowed roles from gql permission policies
|
|
33
|
-
* Only extracts roles from 'read' action policies with 'allow' permit
|
|
34
|
-
*/
|
|
35
|
-
function extractReadAllowedRoles(gqlPermission) {
|
|
36
|
-
if (!gqlPermission) return [];
|
|
37
|
-
const roles = /* @__PURE__ */ new Set();
|
|
38
|
-
for (const policy of gqlPermission) {
|
|
39
|
-
if (policy.permit !== "allow") continue;
|
|
40
|
-
const actions = policy.actions;
|
|
41
|
-
if (!actions.includes("all") && !actions.includes("read")) continue;
|
|
42
|
-
for (const condition of policy.conditions) {
|
|
43
|
-
if (!Array.isArray(condition) || condition.length < 3) continue;
|
|
44
|
-
const [left, operator, right] = condition;
|
|
45
|
-
if (typeof left === "string" && operator === "in" && typeof right === "object" && right !== null && "user" in right && right.user === "roles") roles.add(left);
|
|
46
|
-
if (typeof right === "string" && operator === "in" && typeof left === "object" && left !== null && "user" in left && left.user === "roles") roles.add(right);
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
return Array.from(roles);
|
|
50
|
-
}
|
|
51
|
-
/**
|
|
52
32
|
* Creates a custom generator that extracts table metadata for Data View
|
|
53
33
|
*/
|
|
54
34
|
function dataViewerMetadataGenerator(options = {}) {
|
|
@@ -100,13 +80,11 @@ function dataViewerMetadataGenerator(options = {}) {
|
|
|
100
80
|
};
|
|
101
81
|
fields.push(fieldMetadata);
|
|
102
82
|
}
|
|
103
|
-
const readAllowedRoles = extractReadAllowedRoles(type.permissions.gql);
|
|
104
83
|
return {
|
|
105
84
|
name: toCamelCase(type.name),
|
|
106
85
|
pluralForm: toCamelCase(type.pluralForm),
|
|
107
86
|
originalName: type.name,
|
|
108
87
|
description: type.description,
|
|
109
|
-
readAllowedRoles,
|
|
110
88
|
fields,
|
|
111
89
|
relations
|
|
112
90
|
};
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createContext, useContext, type ReactNode } from "react";
|
|
2
2
|
import type { UseCollectionReturn } from "../types";
|
|
3
3
|
|
|
4
|
-
const
|
|
4
|
+
const CollectionVariablesContext = createContext<UseCollectionReturn<
|
|
5
5
|
string,
|
|
6
6
|
unknown
|
|
7
7
|
> | null>(null);
|
|
@@ -9,14 +9,18 @@ const CollectionContext = createContext<UseCollectionReturn<
|
|
|
9
9
|
/**
|
|
10
10
|
* Provider that shares collection query parameters via React Context.
|
|
11
11
|
*
|
|
12
|
-
* @example
|
|
12
|
+
* @example
|
|
13
|
+
* ```tsx
|
|
14
|
+
* const { variables, ...collection } = useCollectionVariables({ params: { pageSize: 20 } });
|
|
15
|
+
*
|
|
16
|
+
* <CollectionVariablesProvider value={collection}>
|
|
13
17
|
* <FilterPanel />
|
|
14
18
|
* <DataTable.Root>...</DataTable.Root>
|
|
15
|
-
* <Pagination
|
|
16
|
-
* </
|
|
19
|
+
* <Pagination />
|
|
20
|
+
* </CollectionVariablesProvider>
|
|
17
21
|
* ```
|
|
18
22
|
*/
|
|
19
|
-
export function
|
|
23
|
+
export function CollectionVariablesProvider({
|
|
20
24
|
value,
|
|
21
25
|
children,
|
|
22
26
|
}: {
|
|
@@ -24,56 +28,42 @@ export function CollectionProvider({
|
|
|
24
28
|
children: ReactNode;
|
|
25
29
|
}) {
|
|
26
30
|
return (
|
|
27
|
-
<
|
|
31
|
+
<CollectionVariablesContext.Provider value={value}>
|
|
28
32
|
{children}
|
|
29
|
-
</
|
|
33
|
+
</CollectionVariablesContext.Provider>
|
|
30
34
|
);
|
|
31
35
|
}
|
|
32
36
|
|
|
33
37
|
/**
|
|
34
|
-
* Hook to access collection state from the nearest `
|
|
38
|
+
* Hook to access collection state from the nearest `CollectionVariablesProvider`.
|
|
35
39
|
*
|
|
36
|
-
* Returns the same interface as `
|
|
40
|
+
* Returns the same interface as `useCollectionVariables()`. Pass a `TFieldName`
|
|
37
41
|
* type parameter to narrow method arguments like `addFilter` / `setSort`.
|
|
38
42
|
*
|
|
39
43
|
* @typeParam TFieldName - Union of allowed field name strings (default: `string`).
|
|
40
44
|
*
|
|
41
|
-
* @throws Error if used outside of `
|
|
45
|
+
* @throws Error if used outside of `CollectionVariablesProvider`.
|
|
42
46
|
*
|
|
43
47
|
* @example
|
|
44
48
|
* ```tsx
|
|
45
49
|
* function StatusFilter() {
|
|
46
|
-
* const { filters, addFilter, removeFilter } =
|
|
50
|
+
* const { filters, addFilter, removeFilter } = useCollectionVariablesContext();
|
|
47
51
|
* // ...
|
|
48
52
|
* }
|
|
49
53
|
*
|
|
50
54
|
* // With typed field names:
|
|
51
55
|
* type TaskField = FieldName<typeof tableMetadata, "task">;
|
|
52
|
-
* const { addFilter } =
|
|
56
|
+
* const { addFilter } = useCollectionVariablesContext<TaskField>();
|
|
53
57
|
* ```
|
|
54
58
|
*/
|
|
55
|
-
export function
|
|
59
|
+
export function useCollectionVariablesContext<
|
|
56
60
|
TFieldName extends string = string,
|
|
57
61
|
>(): UseCollectionReturn<TFieldName> {
|
|
58
|
-
const ctx = useContext(
|
|
62
|
+
const ctx = useContext(CollectionVariablesContext);
|
|
59
63
|
if (!ctx) {
|
|
60
64
|
throw new Error(
|
|
61
|
-
"
|
|
65
|
+
"useCollectionVariablesContext must be used within <CollectionVariablesProvider>",
|
|
62
66
|
);
|
|
63
67
|
}
|
|
64
68
|
return ctx as UseCollectionReturn<TFieldName>;
|
|
65
69
|
}
|
|
66
|
-
|
|
67
|
-
/**
|
|
68
|
-
* `Collection` namespace object providing the Provider component.
|
|
69
|
-
*
|
|
70
|
-
* @example
|
|
71
|
-
* ```tsx
|
|
72
|
-
* <Collection.Provider value={collection}>
|
|
73
|
-
* ...
|
|
74
|
-
* </Collection.Provider>
|
|
75
|
-
* ```
|
|
76
|
-
*/
|
|
77
|
-
export const Collection = {
|
|
78
|
-
Provider: CollectionProvider,
|
|
79
|
-
} as const;
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import { renderHook, act } from "@testing-library/react";
|
|
2
2
|
import { describe, it, expect } from "vitest";
|
|
3
3
|
import type { TableMetadataMap } from "../../generator/metadata-generator";
|
|
4
|
-
import {
|
|
4
|
+
import { useCollectionVariables } from "./use-collection";
|
|
5
5
|
|
|
6
|
-
describe("
|
|
6
|
+
describe("useCollectionVariables", () => {
|
|
7
7
|
// ---------------------------------------------------------------------------
|
|
8
8
|
// Initial state
|
|
9
9
|
// ---------------------------------------------------------------------------
|
|
10
10
|
describe("initial state", () => {
|
|
11
11
|
it("returns default variables with pageSize 20", () => {
|
|
12
|
-
const { result } = renderHook(() =>
|
|
12
|
+
const { result } = renderHook(() => useCollectionVariables({}));
|
|
13
13
|
expect(result.current.variables.pagination).toEqual({ first: 20 });
|
|
14
14
|
expect(result.current.variables.query).toBeUndefined();
|
|
15
15
|
expect(result.current.variables.order).toBeUndefined();
|
|
@@ -21,14 +21,14 @@ describe("useCollection", () => {
|
|
|
21
21
|
|
|
22
22
|
it("uses custom pageSize", () => {
|
|
23
23
|
const { result } = renderHook(() =>
|
|
24
|
-
|
|
24
|
+
useCollectionVariables({ params: { pageSize: 50 } }),
|
|
25
25
|
);
|
|
26
26
|
expect(result.current.variables.pagination.first).toBe(50);
|
|
27
27
|
});
|
|
28
28
|
|
|
29
29
|
it("applies initial sort", () => {
|
|
30
30
|
const { result } = renderHook(() =>
|
|
31
|
-
|
|
31
|
+
useCollectionVariables({
|
|
32
32
|
params: {
|
|
33
33
|
initialSort: [{ field: "createdAt", direction: "Desc" }],
|
|
34
34
|
},
|
|
@@ -44,7 +44,7 @@ describe("useCollection", () => {
|
|
|
44
44
|
|
|
45
45
|
it("applies initial filters", () => {
|
|
46
46
|
const { result } = renderHook(() =>
|
|
47
|
-
|
|
47
|
+
useCollectionVariables({
|
|
48
48
|
params: {
|
|
49
49
|
initialFilters: [
|
|
50
50
|
{
|
|
@@ -68,7 +68,7 @@ describe("useCollection", () => {
|
|
|
68
68
|
// ---------------------------------------------------------------------------
|
|
69
69
|
describe("filter operations", () => {
|
|
70
70
|
it("adds a filter", () => {
|
|
71
|
-
const { result } = renderHook(() =>
|
|
71
|
+
const { result } = renderHook(() => useCollectionVariables({}));
|
|
72
72
|
|
|
73
73
|
act(() => {
|
|
74
74
|
result.current.addFilter("status", "eq", "ACTIVE");
|
|
@@ -86,7 +86,7 @@ describe("useCollection", () => {
|
|
|
86
86
|
});
|
|
87
87
|
|
|
88
88
|
it("replaces filter for same field", () => {
|
|
89
|
-
const { result } = renderHook(() =>
|
|
89
|
+
const { result } = renderHook(() => useCollectionVariables({}));
|
|
90
90
|
|
|
91
91
|
act(() => {
|
|
92
92
|
result.current.addFilter("status", "eq", "ACTIVE");
|
|
@@ -100,7 +100,7 @@ describe("useCollection", () => {
|
|
|
100
100
|
});
|
|
101
101
|
|
|
102
102
|
it("sets filters in bulk", () => {
|
|
103
|
-
const { result } = renderHook(() =>
|
|
103
|
+
const { result } = renderHook(() => useCollectionVariables({}));
|
|
104
104
|
|
|
105
105
|
act(() => {
|
|
106
106
|
result.current.setFilters([
|
|
@@ -125,7 +125,7 @@ describe("useCollection", () => {
|
|
|
125
125
|
});
|
|
126
126
|
|
|
127
127
|
it("removes a filter", () => {
|
|
128
|
-
const { result } = renderHook(() =>
|
|
128
|
+
const { result } = renderHook(() => useCollectionVariables({}));
|
|
129
129
|
|
|
130
130
|
act(() => {
|
|
131
131
|
result.current.addFilter("status", "eq", "ACTIVE");
|
|
@@ -140,7 +140,7 @@ describe("useCollection", () => {
|
|
|
140
140
|
});
|
|
141
141
|
|
|
142
142
|
it("clears all filters", () => {
|
|
143
|
-
const { result } = renderHook(() =>
|
|
143
|
+
const { result } = renderHook(() => useCollectionVariables({}));
|
|
144
144
|
|
|
145
145
|
act(() => {
|
|
146
146
|
result.current.addFilter("status", "eq", "ACTIVE");
|
|
@@ -155,7 +155,7 @@ describe("useCollection", () => {
|
|
|
155
155
|
});
|
|
156
156
|
|
|
157
157
|
it("resets pagination when filters change", () => {
|
|
158
|
-
const { result } = renderHook(() =>
|
|
158
|
+
const { result } = renderHook(() => useCollectionVariables({}));
|
|
159
159
|
|
|
160
160
|
// Navigate to next page
|
|
161
161
|
act(() => {
|
|
@@ -177,7 +177,7 @@ describe("useCollection", () => {
|
|
|
177
177
|
// ---------------------------------------------------------------------------
|
|
178
178
|
describe("sort operations", () => {
|
|
179
179
|
it("sets sort", () => {
|
|
180
|
-
const { result } = renderHook(() =>
|
|
180
|
+
const { result } = renderHook(() => useCollectionVariables({}));
|
|
181
181
|
|
|
182
182
|
act(() => {
|
|
183
183
|
result.current.setSort("createdAt", "Desc");
|
|
@@ -192,7 +192,7 @@ describe("useCollection", () => {
|
|
|
192
192
|
});
|
|
193
193
|
|
|
194
194
|
it("appends sort for different fields", () => {
|
|
195
|
-
const { result } = renderHook(() =>
|
|
195
|
+
const { result } = renderHook(() => useCollectionVariables({}));
|
|
196
196
|
|
|
197
197
|
act(() => {
|
|
198
198
|
result.current.setSort("createdAt", "Desc");
|
|
@@ -208,7 +208,7 @@ describe("useCollection", () => {
|
|
|
208
208
|
});
|
|
209
209
|
|
|
210
210
|
it("replaces direction for existing field", () => {
|
|
211
|
-
const { result } = renderHook(() =>
|
|
211
|
+
const { result } = renderHook(() => useCollectionVariables({}));
|
|
212
212
|
|
|
213
213
|
act(() => {
|
|
214
214
|
result.current.setSort("createdAt", "Desc");
|
|
@@ -227,7 +227,7 @@ describe("useCollection", () => {
|
|
|
227
227
|
});
|
|
228
228
|
|
|
229
229
|
it("removes sort when direction is undefined", () => {
|
|
230
|
-
const { result } = renderHook(() =>
|
|
230
|
+
const { result } = renderHook(() => useCollectionVariables({}));
|
|
231
231
|
|
|
232
232
|
act(() => {
|
|
233
233
|
result.current.setSort("createdAt", "Desc");
|
|
@@ -245,7 +245,7 @@ describe("useCollection", () => {
|
|
|
245
245
|
});
|
|
246
246
|
|
|
247
247
|
it("clears sort", () => {
|
|
248
|
-
const { result } = renderHook(() =>
|
|
248
|
+
const { result } = renderHook(() => useCollectionVariables({}));
|
|
249
249
|
|
|
250
250
|
act(() => {
|
|
251
251
|
result.current.setSort("createdAt", "Desc");
|
|
@@ -264,7 +264,7 @@ describe("useCollection", () => {
|
|
|
264
264
|
// ---------------------------------------------------------------------------
|
|
265
265
|
describe("pagination operations", () => {
|
|
266
266
|
it("navigates to next page (forward)", () => {
|
|
267
|
-
const { result } = renderHook(() =>
|
|
267
|
+
const { result } = renderHook(() => useCollectionVariables({}));
|
|
268
268
|
|
|
269
269
|
act(() => {
|
|
270
270
|
result.current.nextPage("cursor1");
|
|
@@ -279,7 +279,7 @@ describe("useCollection", () => {
|
|
|
279
279
|
});
|
|
280
280
|
|
|
281
281
|
it("navigates to previous page (backward)", () => {
|
|
282
|
-
const { result } = renderHook(() =>
|
|
282
|
+
const { result } = renderHook(() => useCollectionVariables({}));
|
|
283
283
|
|
|
284
284
|
act(() => {
|
|
285
285
|
result.current.prevPage("cursor1");
|
|
@@ -294,7 +294,7 @@ describe("useCollection", () => {
|
|
|
294
294
|
});
|
|
295
295
|
|
|
296
296
|
it("switches direction on nextPage after prevPage", () => {
|
|
297
|
-
const { result } = renderHook(() =>
|
|
297
|
+
const { result } = renderHook(() => useCollectionVariables({}));
|
|
298
298
|
|
|
299
299
|
act(() => {
|
|
300
300
|
result.current.prevPage("cursorB");
|
|
@@ -310,7 +310,7 @@ describe("useCollection", () => {
|
|
|
310
310
|
});
|
|
311
311
|
|
|
312
312
|
it("resets page to forward direction", () => {
|
|
313
|
-
const { result } = renderHook(() =>
|
|
313
|
+
const { result } = renderHook(() => useCollectionVariables({}));
|
|
314
314
|
|
|
315
315
|
act(() => {
|
|
316
316
|
result.current.prevPage("cursor1");
|
|
@@ -324,7 +324,7 @@ describe("useCollection", () => {
|
|
|
324
324
|
});
|
|
325
325
|
|
|
326
326
|
it("tracks hasPrevPage from currentPage and hasNextPage from setPageInfo", () => {
|
|
327
|
-
const { result } = renderHook(() =>
|
|
327
|
+
const { result } = renderHook(() => useCollectionVariables({}));
|
|
328
328
|
|
|
329
329
|
// Initially on page 1: no prev, no next
|
|
330
330
|
expect(result.current.hasPrevPage).toBe(false);
|
|
@@ -357,7 +357,7 @@ describe("useCollection", () => {
|
|
|
357
357
|
describe("variables", () => {
|
|
358
358
|
it("generates complete variables with filters, sort, and cursor", () => {
|
|
359
359
|
const { result } = renderHook(() =>
|
|
360
|
-
|
|
360
|
+
useCollectionVariables({
|
|
361
361
|
params: {
|
|
362
362
|
pageSize: 10,
|
|
363
363
|
initialFilters: [
|
|
@@ -387,7 +387,7 @@ describe("useCollection", () => {
|
|
|
387
387
|
});
|
|
388
388
|
|
|
389
389
|
it("omits undefined fields from pagination", () => {
|
|
390
|
-
const { result } = renderHook(() =>
|
|
390
|
+
const { result } = renderHook(() => useCollectionVariables({}));
|
|
391
391
|
const { pagination } = result.current.variables;
|
|
392
392
|
expect(pagination).toEqual({ first: 20 });
|
|
393
393
|
expect("after" in pagination).toBe(false);
|
|
@@ -397,7 +397,7 @@ describe("useCollection", () => {
|
|
|
397
397
|
|
|
398
398
|
it("returns undefined for query and order when empty", () => {
|
|
399
399
|
const { result } = renderHook(() =>
|
|
400
|
-
|
|
400
|
+
useCollectionVariables({ params: { pageSize: 10 } }),
|
|
401
401
|
);
|
|
402
402
|
|
|
403
403
|
expect(result.current.variables.query).toBeUndefined();
|
|
@@ -414,7 +414,6 @@ describe("useCollection", () => {
|
|
|
414
414
|
task: {
|
|
415
415
|
name: "task",
|
|
416
416
|
pluralForm: "tasks",
|
|
417
|
-
readAllowedRoles: [],
|
|
418
417
|
fields: [
|
|
419
418
|
{ name: "id", type: "uuid", required: true },
|
|
420
419
|
{ name: "title", type: "string", required: true },
|
|
@@ -432,7 +431,7 @@ describe("useCollection", () => {
|
|
|
432
431
|
|
|
433
432
|
it("works with tableMetadata", () => {
|
|
434
433
|
const { result } = renderHook(() =>
|
|
435
|
-
|
|
434
|
+
useCollectionVariables({
|
|
436
435
|
tableMetadata: testMetadata.task,
|
|
437
436
|
params: { pageSize: 10 },
|
|
438
437
|
}),
|
|
@@ -442,7 +441,7 @@ describe("useCollection", () => {
|
|
|
442
441
|
|
|
443
442
|
it("applies typed initialSort", () => {
|
|
444
443
|
const { result } = renderHook(() =>
|
|
445
|
-
|
|
444
|
+
useCollectionVariables({
|
|
446
445
|
tableMetadata: testMetadata.task,
|
|
447
446
|
params: {
|
|
448
447
|
initialSort: [{ field: "dueDate", direction: "Desc" }],
|
|
@@ -29,18 +29,18 @@ import type { TableFieldName, TableOrderableFieldName } from "../types";
|
|
|
29
29
|
* ```tsx
|
|
30
30
|
* import { tableMetadata } from "./generated/data-viewer-metadata.generated";
|
|
31
31
|
*
|
|
32
|
-
* const
|
|
32
|
+
* const { variables } = useCollectionVariables({
|
|
33
33
|
* tableMetadata: tableMetadata.task,
|
|
34
34
|
* params: { pageSize: 20 },
|
|
35
35
|
* });
|
|
36
|
-
* const { query, order, pagination } =
|
|
36
|
+
* const { query, order, pagination } = variables;
|
|
37
37
|
* const [result] = useQuery({
|
|
38
38
|
* query: GET_TASKS,
|
|
39
39
|
* variables: { ...pagination, query, order },
|
|
40
40
|
* });
|
|
41
41
|
* ```
|
|
42
42
|
*/
|
|
43
|
-
export function
|
|
43
|
+
export function useCollectionVariables<const TTable extends TableMetadata>(
|
|
44
44
|
options: UseCollectionOptions<
|
|
45
45
|
TableFieldName<TTable>,
|
|
46
46
|
TableMetadataFilter<TTable>
|
|
@@ -70,15 +70,15 @@ export function useCollection<const TTable extends TableMetadata>(
|
|
|
70
70
|
*
|
|
71
71
|
* @example
|
|
72
72
|
* ```tsx
|
|
73
|
-
* const
|
|
74
|
-
* const { query, order, pagination } =
|
|
73
|
+
* const { variables } = useCollectionVariables({ params: { pageSize: 20 } });
|
|
74
|
+
* const { query, order, pagination } = variables;
|
|
75
75
|
* const [result] = useQuery({
|
|
76
76
|
* query: GET_ORDERS,
|
|
77
77
|
* variables: { ...pagination, query, order },
|
|
78
78
|
* });
|
|
79
79
|
* ```
|
|
80
80
|
*/
|
|
81
|
-
export function
|
|
81
|
+
export function useCollectionVariables(
|
|
82
82
|
options: UseCollectionOptions & {
|
|
83
83
|
tableMetadata?: never;
|
|
84
84
|
},
|
|
@@ -87,7 +87,7 @@ export function useCollection(
|
|
|
87
87
|
// -----------------------------------------------------------------------------
|
|
88
88
|
// Implementation
|
|
89
89
|
// -----------------------------------------------------------------------------
|
|
90
|
-
export function
|
|
90
|
+
export function useCollectionVariables(
|
|
91
91
|
options: UseCollectionOptions & {
|
|
92
92
|
tableMetadata?: TableMetadata;
|
|
93
93
|
},
|
|
@@ -22,7 +22,6 @@ import type { TableMetadata } from "../../generator/metadata-generator";
|
|
|
22
22
|
type TestTable = {
|
|
23
23
|
readonly name: "test";
|
|
24
24
|
readonly pluralForm: "tests";
|
|
25
|
-
readonly readAllowedRoles: readonly [];
|
|
26
25
|
readonly fields: readonly [
|
|
27
26
|
{ readonly name: "id"; readonly type: "uuid"; readonly required: true },
|
|
28
27
|
{
|
|
@@ -82,9 +81,13 @@ type AssertNumberField = TestQuery extends {
|
|
|
82
81
|
: never;
|
|
83
82
|
export const assertNumberField: AssertNumberField = true;
|
|
84
83
|
|
|
85
|
-
// ✅ Enum fields produce
|
|
84
|
+
// ✅ Enum fields produce literal union types from enumValues
|
|
86
85
|
type AssertEnumField = TestQuery extends {
|
|
87
|
-
status?: {
|
|
86
|
+
status?: {
|
|
87
|
+
eq?: "active" | "inactive";
|
|
88
|
+
in?: ("active" | "inactive")[];
|
|
89
|
+
nin?: ("active" | "inactive")[];
|
|
90
|
+
};
|
|
88
91
|
}
|
|
89
92
|
? true
|
|
90
93
|
: never;
|
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
type ReactNode,
|
|
9
9
|
} from "react";
|
|
10
10
|
import { cn } from "../lib/utils";
|
|
11
|
-
import {
|
|
11
|
+
import { CollectionVariablesProvider } from "../collection/collection-provider";
|
|
12
12
|
import { Table } from "../table";
|
|
13
13
|
import type { RowAction, SortConfig, UseDataTableReturn } from "../types";
|
|
14
14
|
import {
|
|
@@ -53,14 +53,14 @@ function DataTableRoot({
|
|
|
53
53
|
/**
|
|
54
54
|
* Provider that shares `useDataTable()` state via React Context.
|
|
55
55
|
*
|
|
56
|
-
* Internally provides both `DataTableContext` and `
|
|
56
|
+
* Internally provides both `DataTableContext` and `CollectionVariablesContext`
|
|
57
57
|
* so that utility components (`Pagination`, `ColumnSelector`,
|
|
58
58
|
* `SearchFilterForm`, `CsvButton`) can consume data without explicit props.
|
|
59
59
|
*
|
|
60
60
|
* @example
|
|
61
61
|
* ```tsx
|
|
62
|
-
* const collection =
|
|
63
|
-
* const [result] = useQuery({ query: GET_ORDERS, variables: { ...
|
|
62
|
+
* const { variables, ...collection } = useCollectionVariables({ params: { pageSize: 20 } });
|
|
63
|
+
* const [result] = useQuery({ query: GET_ORDERS, variables: { ...variables.pagination, query: variables.query, order: variables.order } });
|
|
64
64
|
* const table = useDataTable({ columns, data: result.data?.orders, collection });
|
|
65
65
|
*
|
|
66
66
|
* <DataTable.Provider value={table}>
|
|
@@ -110,10 +110,12 @@ function DataTableProviderComponent<TRow extends Record<string, unknown>>({
|
|
|
110
110
|
</DataTableContext.Provider>
|
|
111
111
|
);
|
|
112
112
|
|
|
113
|
-
// Wrap with
|
|
113
|
+
// Wrap with CollectionVariablesContext when collection is available
|
|
114
114
|
if (collectionValue) {
|
|
115
115
|
return (
|
|
116
|
-
<
|
|
116
|
+
<CollectionVariablesProvider value={collectionValue}>
|
|
117
|
+
{inner}
|
|
118
|
+
</CollectionVariablesProvider>
|
|
117
119
|
);
|
|
118
120
|
}
|
|
119
121
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useDataTableContext } from "./data-table-context";
|
|
2
|
-
import {
|
|
2
|
+
import { useCollectionVariablesContext } from "../collection/collection-provider";
|
|
3
3
|
import { getLabels } from "./i18n";
|
|
4
4
|
|
|
5
5
|
// =============================================================================
|
|
@@ -112,7 +112,7 @@ const btnClass =
|
|
|
112
112
|
/**
|
|
113
113
|
* Pagination controls with first/prev/next/last navigation and page indicator.
|
|
114
114
|
*
|
|
115
|
-
* Reads pagination state from `DataTableContext` and `
|
|
115
|
+
* Reads pagination state from `DataTableContext` and `CollectionVariablesContext`.
|
|
116
116
|
* Must be rendered inside `DataTable.Provider` but **outside** `DataTable.Root`.
|
|
117
117
|
*
|
|
118
118
|
* @example
|
|
@@ -139,7 +139,7 @@ export function Pagination({ labels, pageSizeOptions }: PaginationProps = {}) {
|
|
|
139
139
|
goToLastPage,
|
|
140
140
|
pageSize,
|
|
141
141
|
setPageSize,
|
|
142
|
-
} =
|
|
142
|
+
} = useCollectionVariablesContext();
|
|
143
143
|
|
|
144
144
|
const pl = getLabels(locale).pagination;
|
|
145
145
|
const firstLabel = labels?.first ?? pl.first;
|
|
@@ -14,7 +14,7 @@ import type {
|
|
|
14
14
|
} from "../types";
|
|
15
15
|
import { OPERATORS_BY_FILTER_TYPE } from "../types";
|
|
16
16
|
import { useDataTableContext } from "./data-table-context";
|
|
17
|
-
import {
|
|
17
|
+
import { useCollectionVariablesContext } from "../collection/collection-provider";
|
|
18
18
|
import { getLabels } from "./i18n";
|
|
19
19
|
|
|
20
20
|
/**
|
|
@@ -22,7 +22,7 @@ import { getLabels } from "./i18n";
|
|
|
22
22
|
*
|
|
23
23
|
* Renders a dropdown panel with type-specific filter inputs, operator
|
|
24
24
|
* selectors, and active filter badges. Reads `columns` from
|
|
25
|
-
* `DataTableContext` and filter state from `
|
|
25
|
+
* `DataTableContext` and filter state from `CollectionVariablesContext`.
|
|
26
26
|
* Must be rendered inside `DataTable.Provider`.
|
|
27
27
|
*
|
|
28
28
|
* All text is customisable through the optional `labels` prop (defaults
|
|
@@ -46,7 +46,7 @@ export function SearchFilterForm({
|
|
|
46
46
|
} = {}) {
|
|
47
47
|
const { columns, locale } = useDataTableContext();
|
|
48
48
|
const { filters, addFilter, removeFilter, clearFilters } =
|
|
49
|
-
|
|
49
|
+
useCollectionVariablesContext();
|
|
50
50
|
const sf = getLabels(locale).searchFilter;
|
|
51
51
|
const filterableColumns = columns.filter((col) => !!col.filter);
|
|
52
52
|
|
|
@@ -13,8 +13,8 @@ import type {
|
|
|
13
13
|
*
|
|
14
14
|
* @example
|
|
15
15
|
* ```tsx
|
|
16
|
-
* const collection =
|
|
17
|
-
* const { query, order, pagination } =
|
|
16
|
+
* const { variables, ...collection } = useCollectionVariables({ params: { pageSize: 20 } });
|
|
17
|
+
* const { query, order, pagination } = variables;
|
|
18
18
|
* const [result] = useQuery({
|
|
19
19
|
* query: GET_ORDERS,
|
|
20
20
|
* variables: { ...pagination, query, order },
|
|
@@ -75,7 +75,6 @@ describe("inferColumns()", () => {
|
|
|
75
75
|
const metadata = {
|
|
76
76
|
name: "task",
|
|
77
77
|
pluralForm: "tasks",
|
|
78
|
-
readAllowedRoles: [],
|
|
79
78
|
fields: [
|
|
80
79
|
{ name: "id", type: "uuid", required: true },
|
|
81
80
|
{ name: "title", type: "string", required: true },
|
|
@@ -204,7 +203,6 @@ describe("inferColumns() with metadata", () => {
|
|
|
204
203
|
task: {
|
|
205
204
|
name: "task",
|
|
206
205
|
pluralForm: "tasks",
|
|
207
|
-
readAllowedRoles: [],
|
|
208
206
|
fields: [
|
|
209
207
|
{ name: "id", type: "uuid", required: true },
|
|
210
208
|
{ name: "title", type: "string", required: true },
|
|
@@ -370,7 +368,6 @@ describe("createColumnHelper()", () => {
|
|
|
370
368
|
const metadata = {
|
|
371
369
|
name: "order",
|
|
372
370
|
pluralForm: "orders",
|
|
373
|
-
readAllowedRoles: [],
|
|
374
371
|
fields: [
|
|
375
372
|
{ name: "id", type: "uuid", required: true },
|
|
376
373
|
{ name: "name", type: "string", required: true },
|
|
@@ -390,7 +387,6 @@ describe("createColumnHelper()", () => {
|
|
|
390
387
|
const metadata = {
|
|
391
388
|
name: "order",
|
|
392
389
|
pluralForm: "orders",
|
|
393
|
-
readAllowedRoles: [],
|
|
394
390
|
fields: [
|
|
395
391
|
{ name: "id", type: "uuid", required: true },
|
|
396
392
|
{ name: "name", type: "string", required: true },
|
package/src/component/index.ts
CHANGED
|
@@ -42,11 +42,10 @@ export {
|
|
|
42
42
|
} from "./types";
|
|
43
43
|
|
|
44
44
|
// Collection
|
|
45
|
-
export {
|
|
45
|
+
export { useCollectionVariables } from "./collection/use-collection";
|
|
46
46
|
export {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
useCollectionContext,
|
|
47
|
+
CollectionVariablesProvider,
|
|
48
|
+
useCollectionVariablesContext,
|
|
50
49
|
} from "./collection/collection-provider";
|
|
51
50
|
|
|
52
51
|
// Table (static)
|
package/src/component/types.ts
CHANGED
|
@@ -219,13 +219,13 @@ export interface PageInfo {
|
|
|
219
219
|
* GraphQL query variables in Tailor Platform format.
|
|
220
220
|
*
|
|
221
221
|
* Field-level type safety for `query` and `order` is not enforced here.
|
|
222
|
-
* When `
|
|
222
|
+
* When `useCollectionVariables` is called with metadata, `BuildQueryVariables`
|
|
223
223
|
* produces precise per-field filter types at compile time.
|
|
224
224
|
*/
|
|
225
225
|
export interface QueryVariables {
|
|
226
|
-
/** Filter object built at runtime by `
|
|
226
|
+
/** Filter object built at runtime by `useCollectionVariables`. */
|
|
227
227
|
query?: Record<string, unknown>;
|
|
228
|
-
/** Sort order built at runtime by `
|
|
228
|
+
/** Sort order built at runtime by `useCollectionVariables`. */
|
|
229
229
|
order?: { field: string; direction: "Asc" | "Desc" }[];
|
|
230
230
|
/** Forward pagination: number of items to fetch */
|
|
231
231
|
first?: number | null;
|
|
@@ -274,6 +274,23 @@ type FieldTypeToTSType = {
|
|
|
274
274
|
enum: string;
|
|
275
275
|
};
|
|
276
276
|
|
|
277
|
+
/**
|
|
278
|
+
* Resolves the value type for a field, narrowing enum fields to their
|
|
279
|
+
* literal union when `enumValues` is present in the metadata.
|
|
280
|
+
*
|
|
281
|
+
* - enum field with `enumValues: readonly ["OPEN", "CLOSED"]` → `"OPEN" | "CLOSED"`
|
|
282
|
+
* - enum field without `enumValues` → `string` (fallback)
|
|
283
|
+
* - all other field types → `FieldTypeToTSType[T]` (unchanged)
|
|
284
|
+
*/
|
|
285
|
+
type ResolveFieldValueType<
|
|
286
|
+
F,
|
|
287
|
+
T extends keyof FieldTypeToTSType,
|
|
288
|
+
> = T extends "enum"
|
|
289
|
+
? F extends { readonly enumValues: readonly (infer V extends string)[] }
|
|
290
|
+
? V
|
|
291
|
+
: string
|
|
292
|
+
: FieldTypeToTSType[T];
|
|
293
|
+
|
|
277
294
|
/**
|
|
278
295
|
* Resolves the value type for a filter operator.
|
|
279
296
|
* `in`/`nin` operators accept arrays, `between` accepts `{ min, max }`,
|
|
@@ -324,7 +341,7 @@ export type BuildQueryVariables<TTable extends TableMetadata> = {
|
|
|
324
341
|
}
|
|
325
342
|
? FieldTypeToFilterConfigType[T] extends infer FCT extends
|
|
326
343
|
FilterConfig["type"]
|
|
327
|
-
? FilterInputForFieldType<FCT,
|
|
344
|
+
? FilterInputForFieldType<FCT, ResolveFieldValueType<F, T>>
|
|
328
345
|
: never
|
|
329
346
|
: never;
|
|
330
347
|
};
|
|
@@ -425,11 +442,11 @@ export type ColumnDefinition<TRow extends Record<string, unknown>> =
|
|
|
425
442
|
Column<TRow>;
|
|
426
443
|
|
|
427
444
|
// =============================================================================
|
|
428
|
-
//
|
|
445
|
+
// useCollectionVariables Types
|
|
429
446
|
// =============================================================================
|
|
430
447
|
|
|
431
448
|
/**
|
|
432
|
-
* Options for `
|
|
449
|
+
* Options for `useCollectionVariables` hook.
|
|
433
450
|
*
|
|
434
451
|
* @typeParam TFieldName - Union of allowed field name strings (default: `string`).
|
|
435
452
|
*/
|
|
@@ -449,7 +466,7 @@ export interface UseCollectionOptions<
|
|
|
449
466
|
}
|
|
450
467
|
|
|
451
468
|
/**
|
|
452
|
-
* Return type of `
|
|
469
|
+
* Return type of `useCollectionVariables` hook.
|
|
453
470
|
*
|
|
454
471
|
* Methods that accept a field name are typed with `TFieldName` so that
|
|
455
472
|
* auto-completion works when a concrete union is supplied.
|
|
@@ -86,7 +86,6 @@ export interface TableMetadata {
|
|
|
86
86
|
readonly name: string;
|
|
87
87
|
readonly pluralForm: string;
|
|
88
88
|
readonly description?: string;
|
|
89
|
-
readonly readAllowedRoles: readonly string[];
|
|
90
89
|
readonly fields: readonly FieldMetadata[];
|
|
91
90
|
/** Relations (manyToOne, oneToOne, and oneToMany) */
|
|
92
91
|
readonly relations?: readonly RelationMetadata[];
|
|
@@ -112,7 +111,6 @@ interface ProcessedTable {
|
|
|
112
111
|
pluralForm: string;
|
|
113
112
|
originalName: string; // PascalCase name for relation lookup
|
|
114
113
|
description?: string;
|
|
115
|
-
readAllowedRoles: string[];
|
|
116
114
|
fields: FieldMetadata[];
|
|
117
115
|
relations: RelationMetadata[];
|
|
118
116
|
}
|
|
@@ -154,63 +152,6 @@ function toCamelCase(str: string): string {
|
|
|
154
152
|
return str.charAt(0).toLowerCase() + str.slice(1);
|
|
155
153
|
}
|
|
156
154
|
|
|
157
|
-
/**
|
|
158
|
-
* Extract allowed roles from gql permission policies
|
|
159
|
-
* Only extracts roles from 'read' action policies with 'allow' permit
|
|
160
|
-
*/
|
|
161
|
-
function extractReadAllowedRoles(
|
|
162
|
-
gqlPermission?: readonly {
|
|
163
|
-
conditions: readonly unknown[];
|
|
164
|
-
actions: readonly ["all"] | readonly string[];
|
|
165
|
-
permit: "allow" | "deny";
|
|
166
|
-
description?: string;
|
|
167
|
-
}[],
|
|
168
|
-
): string[] {
|
|
169
|
-
if (!gqlPermission) return [];
|
|
170
|
-
|
|
171
|
-
const roles = new Set<string>();
|
|
172
|
-
|
|
173
|
-
for (const policy of gqlPermission) {
|
|
174
|
-
// Only process 'allow' policies that include 'read' action
|
|
175
|
-
if (policy.permit !== "allow") continue;
|
|
176
|
-
const actions = policy.actions as readonly string[];
|
|
177
|
-
if (!actions.includes("all") && !actions.includes("read")) continue;
|
|
178
|
-
|
|
179
|
-
// Extract roles from conditions
|
|
180
|
-
for (const condition of policy.conditions) {
|
|
181
|
-
if (!Array.isArray(condition) || condition.length < 3) continue;
|
|
182
|
-
|
|
183
|
-
const [left, operator, right] = condition;
|
|
184
|
-
|
|
185
|
-
// Check for pattern: ["ROLE_NAME", "in", { user: "roles" }]
|
|
186
|
-
if (
|
|
187
|
-
typeof left === "string" &&
|
|
188
|
-
operator === "in" &&
|
|
189
|
-
typeof right === "object" &&
|
|
190
|
-
right !== null &&
|
|
191
|
-
"user" in right &&
|
|
192
|
-
(right as { user: string }).user === "roles"
|
|
193
|
-
) {
|
|
194
|
-
roles.add(left);
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
// Check for pattern: [{ user: "roles" }, "in", "ROLE_NAME"] (reversed)
|
|
198
|
-
if (
|
|
199
|
-
typeof right === "string" &&
|
|
200
|
-
operator === "in" &&
|
|
201
|
-
typeof left === "object" &&
|
|
202
|
-
left !== null &&
|
|
203
|
-
"user" in left &&
|
|
204
|
-
(left as { user: string }).user === "roles"
|
|
205
|
-
) {
|
|
206
|
-
roles.add(right);
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
return Array.from(roles);
|
|
212
|
-
}
|
|
213
|
-
|
|
214
155
|
/**
|
|
215
156
|
* Parsed field type from TailorDB
|
|
216
157
|
*/
|
|
@@ -402,15 +343,11 @@ export function dataViewerMetadataGenerator(
|
|
|
402
343
|
fields.push(fieldMetadata);
|
|
403
344
|
}
|
|
404
345
|
|
|
405
|
-
// Extract read allowed roles from gql permission
|
|
406
|
-
const readAllowedRoles = extractReadAllowedRoles(type.permissions.gql);
|
|
407
|
-
|
|
408
346
|
return {
|
|
409
347
|
name: toCamelCase(type.name),
|
|
410
348
|
pluralForm: toCamelCase(type.pluralForm),
|
|
411
349
|
originalName: type.name,
|
|
412
350
|
description: type.description,
|
|
413
|
-
readAllowedRoles,
|
|
414
351
|
fields,
|
|
415
352
|
relations,
|
|
416
353
|
};
|
package/src/tests/helpers.tsx
CHANGED
|
@@ -3,7 +3,7 @@ import { vi } from "vitest";
|
|
|
3
3
|
import type { Column, UseCollectionReturn } from "../component/types";
|
|
4
4
|
import { DataTableContext } from "../component/data-table/data-table-context";
|
|
5
5
|
import type { DataTableContextValue } from "../component/data-table/data-table-context";
|
|
6
|
-
import {
|
|
6
|
+
import { CollectionVariablesProvider } from "../component/collection/collection-provider";
|
|
7
7
|
|
|
8
8
|
// =============================================================================
|
|
9
9
|
// Mock factory: DataTableContext
|
|
@@ -43,7 +43,7 @@ export function createMockDataTableContext<T extends Record<string, unknown>>(
|
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
// =============================================================================
|
|
46
|
-
// Mock factory:
|
|
46
|
+
// Mock factory: CollectionVariablesContext
|
|
47
47
|
// =============================================================================
|
|
48
48
|
|
|
49
49
|
export function createMockCollectionContext(
|
|
@@ -122,7 +122,7 @@ export function createTestProviders<
|
|
|
122
122
|
collection?: Partial<UseCollectionReturn<string, unknown>>;
|
|
123
123
|
}) {
|
|
124
124
|
return (
|
|
125
|
-
<
|
|
125
|
+
<CollectionVariablesProvider
|
|
126
126
|
value={createMockCollectionContext({
|
|
127
127
|
...defaults.collectionDefaults,
|
|
128
128
|
...collection,
|
|
@@ -136,7 +136,7 @@ export function createTestProviders<
|
|
|
136
136
|
>
|
|
137
137
|
{children}
|
|
138
138
|
</DataTableContext.Provider>
|
|
139
|
-
</
|
|
139
|
+
</CollectionVariablesProvider>
|
|
140
140
|
);
|
|
141
141
|
};
|
|
142
142
|
}
|