nucleus-core-ts 0.9.790 → 0.9.792
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/fe/components/IntegrationsPage/components/IntegrationsPage.js +20 -4
- package/dist/fe/components/IntegrationsPage/components/ParamBindingRow.js +2 -1
- package/dist/fe/components/IntegrationsPage/components/formSupport.d.ts +12 -1
- package/dist/fe/components/IntegrationsPage/components/formSupport.js +16 -1
- package/dist/fe/components/IntegrationsPage/components/formSupport.test.d.ts +1 -0
- package/dist/fe/components/IntegrationsPage/components/formSupport.test.js +59 -0
- package/package.json +1 -1
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
3
|
import { useGSAP } from '@gsap/react';
|
|
4
4
|
import gsap from 'gsap';
|
|
5
|
-
import { useEffect, useEffectEvent, useRef } from 'react';
|
|
5
|
+
import { useEffect, useEffectEvent, useRef, useState } from 'react';
|
|
6
6
|
import { cn } from '../../../utils/cn';
|
|
7
7
|
import { useIntegrationsStore } from '../store';
|
|
8
8
|
import { integrationsPageTheme } from '../theme';
|
|
@@ -26,6 +26,19 @@ export function IntegrationsPage({ title = 'Integrations', subtitle = 'Connect a
|
|
|
26
26
|
const store = useIntegrationsStore();
|
|
27
27
|
const containerRef = useRef(null);
|
|
28
28
|
const contentRef = useRef(null);
|
|
29
|
+
/**
|
|
30
|
+
* Bumped after a connection is written, to pull the rail again.
|
|
31
|
+
*
|
|
32
|
+
* `ConnectionList` and `ConnectionSettingsTab` were both built to be told
|
|
33
|
+
* about a save and neither was ever told: the shell mounted them with only
|
|
34
|
+
* `actions`. Creating the first connection therefore selected a record the
|
|
35
|
+
* rail had never fetched, so the rail kept saying "0 connections" while the
|
|
36
|
+
* detail treated the missing record as still loading and sat on skeletons
|
|
37
|
+
* until the page was reloaded by hand. The write itself had succeeded.
|
|
38
|
+
*/ const [sourcesReloadToken, setSourcesReloadToken] = useState(0);
|
|
39
|
+
const handleSourceSaved = ()=>{
|
|
40
|
+
setSourcesReloadToken((token)=>token + 1);
|
|
41
|
+
};
|
|
29
42
|
// One measured entrance. Anything longer makes an operator wait to read counts
|
|
30
43
|
// they came here to check.
|
|
31
44
|
useGSAP(()=>{
|
|
@@ -114,7 +127,8 @@ export function IntegrationsPage({ title = 'Integrations', subtitle = 'Connect a
|
|
|
114
127
|
"aria-label": "Connections",
|
|
115
128
|
className: theme.layout.aside,
|
|
116
129
|
children: /*#__PURE__*/ _jsx(ConnectionList, {
|
|
117
|
-
actions: actions
|
|
130
|
+
actions: actions,
|
|
131
|
+
reloadToken: sourcesReloadToken
|
|
118
132
|
})
|
|
119
133
|
}),
|
|
120
134
|
/*#__PURE__*/ _jsx("section", {
|
|
@@ -138,7 +152,8 @@ export function IntegrationsPage({ title = 'Integrations', subtitle = 'Connect a
|
|
|
138
152
|
role: "tabpanel",
|
|
139
153
|
children: [
|
|
140
154
|
tab === 'connection' ? /*#__PURE__*/ _jsx(ConnectionSettingsTab, {
|
|
141
|
-
actions: actions
|
|
155
|
+
actions: actions,
|
|
156
|
+
onSaved: handleSourceSaved
|
|
142
157
|
}) : null,
|
|
143
158
|
tab === 'endpoints' ? /*#__PURE__*/ _jsx(EndpointsTab, {
|
|
144
159
|
actions: actions
|
|
@@ -163,7 +178,8 @@ export function IntegrationsPage({ title = 'Integrations', subtitle = 'Connect a
|
|
|
163
178
|
children: "Select a connection on the left, or set up a new one here."
|
|
164
179
|
}),
|
|
165
180
|
/*#__PURE__*/ _jsx(ConnectionSettingsTab, {
|
|
166
|
-
actions: actions
|
|
181
|
+
actions: actions,
|
|
182
|
+
onSaved: handleSourceSaved
|
|
167
183
|
})
|
|
168
184
|
]
|
|
169
185
|
})
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
3
|
import { useId } from 'react';
|
|
4
4
|
import { integrationsPageTheme as theme } from '../theme';
|
|
5
|
+
import { endpointLabel } from './formSupport';
|
|
5
6
|
import { Field } from './Primitives';
|
|
6
7
|
export function ParamBindingRow({ binding, onChange, onRemove, siblings, entities, disabled }) {
|
|
7
8
|
const rowId = useId();
|
|
@@ -107,7 +108,7 @@ export function ParamBindingRow({ binding, onChange, onRemove, siblings, entitie
|
|
|
107
108
|
value: binding.endpointId,
|
|
108
109
|
children: siblings.map((sibling)=>/*#__PURE__*/ _jsx("option", {
|
|
109
110
|
value: sibling.id,
|
|
110
|
-
children: sibling
|
|
111
|
+
children: endpointLabel(sibling)
|
|
111
112
|
}, sibling.id))
|
|
112
113
|
})
|
|
113
114
|
}),
|
|
@@ -1,6 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* The small things every form in this panel needs and none of them deserves a file.
|
|
3
3
|
*/
|
|
4
|
+
import type { IntegrationEndpoint } from '../types';
|
|
5
|
+
/**
|
|
6
|
+
* How an endpoint should read in a list someone has to choose from.
|
|
7
|
+
*
|
|
8
|
+
* The parameter-binding picker showed `tag`, which groups the endpoints of one
|
|
9
|
+
* API — so a source whose endpoints are all tagged `corporate-portal` offered
|
|
10
|
+
* three identical options and no way to tell which one supplied the value.
|
|
11
|
+
* The name is what distinguishes them; the path is what distinguishes them when
|
|
12
|
+
* nobody typed a name, since it is the one field that cannot be blank.
|
|
13
|
+
*/
|
|
14
|
+
export declare function endpointLabel(endpoint: IntegrationEndpoint): string;
|
|
4
15
|
/**
|
|
5
16
|
* The ids a control should point `aria-describedby` at.
|
|
6
17
|
*
|
|
@@ -1,6 +1,21 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* The small things every form in this panel needs and none of them deserves a file.
|
|
3
3
|
*/ /**
|
|
4
|
+
* How an endpoint should read in a list someone has to choose from.
|
|
5
|
+
*
|
|
6
|
+
* The parameter-binding picker showed `tag`, which groups the endpoints of one
|
|
7
|
+
* API — so a source whose endpoints are all tagged `corporate-portal` offered
|
|
8
|
+
* three identical options and no way to tell which one supplied the value.
|
|
9
|
+
* The name is what distinguishes them; the path is what distinguishes them when
|
|
10
|
+
* nobody typed a name, since it is the one field that cannot be blank.
|
|
11
|
+
*/ export function endpointLabel(endpoint) {
|
|
12
|
+
const name = endpoint.name?.trim();
|
|
13
|
+
if (name) return name;
|
|
14
|
+
const path = endpoint.path?.trim();
|
|
15
|
+
if (path) return `${endpoint.method} ${path}`;
|
|
16
|
+
return endpoint.tag;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
4
19
|
* The ids a control should point `aria-describedby` at.
|
|
5
20
|
*
|
|
6
21
|
* {@link Field} renders its hint as `<id>-hint` and its error as `<id>-error`,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { describe, expect, test } from 'bun:test';
|
|
2
|
+
import { describedBy, endpointLabel, errorText } from './formSupport';
|
|
3
|
+
const endpoint = (over)=>({
|
|
4
|
+
id: 'e1',
|
|
5
|
+
sourceId: 's1',
|
|
6
|
+
tag: 'corporate-portal',
|
|
7
|
+
method: 'GET',
|
|
8
|
+
path: '/api/v1/ext/corporate-portal/company',
|
|
9
|
+
...over
|
|
10
|
+
});
|
|
11
|
+
/**
|
|
12
|
+
* Seen while wiring a chained import against a real API: every endpoint of one
|
|
13
|
+
* service shares a tag, and the binding picker labelled its options with that
|
|
14
|
+
* tag — three entries reading "corporate-portal" and no way to tell which one
|
|
15
|
+
* feeds the parameter.
|
|
16
|
+
*/ describe('endpointLabel', ()=>{
|
|
17
|
+
test('the name is what distinguishes one endpoint from its siblings', ()=>{
|
|
18
|
+
expect(endpointLabel(endpoint({
|
|
19
|
+
name: 'Şirketler'
|
|
20
|
+
}))).toBe('Şirketler');
|
|
21
|
+
});
|
|
22
|
+
test('without a name, the method and path do — never the shared tag', ()=>{
|
|
23
|
+
const label = endpointLabel(endpoint({
|
|
24
|
+
name: null
|
|
25
|
+
}));
|
|
26
|
+
expect(label).toBe('GET /api/v1/ext/corporate-portal/company');
|
|
27
|
+
expect(label).not.toBe('corporate-portal');
|
|
28
|
+
});
|
|
29
|
+
test('a name of only spaces is not a name', ()=>{
|
|
30
|
+
expect(endpointLabel(endpoint({
|
|
31
|
+
name: ' '
|
|
32
|
+
}))).toBe('GET /api/v1/ext/corporate-portal/company');
|
|
33
|
+
});
|
|
34
|
+
test('the tag is the last resort, not the first', ()=>{
|
|
35
|
+
expect(endpointLabel(endpoint({
|
|
36
|
+
name: null,
|
|
37
|
+
path: ''
|
|
38
|
+
}))).toBe('corporate-portal');
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
describe('describedBy', ()=>{
|
|
42
|
+
test('names only the ids that were rendered', ()=>{
|
|
43
|
+
expect(describedBy('f', true, true)).toBe('f-hint f-error');
|
|
44
|
+
expect(describedBy('f', true, false)).toBe('f-hint');
|
|
45
|
+
expect(describedBy('f', false, true)).toBe('f-error');
|
|
46
|
+
expect(describedBy('f', false, false)).toBeUndefined();
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
describe('errorText', ()=>{
|
|
50
|
+
test("the server's own wording is kept, because only it knows what was wrong", ()=>{
|
|
51
|
+
expect(errorText(new Error('Token request failed'), 'fallback')).toBe('Token request failed');
|
|
52
|
+
expect(errorText('plain reason', 'fallback')).toBe('plain reason');
|
|
53
|
+
});
|
|
54
|
+
test('an empty or unrecognised reason falls back rather than saying nothing', ()=>{
|
|
55
|
+
expect(errorText(new Error(''), 'fallback')).toBe('fallback');
|
|
56
|
+
expect(errorText(undefined, 'fallback')).toBe('fallback');
|
|
57
|
+
expect(errorText({}, 'fallback')).toBe('fallback');
|
|
58
|
+
});
|
|
59
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nucleus-core-ts",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.792",
|
|
4
4
|
"description": "Production-ready, enterprise-grade TypeScript framework for building multi-tenant APIs",
|
|
5
5
|
"author": "Hidayet Can Özcan <hidayetcan@gmail.com>",
|
|
6
6
|
"license": "SEE LICENSE IN LICENSE",
|