@startsimpli/integrations 0.2.24 → 0.2.26
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__/connections.test.tsx +131 -0
- package/src/__tests__/corpus-planning.test.tsx +556 -0
- package/src/api/connections.ts +46 -0
- package/src/components/index.ts +17 -0
- package/src/components/pages/ConnectionsPage.tsx +139 -15
- package/src/components/pages/PlanningPage.tsx +691 -0
- package/src/components/pages/TrackerPage.tsx +22 -134
- package/src/components/pages/types.ts +59 -0
- package/src/components/pages/work-items.tsx +256 -0
- package/src/corpus/client.ts +368 -1
- package/src/corpus/entities.ts +63 -1
- package/src/corpus/index.ts +26 -1
- package/src/corpus/planning.ts +323 -0
- package/src/corpus/types.ts +241 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@startsimpli/integrations",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.26",
|
|
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",
|
|
@@ -1107,3 +1107,134 @@ describe('MemberConnectionRoster — Disconnect is not offered twice', () => {
|
|
|
1107
1107
|
expect(api.disconnectMemberConnection).toHaveBeenCalledWith('acme', 'm-live'));
|
|
1108
1108
|
});
|
|
1109
1109
|
});
|
|
1110
|
+
|
|
1111
|
+
// --------------------------------------------------------------------------
|
|
1112
|
+
// auth_mode: the OAuth branch (bd startsim-768w.22.25.14 / .23)
|
|
1113
|
+
// --------------------------------------------------------------------------
|
|
1114
|
+
|
|
1115
|
+
/**
|
|
1116
|
+
* `schema.auth_mode_for`'s own docstring calls `auth_mode` "the ONLY thing the
|
|
1117
|
+
* connect card branches on", and until this suite existed the card never
|
|
1118
|
+
* branched: `fields = [...credential_schema, ...config_schema]` and one
|
|
1119
|
+
* unconditional `CredentialSchemaForm`. On the admin catalog that rendered
|
|
1120
|
+
* Dropbox's `access_token` / `refresh_token` / `expires_at` as text inputs —
|
|
1121
|
+
* the product asking a human to go and mint a refresh token by hand.
|
|
1122
|
+
*/
|
|
1123
|
+
const OAUTH_ENTRY = {
|
|
1124
|
+
key: 'dropbox',
|
|
1125
|
+
name: 'Dropbox',
|
|
1126
|
+
description: 'Poll file metadata',
|
|
1127
|
+
logo_url: '',
|
|
1128
|
+
docs_url: '',
|
|
1129
|
+
capabilities: ['poll'],
|
|
1130
|
+
auth_mode: 'oauth',
|
|
1131
|
+
credential_schema: {
|
|
1132
|
+
version: 1,
|
|
1133
|
+
fields: [
|
|
1134
|
+
{ key: 'access_token', label: 'Access token', type: 'secret', required: true },
|
|
1135
|
+
{ key: 'refresh_token', label: 'Refresh token', type: 'secret', required: false },
|
|
1136
|
+
],
|
|
1137
|
+
},
|
|
1138
|
+
config_schema: {
|
|
1139
|
+
version: 1,
|
|
1140
|
+
fields: [{ key: 'path', label: 'Folder', type: 'string', required: false }],
|
|
1141
|
+
},
|
|
1142
|
+
record_types: [],
|
|
1143
|
+
supports_webhooks: true,
|
|
1144
|
+
availability: { available: true, reason: '' },
|
|
1145
|
+
};
|
|
1146
|
+
|
|
1147
|
+
const START = {
|
|
1148
|
+
authorization_url:
|
|
1149
|
+
'https://www.dropbox.com/oauth2/authorize?client_id=abc&response_type=code'
|
|
1150
|
+
+ '&token_access_type=offline&redirect_uri=https%3A%2F%2Fapi.example%2Fcb%2F&state=signed',
|
|
1151
|
+
state: 'signed',
|
|
1152
|
+
redirect_uri: 'https://api.example/cb/',
|
|
1153
|
+
};
|
|
1154
|
+
|
|
1155
|
+
function oauthApi(overrides: Partial<ConnectionsApi> = {}): ConnectionsApi {
|
|
1156
|
+
return stubApi({
|
|
1157
|
+
connectorCatalog: vi.fn().mockResolvedValue({ results: [OAUTH_ENTRY] }),
|
|
1158
|
+
startOAuth: vi.fn().mockResolvedValue(START),
|
|
1159
|
+
...overrides,
|
|
1160
|
+
});
|
|
1161
|
+
}
|
|
1162
|
+
|
|
1163
|
+
async function openOAuthCard(api: ConnectionsApi) {
|
|
1164
|
+
render(<ConnectionsPage api={api} foundrySlug="acme" />);
|
|
1165
|
+
await screen.findByText('Dropbox');
|
|
1166
|
+
await userEvent.click(screen.getByRole('button', { name: /^connect$/i }));
|
|
1167
|
+
}
|
|
1168
|
+
|
|
1169
|
+
describe('an OAuth connector is never a form asking for token material', () => {
|
|
1170
|
+
it('renders NO credential inputs for an oauth connector', async () => {
|
|
1171
|
+
await openOAuthCard(oauthApi());
|
|
1172
|
+
expect(screen.queryByLabelText(/access token/i)).not.toBeInTheDocument();
|
|
1173
|
+
expect(screen.queryByLabelText(/refresh token/i)).not.toBeInTheDocument();
|
|
1174
|
+
});
|
|
1175
|
+
|
|
1176
|
+
it('still asks for config, which is not a secret', async () => {
|
|
1177
|
+
await openOAuthCard(oauthApi());
|
|
1178
|
+
expect(screen.getByLabelText(/folder/i)).toBeInTheDocument();
|
|
1179
|
+
});
|
|
1180
|
+
|
|
1181
|
+
it('offers a hand-off to the vendor instead of Test and save', async () => {
|
|
1182
|
+
await openOAuthCard(oauthApi());
|
|
1183
|
+
expect(screen.getByRole('button', { name: /continue to dropbox/i })).toBeInTheDocument();
|
|
1184
|
+
expect(screen.queryByRole('button', { name: /test and save/i })).not.toBeInTheDocument();
|
|
1185
|
+
});
|
|
1186
|
+
|
|
1187
|
+
it('an api_key connector is untouched by the branch', async () => {
|
|
1188
|
+
render(<ConnectionsPage api={stubApi()} foundrySlug="acme" />);
|
|
1189
|
+
await screen.findByText('n8n');
|
|
1190
|
+
await userEvent.click(screen.getAllByRole('button', { name: /^connect$/i })[0]);
|
|
1191
|
+
expect(screen.getByRole('button', { name: /test and save/i })).toBeInTheDocument();
|
|
1192
|
+
});
|
|
1193
|
+
});
|
|
1194
|
+
|
|
1195
|
+
describe('the hand-off sends the browser to the consent URL', () => {
|
|
1196
|
+
it('starts the round trip with the connector key and the gathered config', async () => {
|
|
1197
|
+
const api = oauthApi();
|
|
1198
|
+
await openOAuthCard(api);
|
|
1199
|
+
await userEvent.type(screen.getByLabelText(/folder/i), '/Reports');
|
|
1200
|
+
await userEvent.click(screen.getByRole('button', { name: /continue to dropbox/i }));
|
|
1201
|
+
await waitFor(() => expect(api.startOAuth).toHaveBeenCalled());
|
|
1202
|
+
const [slug, input] = (api.startOAuth as ReturnType<typeof vi.fn>).mock.calls[0];
|
|
1203
|
+
expect(slug).toBe('acme');
|
|
1204
|
+
expect(input.connector_key).toBe('dropbox');
|
|
1205
|
+
expect(input.config).toEqual({ path: '/Reports' });
|
|
1206
|
+
});
|
|
1207
|
+
|
|
1208
|
+
it('opens the URL VERBATIM — the client adds no parameters of its own', async () => {
|
|
1209
|
+
const assign = vi.fn();
|
|
1210
|
+
const original = window.location;
|
|
1211
|
+
Object.defineProperty(window, 'location', {
|
|
1212
|
+
configurable: true, value: { ...original, href: 'https://app.example/c', assign },
|
|
1213
|
+
});
|
|
1214
|
+
try {
|
|
1215
|
+
const api = oauthApi();
|
|
1216
|
+
await openOAuthCard(api);
|
|
1217
|
+
await userEvent.click(screen.getByRole('button', { name: /continue to dropbox/i }));
|
|
1218
|
+
await waitFor(() => expect(assign).toHaveBeenCalledWith(START.authorization_url));
|
|
1219
|
+
} finally {
|
|
1220
|
+
Object.defineProperty(window, 'location', { configurable: true, value: original });
|
|
1221
|
+
}
|
|
1222
|
+
});
|
|
1223
|
+
|
|
1224
|
+
it('surfaces a refusal instead of navigating', async () => {
|
|
1225
|
+
const api = oauthApi({
|
|
1226
|
+
startOAuth: vi.fn().mockRejectedValue(new Error('no app registration')),
|
|
1227
|
+
});
|
|
1228
|
+
await openOAuthCard(api);
|
|
1229
|
+
await userEvent.click(screen.getByRole('button', { name: /continue to dropbox/i }));
|
|
1230
|
+
expect(await screen.findByRole('alert')).toHaveTextContent(/no app registration/i);
|
|
1231
|
+
});
|
|
1232
|
+
|
|
1233
|
+
it('a client without the verb says so rather than offering a dead button', async () => {
|
|
1234
|
+
const api = oauthApi();
|
|
1235
|
+
delete (api as { startOAuth?: unknown }).startOAuth;
|
|
1236
|
+
await openOAuthCard(api);
|
|
1237
|
+
expect(screen.getByText(/cannot start a dropbox sign-in yet/i)).toBeInTheDocument();
|
|
1238
|
+
expect(screen.getByRole('button', { name: /continue to dropbox/i })).toBeDisabled();
|
|
1239
|
+
});
|
|
1240
|
+
});
|