@jskit-ai/connectors-web 0.1.2 → 0.1.4

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,13 +1,16 @@
1
1
  {
2
2
  "name": "@jskit-ai/connectors-web",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "Shared Vue fields for portable application integration configuration.",
5
5
  "type": "module",
6
+ "files": [
7
+ "src"
8
+ ],
6
9
  "exports": {
7
10
  "./client": "./src/client/index.js"
8
11
  },
9
12
  "dependencies": {
10
- "@jskit-ai/connectors-core": "0.1.2"
13
+ "@jskit-ai/connectors-core": "0.1.4"
11
14
  },
12
15
  "peerDependencies": {
13
16
  "vue": "^3.5.13",
@@ -1,493 +0,0 @@
1
- import { test, expect } from "@playwright/test";
2
-
3
- for (const width of [390, 820, 1440]) {
4
- test(`Snowflake setting changes select required scopes without restoring optional choices at ${width}px`, async ({ page }) => {
5
- await page.setViewportSize({ width, height: 1000 }); await page.goto("/");
6
- await page.getByRole("button", { name: "Edit Snowflake", exact: true }).click();
7
- const role = page.getByRole("textbox", { name: "Role", exact: true });
8
- const permissions = page.getByRole("button", { name: "Permissions", exact: true });
9
- const save = page.getByRole("button", { name: "Export configuration", exact: true });
10
- await save.click(); const original = JSON.parse(await page.getByTestId("export").textContent());
11
- await role.fill("VIBE64_READER"); await permissions.click();
12
- const named = page.getByRole("checkbox", { name: "Use role VIBE64_READER (required)", exact: true });
13
- await expect(named).toBeChecked(); await expect(named).toBeDisabled();
14
- const offline = page.getByRole("checkbox", { name: "Keep access between visits", exact: true });
15
- await offline.uncheck(); await role.fill("Inventory & stock");
16
- await expect(named).toHaveCount(0); await expect(offline).not.toBeChecked();
17
- await expect(page.getByRole("checkbox", { name: "Use role Inventory & stock (required)", exact: true })).toBeChecked();
18
- await save.click(); const namedFile = JSON.parse(await page.getByTestId("export").textContent());
19
- expect(namedFile.integrations.snowflake.scopes).toEqual(["session:role-encoded:Inventory%20%26%20stock"]);
20
- expect(namedFile.integrations.snowflake.extensions).toEqual({ keep: "account" });
21
- expect(namedFile.integrations.calendar).toEqual(original.integrations.calendar);
22
- await page.getByRole("button", { name: "Leave configuration" }).click(); await page.getByRole("button", { name: "Return to configuration" }).click();
23
- await expect(role).toHaveValue("Inventory & stock");
24
- await page.getByLabel("Lock form").check(); await expect(role).toBeDisabled(); await page.getByLabel("Lock form").uncheck();
25
- await role.fill(""); await save.click(); const defaults = JSON.parse(await page.getByTestId("export").textContent());
26
- expect(defaults.integrations.snowflake.scopes).toEqual(["refresh_token"]); expect(defaults.integrations.snowflake.settings.role).toBeUndefined();
27
- if (await permissions.getAttribute("aria-expanded") !== "true") await permissions.click();
28
- const requiredOffline = page.getByRole("checkbox", { name: "Keep access between visits (required)", exact: true });
29
- await expect(requiredOffline).toBeChecked(); await expect(requiredOffline).toBeDisabled();
30
- await page.getByLabel("Import configuration JSON").fill(JSON.stringify(namedFile)); await page.getByRole("button", { name: "Import configuration", exact: true }).click();
31
- await expect(role).toHaveValue("Inventory & stock"); await expect(offline).not.toBeChecked();
32
- expect(await page.evaluate(() => document.documentElement.scrollWidth <= window.innerWidth)).toBe(true);
33
- });
34
-
35
- test(`Databricks OAuth flow changes keep file fields, permissions and account identity consistent at ${width}px`, async ({ page }) => {
36
- await page.setViewportSize({ width, height: 1000 });
37
- await page.goto("/");
38
- await page.getByRole("button", { name: "Edit Databricks", exact: true }).click();
39
- const flow = page.getByRole("combobox", { name: "OAuth flow", exact: true });
40
- const account = page.getByRole("combobox", { name: "Account used by the application", exact: true });
41
- const callback = page.getByRole("textbox", { name: "Callback URL reference", exact: true });
42
- const client = page.getByRole("textbox", { name: "Client ID", exact: true });
43
- const save = page.getByRole("button", { name: "Export configuration", exact: true });
44
- const permissions = page.getByRole("button", { name: "Permissions", exact: true });
45
- await expect(flow).toHaveValue("User consent"); await expect(account).toHaveValue("Each app user's own account");
46
- await expect(callback).toHaveValue("env:DATABRICKS_CALLBACK");
47
- await save.click(); const initial = JSON.parse(await page.getByTestId("export").textContent());
48
- await page.getByText("User consent", { exact: true }).click();
49
- await page.getByRole("option", { name: "Service account", exact: true }).click();
50
- await expect(callback).toHaveCount(0); await expect(account).toHaveValue("One shared account");
51
- await page.getByText("One shared account", { exact: true }).click();
52
- await expect(page.getByRole("option", { name: "Each app user's own account", exact: true })).toHaveCount(0);
53
- await page.keyboard.press("Escape");
54
- await client.fill("service-client");
55
- await permissions.click();
56
- const jobs = page.getByRole("checkbox", { name: "Jobs API access for the service account", exact: true });
57
- await jobs.check();
58
- await expect(page.getByRole("checkbox", { name: "Keep user access between visits", exact: true })).toHaveCount(0);
59
- await save.click(); const machine = JSON.parse(await page.getByTestId("export").textContent());
60
- expect(machine.registrations.databricks).toEqual({ source: "own", clientId: "service-client", clientSecretRef: "env:DATABRICKS_SECRET",
61
- grantType: "client_credentials", tokenEndpointAuthMethod: "client_secret_basic" });
62
- expect(machine.integrations.databricks.scopes).toEqual(["all-apis", "jobs"]);
63
- expect(machine.integrations.databricks.accountMode).toBe("shared");
64
- expect(machine.integrations.databricks.extensions).toEqual({ keep: "workspace" });
65
- expect(machine.integrations.calendar).toEqual(initial.integrations.calendar);
66
- expect(machine.registrations.google).toEqual(initial.registrations.google);
67
- await page.getByLabel("Lock form").check(); await expect(flow).toBeDisabled(); await expect(jobs).toBeDisabled();
68
- await page.getByLabel("Lock form").uncheck();
69
- await page.getByRole("button", { name: "Leave configuration" }).click();
70
- await page.getByRole("button", { name: "Return to configuration" }).click();
71
- await expect(flow).toHaveValue("Service account"); await expect(callback).toHaveCount(0);
72
- await page.getByText("Service account", { exact: true }).click();
73
- await page.getByRole("option", { name: "User consent", exact: true }).click();
74
- await expect(callback).toHaveValue("");
75
- await save.click();
76
- await expect(page.locator(".v-input").filter({ has: callback }).getByText("This value is required.", { exact: true })).toBeVisible();
77
- await callback.fill("env:USER_CALLBACK"); await client.fill("user-client");
78
- if (await permissions.getAttribute("aria-expanded") !== "true") await permissions.click();
79
- await expect(jobs).toHaveCount(0);
80
- await page.getByRole("checkbox", { name: "Keep user access between visits", exact: true }).check();
81
- await save.click(); const user = JSON.parse(await page.getByTestId("export").textContent());
82
- expect(user.registrations.databricks.tokenEndpointAuthMethod).toBe("client_secret_post");
83
- expect(user.integrations.databricks.scopes).toEqual(["all-apis", "offline_access"]);
84
- const invalid = structuredClone(machine); invalid.integrations.databricks.accountMode = "per-user";
85
- await page.getByLabel("Import configuration JSON").fill(JSON.stringify(invalid));
86
- await page.getByRole("button", { name: "Import configuration", exact: true }).click();
87
- await expect(page.getByText("A service account cannot connect as each app user.", { exact: true })).toBeVisible();
88
- await expect(flow).toHaveValue("User consent");
89
- await page.getByLabel("Import configuration JSON").fill(JSON.stringify(machine));
90
- await page.getByRole("button", { name: "Import configuration", exact: true }).click();
91
- await expect(flow).toHaveValue("Service account"); await expect(callback).toHaveCount(0);
92
- expect(await page.evaluate(() => document.documentElement.scrollWidth <= window.innerWidth)).toBe(true);
93
- });
94
-
95
- test(`Redshift deployment changes share CLI validation and remove inactive fields at ${width}px`, async ({ page }) => {
96
- await page.setViewportSize({ width, height: 1000 });
97
- await page.goto("/");
98
- await page.getByRole("button", { name: "Edit Redshift", exact: true }).click();
99
- const mode = page.getByRole("combobox", { name: "Deployment type", exact: true });
100
- const workgroup = page.getByRole("textbox", { name: "Workgroup name", exact: true });
101
- const cluster = page.getByRole("textbox", { name: "Cluster identifier", exact: true });
102
- const database = page.getByRole("textbox", { name: "Database", exact: true });
103
- const user = page.getByRole("textbox", { name: "Database user (optional)", exact: true });
104
- const save = page.getByRole("button", { name: "Export configuration", exact: true });
105
- await expect(mode).toHaveValue("Serverless");
106
- await expect(workgroup).toHaveValue("analytics"); await expect(cluster).toHaveCount(0); await expect(user).toHaveCount(0);
107
- await save.click();
108
- await expect(page.getByTestId("export")).toContainText('"workgroup": "analytics"');
109
- const original = JSON.parse(await page.getByTestId("export").textContent());
110
- await page.getByText("Serverless", { exact: true }).click();
111
- await page.getByRole("option", { name: "Provisioned cluster", exact: true }).click();
112
- await expect(workgroup).toHaveCount(0); await expect(cluster).toHaveValue(""); await expect(database).toHaveValue("dev");
113
- await save.click();
114
- await expect(page.getByTestId("export")).toHaveText(JSON.stringify(original, null, 2));
115
- await cluster.fill("analytics-cluster"); await user.fill("report_reader"); await save.click();
116
- await expect(page.getByTestId("export")).toContainText('"databaseUser": "report_reader"');
117
- const provisioned = JSON.parse(await page.getByTestId("export").textContent());
118
- expect(provisioned.integrations.redshift.settings).toEqual({ deploymentType: "provisioned", region: "us-east-1", database: "dev", accessKeyIdRef: "env:AWS_ACCESS_KEY_ID", clusterIdentifier: "analytics-cluster", databaseUser: "report_reader" });
119
- expect(provisioned.integrations.redshift.extensions).toEqual({ keep: "redshift" });
120
- expect(provisioned.integrations.calendar).toEqual(original.integrations.calendar);
121
- await page.getByRole("button", { name: "Leave configuration", exact: true }).click();
122
- await page.getByRole("button", { name: "Return to configuration", exact: true }).click();
123
- await expect(mode).toHaveValue("Provisioned cluster"); await expect(cluster).toHaveValue("analytics-cluster");
124
- await page.getByLabel("Lock form").check(); await expect(mode).toBeDisabled(); await page.getByLabel("Lock form").uncheck();
125
- await page.getByText("Provisioned cluster", { exact: true }).click();
126
- await page.getByRole("option", { name: "Serverless", exact: true }).click();
127
- await expect(cluster).toHaveCount(0); await expect(user).toHaveCount(0); await expect(workgroup).toHaveValue("");
128
- await workgroup.fill("restored-group"); await expect(workgroup).toHaveValue("restored-group"); await save.click();
129
- await expect(page.getByTestId("export")).toContainText('"workgroup": "restored-group"');
130
- const restored = JSON.parse(await page.getByTestId("export").textContent());
131
- expect(restored.integrations.redshift.settings).toEqual({ deploymentType: "serverless", region: "us-east-1", database: "dev", accessKeyIdRef: "env:AWS_ACCESS_KEY_ID", workgroup: "restored-group" });
132
- const mixed = structuredClone(restored); mixed.integrations.redshift.settings.clusterIdentifier = "mixed";
133
- await page.getByLabel("Import configuration JSON").fill(JSON.stringify(mixed));
134
- await page.getByRole("button", { name: "Import configuration", exact: true }).click();
135
- await expect(workgroup).toHaveValue("restored-group"); await expect(cluster).toHaveCount(0);
136
- expect(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth)).toBe(true);
137
- await expect(page.locator(".v-progress-circular")).toHaveCount(0);
138
- });
139
- test(`Slack actor changes retain applicable permissions and reject invalid imports at ${width}px`, async ({ page }) => {
140
- await page.setViewportSize({ width, height: 1000 });
141
- await page.goto("/");
142
- await page.getByRole("button", { name: "Edit Slack", exact: true }).click();
143
- const actor = page.getByRole("combobox", { name: "Act in Slack as", exact: true });
144
- const permissions = page.getByRole("button", { name: "Permissions", exact: true });
145
- const save = page.getByRole("button", { name: "Export configuration", exact: true });
146
- const read = page.getByRole("checkbox", { name: "View basic information about public channels in a workspace", exact: true });
147
- const profile = page.getByRole("checkbox", { name: "Edit a user's profile information and status", exact: true });
148
- const join = page.getByRole("checkbox", { name: "Join public channels in a workspace", exact: true });
149
- if (await permissions.getAttribute("aria-expanded") !== "true") await permissions.click();
150
- await expect(page.locator(".v-expansion-panel-text").getByRole("checkbox")).toHaveCount(52);
151
- await expect(read).toBeChecked();
152
- await profile.check();
153
- await page.getByText("Connected user", { exact: true }).click();
154
- await page.getByRole("option", { name: "Installed bot", exact: true }).click();
155
- await expect(page.locator(".v-expansion-panel-text").getByRole("checkbox")).toHaveCount(49);
156
- await expect(profile).toHaveCount(0);
157
- await expect(read).toBeChecked();
158
- await join.check();
159
- await save.click();
160
- const bot = JSON.parse(await page.getByTestId("export").textContent());
161
- expect(bot.integrations.slack.scopes).toEqual(["channels:read", "channels:join"]);
162
- expect(bot.integrations.slack.settings).toEqual({ actor: "bot" });
163
- expect(bot.integrations.slack.extensions).toEqual({ keep: "workspace" });
164
- await page.getByLabel("Lock form").check();
165
- await expect(actor).toBeDisabled();
166
- await expect(join).toBeDisabled();
167
- await page.getByLabel("Lock form").uncheck();
168
- await page.getByRole("button", { name: "Leave configuration" }).click();
169
- await page.getByRole("button", { name: "Return to configuration" }).click();
170
- await expect(actor).toHaveValue("Installed bot");
171
- await page.getByText("Installed bot", { exact: true }).click();
172
- await page.getByRole("option", { name: "Connected user", exact: true }).click();
173
- await save.click();
174
- const user = JSON.parse(await page.getByTestId("export").textContent());
175
- expect(user.integrations.slack.scopes).toEqual(["channels:read"]);
176
- expect(user.integrations.calendar).toEqual(bot.integrations.calendar);
177
- const invalid = structuredClone(bot);
178
- invalid.integrations.slack.scopes.push("users.profile:write");
179
- await page.getByLabel("Import configuration JSON").fill(JSON.stringify(invalid));
180
- await page.getByRole("button", { name: "Import configuration", exact: true }).click();
181
- if (await permissions.getAttribute("aria-expanded") !== "true") await permissions.click();
182
- await expect(page.getByText("A selected permission is not supported by this provider configuration.", { exact: true })).toBeVisible();
183
- await expect(actor).toHaveValue("Connected user");
184
- await page.getByLabel("Import configuration JSON").fill(JSON.stringify(bot));
185
- await page.getByRole("button", { name: "Import configuration", exact: true }).click();
186
- await expect(actor).toHaveValue("Installed bot");
187
- if (await permissions.getAttribute("aria-expanded") !== "true") await permissions.click();
188
- await expect(join).toBeChecked();
189
- expect(await page.evaluate(() => document.documentElement.scrollWidth <= window.innerWidth)).toBe(true);
190
- });
191
-
192
- test(`ClickHouse authentication modes preserve portable configuration at ${width}px`, async ({ page }) => {
193
- await page.setViewportSize({ width, height: 1000 });
194
- await page.goto("/");
195
- await page.getByRole("button", { name: "Edit ClickHouse", exact: true }).click();
196
- const password = page.getByRole("textbox", { name: "Password reference (optional)", exact: true });
197
- const username = page.getByRole("textbox", { name: "Username (optional)", exact: true });
198
- const endpoint = page.getByRole("textbox", { name: "HTTP Interface URL", exact: true });
199
- const authentication = page.getByRole("combobox", { name: "Authentication", exact: true });
200
- const save = page.getByRole("button", { name: "Export configuration", exact: true });
201
- await expect(username).toHaveValue("reader");
202
- await password.fill("raw-password");
203
- await save.click();
204
- await expect(page.getByText("Use a reference such as env:VARIABLE_NAME.", { exact: true })).toBeVisible();
205
- await password.fill("");
206
- await save.click();
207
- const value = JSON.parse(await page.getByTestId("export").textContent());
208
- expect(value.integrations.warehouse.authentication).toEqual({ method: "api-key" });
209
- await page.getByText("Username and password", { exact: true }).click();
210
- await page.getByRole("option", { name: "No credentials", exact: true }).click();
211
- await expect(password).toHaveCount(0);
212
- await expect(username).toHaveCount(0);
213
- await save.click();
214
- const anonymous = JSON.parse(await page.getByTestId("export").textContent());
215
- expect(anonymous.integrations.warehouse.authentication).toEqual({ method: "none" });
216
- expect(anonymous.integrations.warehouse.settings).toEqual({ httpUrl: "https://warehouse.example:8443/query/" });
217
- expect(anonymous.integrations.warehouse.extensions).toEqual({ keep: "database" });
218
- expect(anonymous.integrations.calendar).toEqual(value.integrations.calendar);
219
- await page.getByLabel("Lock form").check();
220
- await expect(authentication).toBeDisabled();
221
- await expect(endpoint).toBeDisabled();
222
- await page.getByLabel("Lock form").uncheck();
223
- await page.getByRole("button", { name: "Leave configuration" }).click();
224
- await page.getByRole("button", { name: "Return to configuration" }).click();
225
- await expect(page.getByText("No credentials", { exact: true })).toBeVisible();
226
- await expect(endpoint).toHaveValue("https://warehouse.example:8443/query/");
227
- await page.getByText("No credentials", { exact: true }).click();
228
- await page.getByRole("option", { name: "Username and password", exact: true }).click();
229
- await expect(username).toHaveValue("");
230
- await expect(password).toHaveValue("");
231
- value.integrations.warehouse.authentication.secretRef = "env:CLI_PASSWORD";
232
- value.integrations.warehouse.settings.username = "cli-reader";
233
- await page.getByLabel("Import configuration JSON").fill(JSON.stringify(value));
234
- await page.getByRole("button", { name: "Import configuration", exact: true }).click();
235
- await expect(username).toHaveValue("cli-reader");
236
- await expect(password).toHaveValue("env:CLI_PASSWORD");
237
- expect(await page.evaluate(() => document.documentElement.scrollWidth <= window.innerWidth)).toBe(true);
238
- });
239
-
240
- test(`configuration remains portable and usable at ${width}px`, async ({ page }) => {
241
- await page.setViewportSize({ width, height: 1000 });
242
- await page.goto("/");
243
- await page.getByLabel("Display name").fill("My calendar");
244
- await page.getByLabel("Client ID", { exact: true }).fill("updated-client");
245
- await page.getByRole("button", { name: "Permissions", exact: true }).click();
246
- await page.getByLabel("Read events", { exact: true }).uncheck();
247
- await page.getByRole("button", { name: "Export configuration", exact: true }).click();
248
- await expect(page.getByTestId("export")).toContainText("updated-client");
249
- const value = JSON.parse(await page.getByTestId("export").textContent());
250
- expect(value.integrations.calendar.displayName).toBe("My calendar");
251
- expect(value.integrations.calendar.extensions.businessRule).toBe("keep-me");
252
- expect(value.integrations.calendar.scopes).toHaveLength(1);
253
-
254
- await page.getByRole("button", { name: "Leave configuration" }).click();
255
- await page.getByRole("button", { name: "Return to configuration" }).click();
256
- await expect(page.getByLabel("Display name")).toHaveValue("My calendar");
257
-
258
- const obsolete = structuredClone(value);
259
- obsolete.registrations.google.source = "managed";
260
- obsolete.registrations.google.clientId = "must-not-be-imported";
261
- await page.getByLabel("Import configuration JSON").fill(JSON.stringify(obsolete));
262
- await page.getByRole("button", { name: "Import configuration", exact: true }).click();
263
- await expect(page.getByLabel("Client ID", { exact: true })).toHaveValue("updated-client");
264
- await expect(page.getByLabel("Managed registration", { exact: true })).toHaveCount(0);
265
- await page.getByRole("button", { name: "Export configuration", exact: true }).click();
266
- expect(JSON.parse(await page.getByTestId("export").textContent()).registrations.google.source).toBe("own");
267
-
268
- value.registrations.google.clientId = "written-from-cli";
269
- await page.getByLabel("Import configuration JSON").fill(JSON.stringify(value));
270
- await page.getByRole("button", { name: "Import configuration", exact: true }).click();
271
- await expect(page.getByLabel("Client ID", { exact: true })).toHaveValue("written-from-cli");
272
-
273
- await page.getByLabel("Display name").fill("");
274
- await page.getByRole("button", { name: "Export configuration", exact: true }).click();
275
- expect(JSON.parse(await page.getByTestId("export").textContent()).integrations.calendar).not.toHaveProperty("displayName");
276
- await page.getByLabel("Lock form").check();
277
- await expect(page.getByLabel("Client ID", { exact: true })).toBeDisabled();
278
- await page.getByLabel("Lock form").uncheck();
279
-
280
- await page.getByLabel("Client secret reference").fill("an-actual-secret-is-not-a-reference");
281
- await page.getByRole("button", { name: "Export configuration", exact: true }).click();
282
- await expect(page.getByText("Use a reference such as env:VARIABLE_NAME.", { exact: true })).toBeVisible();
283
- await page.getByLabel("Client secret reference").fill("env:GOOGLE_SECRET");
284
- await page.getByLabel("Callback URL reference").fill("https://callback.example.test/oauth");
285
- await page.getByRole("button", { name: "Export configuration", exact: true }).click();
286
- await expect(page.locator(".v-input").filter({ has: page.getByLabel("Callback URL reference") }).getByText("Use a reference such as env:VARIABLE_NAME.", { exact: true })).toBeVisible();
287
- await page.getByLabel("Callback URL reference").fill("env:CALLBACK");
288
- await page.getByRole("button", { name: "Edit Mailgun", exact: true }).click();
289
- await expect(page.getByText("United States (api.mailgun.net)", { exact: true })).toBeVisible();
290
- const region = page.getByRole("combobox", { name: "API region", exact: true });
291
- await page.getByText("United States (api.mailgun.net)", { exact: true }).click();
292
- await page.getByRole("option", { name: "European Union (api.eu.mailgun.net)", exact: true }).click();
293
- await page.getByRole("button", { name: "Export configuration", exact: true }).click();
294
- const regional = JSON.parse(await page.getByTestId("export").textContent());
295
- expect(regional.integrations.mail.settings).toEqual({ region: "eu" });
296
- expect(regional.integrations.mail.extensions).toEqual({ label: "preserve" });
297
- await page.getByLabel("Lock form").check();
298
- await expect(region).toBeDisabled();
299
- await page.getByLabel("Lock form").uncheck();
300
- await page.getByRole("button", { name: "Leave configuration" }).click();
301
- await page.getByRole("button", { name: "Return to configuration" }).click();
302
- await expect(page.getByText("European Union (api.eu.mailgun.net)", { exact: true })).toBeVisible();
303
- await page.getByRole("button", { name: "Edit Algolia", exact: true }).click();
304
- const applicationId = page.getByRole("textbox", { name: "Application ID", exact: true });
305
- const publicKey = page.getByRole("textbox", { name: "Public API key reference (optional)", exact: true });
306
- await expect(applicationId).toHaveValue("ORIGINALAPP");
307
- await applicationId.fill("app.invalid");
308
- await page.getByRole("button", { name: "Export configuration", exact: true }).click();
309
- await expect(page.getByText("Enter the application ID using letters and digits.", { exact: true })).toBeVisible();
310
- await applicationId.fill("");
311
- await page.getByRole("button", { name: "Export configuration", exact: true }).click();
312
- await expect(page.locator(".v-input--error")).toHaveCount(1);
313
- await applicationId.fill("UPDATEDAPP");
314
- await publicKey.fill("raw-search-key");
315
- await page.getByRole("button", { name: "Export configuration", exact: true }).click();
316
- await expect(page.getByText("Use a reference such as env:VARIABLE_NAME.", { exact: true })).toBeVisible();
317
- await publicKey.fill("env:ALGOLIA_SEARCH_KEY");
318
- await page.getByRole("button", { name: "Export configuration", exact: true }).click();
319
- const search = JSON.parse(await page.getByTestId("export").textContent());
320
- expect(search.integrations.search.settings).toEqual({ applicationId: "UPDATEDAPP", publicApiKeyRef: "env:ALGOLIA_SEARCH_KEY" });
321
- expect(search.integrations.search.authentication.secretRef).toBe("env:ALGOLIA_BACKEND_KEY");
322
- expect(search.integrations.search.extensions).toEqual({ keep: true });
323
- await page.getByLabel("Lock form").check();
324
- await expect(applicationId).toBeDisabled();
325
- await expect(publicKey).toBeDisabled();
326
- await page.getByLabel("Lock form").uncheck();
327
- await page.getByRole("button", { name: "Leave configuration" }).click();
328
- await page.getByRole("button", { name: "Return to configuration" }).click();
329
- await expect(publicKey).toHaveValue("env:ALGOLIA_SEARCH_KEY");
330
- await publicKey.fill("");
331
- await page.getByRole("button", { name: "Export configuration", exact: true }).click();
332
- expect(JSON.parse(await page.getByTestId("export").textContent()).integrations.search.settings).toEqual({ applicationId: "UPDATEDAPP" });
333
- search.integrations.search.settings.applicationId = "CLIAPP";
334
- await page.getByLabel("Import configuration JSON").fill(JSON.stringify(search));
335
- await page.getByRole("button", { name: "Import configuration", exact: true }).click();
336
- await expect(applicationId).toHaveValue("CLIAPP");
337
- await expect(publicKey).toHaveValue("env:ALGOLIA_SEARCH_KEY");
338
- expect(await page.evaluate(() => document.documentElement.scrollWidth <= window.innerWidth)).toBe(true);
339
- const smallButtons = await page.getByRole("button").evaluateAll((buttons) => buttons.filter((button) => {
340
- const box = button.getBoundingClientRect();
341
- return box.width > 0 && box.height > 0 && (box.width < 48 || box.height < 48);
342
- }).map((button) => button.textContent));
343
- expect(smallButtons).toEqual([]);
344
- });
345
- }
346
-
347
-
348
- test("Contentful delivery fields preserve UI and CLI configuration", async ({ page }) => {
349
- await page.setViewportSize({ width: 390, height: 1000 });
350
- await page.goto("/");
351
- await page.getByRole("button", { name: "Edit Contentful", exact: true }).click();
352
- const space = page.getByRole("textbox", { name: "Space ID", exact: true });
353
- const environment = page.getByRole("textbox", { name: "Environment ID", exact: true });
354
- const token = page.getByRole("textbox", { name: "Content Delivery API token reference", exact: true });
355
- const save = page.getByRole("button", { name: "Export configuration", exact: true });
356
- await expect(space).toHaveValue("original-space");
357
- await expect(environment).toHaveValue("master");
358
- await space.fill("");
359
- await save.click();
360
- await expect(page.locator(".v-input--error")).toHaveCount(1);
361
- await space.fill("published-space");
362
- await environment.fill("release-2");
363
- await token.fill("raw-token");
364
- await save.click();
365
- await expect(page.getByText("Use a reference such as env:VARIABLE_NAME.", { exact: true })).toBeVisible();
366
- await token.fill("env:CONTENTFUL_ROTATED_TOKEN");
367
- await page.getByText("United States", { exact: true }).click();
368
- await page.getByRole("option", { name: "Europe", exact: true }).click();
369
- await save.click();
370
- const exported = JSON.parse(await page.getByTestId("export").textContent());
371
- expect(exported.integrations.content.settings).toEqual({ spaceId: "published-space", environmentId: "release-2", region: "eu" });
372
- expect(exported.integrations.content.authentication.secretRef).toBe("env:CONTENTFUL_ROTATED_TOKEN");
373
- await page.getByRole("button", { name: "Leave configuration" }).click();
374
- await page.getByRole("button", { name: "Return to configuration" }).click();
375
- await expect(space).toHaveValue("published-space");
376
- await expect(environment).toHaveValue("release-2");
377
- await expect(token).toHaveValue("env:CONTENTFUL_ROTATED_TOKEN");
378
- exported.integrations.content.settings.spaceId = "cli-space";
379
- await page.getByLabel("Import configuration JSON").fill(JSON.stringify(exported));
380
- await page.getByRole("button", { name: "Import configuration", exact: true }).click();
381
- await expect(space).toHaveValue("cli-space");
382
- expect(await page.evaluate(() => document.documentElement.scrollWidth <= window.innerWidth)).toBe(true);
383
- });
384
-
385
-
386
- test("Analytics public setting round-trips without credentials", async ({ page }) => {
387
- await page.setViewportSize({ width: 390, height: 1000 });
388
- await page.goto("/");
389
- await page.getByRole("button", { name: "Edit Analytics", exact: true }).click();
390
- const measurement = page.getByRole("textbox", { name: "Measurement ID", exact: true });
391
- const save = page.getByRole("button", { name: "Export configuration", exact: true });
392
- await expect(measurement).toHaveValue("G-ORIGINAL1");
393
- await expect(page.getByText("Public: this value will be visible in your published application. Copy it from your GA4 web stream.", { exact: true })).toBeVisible();
394
- await expect(page.getByRole("textbox", { name: /secret|callback|client id|API key/i })).toHaveCount(0);
395
- await measurement.fill("");
396
- await save.click();
397
- await expect(page.locator(".v-input--error")).toHaveCount(1);
398
- await measurement.fill("UA-123-1");
399
- await save.click();
400
- await expect(page.locator(".v-input--error")).toHaveCount(1);
401
- await measurement.fill("G-WEBSITE123");
402
- await save.click();
403
- const exported = JSON.parse(await page.getByTestId("export").textContent());
404
- expect(exported.integrations.analytics).toMatchObject({
405
- provider: "google-analytics", accountMode: "shared", scopes: [],
406
- authentication: { method: "none" }, settings: { measurementId: "G-WEBSITE123" }
407
- });
408
- expect(exported.integrations.analytics.authentication).toEqual({ method: "none" });
409
- await page.getByRole("button", { name: "Leave configuration" }).click();
410
- await page.getByRole("button", { name: "Return to configuration" }).click();
411
- await expect(measurement).toHaveValue("G-WEBSITE123");
412
- exported.integrations.analytics.settings.measurementId = "G-CLI123";
413
- await page.getByLabel("Import configuration JSON").fill(JSON.stringify(exported));
414
- await page.getByRole("button", { name: "Import configuration", exact: true }).click();
415
- await expect(measurement).toHaveValue("G-CLI123");
416
- expect(await page.evaluate(() => document.documentElement.scrollWidth <= window.innerWidth)).toBe(true);
417
- });
418
-
419
-
420
- test("Drive captured permissions retain required file access and optional choices", async ({ page }) => {
421
- await page.goto("/");
422
- await page.getByRole("button", { name: "Edit Drive", exact: true }).click();
423
- const required = page.getByRole("checkbox", { name: "Manage files selected for this application", exact: true });
424
- if (!await required.isVisible()) await page.getByRole("button", { name: "Permissions", exact: true }).click();
425
- await expect(required).toBeChecked();
426
- await expect(required).toBeDisabled();
427
- for (const name of ["Read and download files", "Manage application data", "Manage this application’s Drive folder data"]) {
428
- const checkbox = page.getByRole("checkbox", { name, exact: true });
429
- await expect(checkbox).toBeChecked();
430
- await checkbox.uncheck();
431
- }
432
- await page.getByRole("button", { name: "Export configuration", exact: true }).click();
433
- expect(JSON.parse(await page.getByTestId("export").textContent()).integrations.drive.scopes)
434
- .toEqual(["https://www.googleapis.com/auth/drive.file"]);
435
- await page.getByRole("button", { name: "Leave configuration" }).click();
436
- await page.getByRole("button", { name: "Return to configuration" }).click();
437
- if (!await required.isVisible()) await page.getByRole("button", { name: "Permissions", exact: true }).click();
438
- await expect(required).toBeChecked();
439
- await expect(page.getByRole("checkbox", { name: "Read and download files", exact: true })).not.toBeChecked();
440
- });
441
-
442
- test("Gmail captured permissions allow optional sending permissions to be removed", async ({ page }) => {
443
- await page.goto("/");
444
- await page.getByRole("button", { name: "Edit Gmail", exact: true }).click();
445
- const required = page.getByRole("checkbox", { name: "Read messages", exact: true });
446
- if (!await required.isVisible()) await page.getByRole("button", { name: "Permissions", exact: true }).click();
447
- await expect(required).toBeChecked();
448
- await expect(required).toBeDisabled();
449
- for (const name of ["Send messages", "Manage drafts and send messages", "Read and modify messages"]) {
450
- const checkbox = page.getByRole("checkbox", { name, exact: true });
451
- await expect(checkbox).toBeChecked();
452
- await checkbox.uncheck();
453
- }
454
- await page.getByRole("button", { name: "Export configuration", exact: true }).click();
455
- expect(JSON.parse(await page.getByTestId("export").textContent()).integrations.gmail.scopes)
456
- .toEqual(["https://www.googleapis.com/auth/gmail.readonly"]);
457
- await page.getByRole("button", { name: "Leave configuration" }).click();
458
- await page.getByRole("button", { name: "Return to configuration" }).click();
459
- if (!await required.isVisible()) await page.getByRole("button", { name: "Permissions", exact: true }).click();
460
- await expect(required).toBeChecked();
461
- await expect(page.getByRole("checkbox", { name: "Send messages", exact: true })).not.toBeChecked();
462
- });
463
-
464
-
465
- test("BigQuery query project validates and round-trips through CLI configuration", async ({ page }) => {
466
- await page.setViewportSize({ width: 390, height: 1000 });
467
- await page.goto("/");
468
- await page.getByRole("button", { name: "Edit BigQuery", exact: true }).click();
469
- const project = page.getByRole("textbox", { name: "Google Cloud project ID", exact: true });
470
- const save = page.getByRole("button", { name: "Export configuration", exact: true });
471
- await expect(project).toHaveValue("query-project");
472
- for (const value of ["", "123456789", "https://example.com"]) {
473
- await project.fill(value);
474
- await save.click();
475
- await expect(page.locator(".v-input--error")).toHaveCount(1);
476
- }
477
- await project.fill("billing-project");
478
- await save.click();
479
- const exported = JSON.parse(await page.getByTestId("export").textContent());
480
- expect(exported.integrations.bigquery).toMatchObject({
481
- settings: { projectId: "billing-project" },
482
- authentication: { method: "oauth2", registrationRef: "google" },
483
- scopes: ["https://www.googleapis.com/auth/bigquery"]
484
- });
485
- await page.getByRole("button", { name: "Leave configuration" }).click();
486
- await page.getByRole("button", { name: "Return to configuration" }).click();
487
- await expect(project).toHaveValue("billing-project");
488
- exported.integrations.bigquery.settings.projectId = "cli-project";
489
- await page.getByLabel("Import configuration JSON").fill(JSON.stringify(exported));
490
- await page.getByRole("button", { name: "Import configuration", exact: true }).click();
491
- await expect(project).toHaveValue("cli-project");
492
- expect(await page.evaluate(() => document.documentElement.scrollWidth <= window.innerWidth)).toBe(true);
493
- });
@@ -1,126 +0,0 @@
1
- <script setup>
2
- import { computed, ref } from "vue";
3
- import { googleAnalyticsDefinition, googleDriveDefinition, gmailDefinition, bigqueryDefinition } from "../../../connectors-catalog/src/shared/google.js";
4
- import { IntegrationConfigurationFields } from "../../src/client/index.js";
5
- import { googleCalendarDefinition } from "../../../connector-google-calendar/src/shared/definition.js";
6
- import { mailgunDefinition, contentfulDefinition } from "../../../connectors-catalog/src/shared/tokens.js";
7
- import { algoliaDefinition } from "../../../connectors-catalog/src/shared/algolia.js";
8
- import { clickhouseDefinition } from "../../../connectors-catalog/src/shared/clickhouse.js";
9
- import { slackDefinition } from "../../../connectors-catalog/src/shared/slack.js";
10
- import { amazonRedshiftDefinition } from "../../../connectors-catalog/src/shared/amazon-redshift.js";
11
- import { snowflakeDefinition } from "../../../connectors-catalog/src/shared/snowflake.js";
12
- import { databricksDefinition } from "../../../connectors-catalog/src/shared/databricks.js";
13
- import { parseIntegrationConfiguration, validateIntegrationConfiguration } from "../../../connectors-core/src/shared/configuration.js";
14
-
15
- const configuration = ref({
16
- schemaVersion: 1,
17
- integrations: { bigquery: {
18
- provider: "bigquery", accountMode: "shared",
19
- scopes: bigqueryDefinition.scopes.filter((scope) => scope.recommended).map((scope) => scope.value),
20
- authentication: { method: "oauth2", registrationRef: "google" }, settings: { projectId: "query-project" }
21
- }, gmail: {
22
- provider: "gmail", accountMode: "shared",
23
- scopes: gmailDefinition.scopes.filter((scope) => scope.recommended).map((scope) => scope.value),
24
- authentication: { method: "oauth2", registrationRef: "google" }
25
- }, drive: {
26
- provider: "google-drive", accountMode: "shared",
27
- scopes: googleDriveDefinition.scopes.filter((scope) => scope.recommended).map((scope) => scope.value),
28
- authentication: { method: "oauth2", registrationRef: "google" }
29
- }, analytics: {
30
- provider: "google-analytics", accountMode: "shared", scopes: [],
31
- authentication: { method: "none" }, settings: { measurementId: "G-ORIGINAL1" }
32
- }, content: {
33
- provider: "contentful", accountMode: "shared", scopes: [], settings: { spaceId: "original-space" },
34
- authentication: { method: "api-key", secretRef: "env:CONTENTFUL_DELIVERY_TOKEN" }
35
- }, calendar: {
36
- provider: "google-calendar", displayName: "Team calendar", accountMode: "per-user",
37
- scopes: googleCalendarDefinition.scopes.filter((scope) => scope.recommended).map((scope) => scope.value),
38
- authentication: { method: "oauth2", registrationRef: "google" },
39
- extensions: { businessRule: "keep-me" }
40
- }, mail: {
41
- provider: "mailgun", accountMode: "shared", scopes: [],
42
- authentication: { method: "api-key", secretRef: "env:MAILGUN_KEY" }, extensions: { label: "preserve" }
43
- }, search: {
44
- provider: "algolia", accountMode: "shared", scopes: [],
45
- authentication: { method: "api-key", secretRef: "env:ALGOLIA_BACKEND_KEY" },
46
- settings: { applicationId: "ORIGINALAPP" }, extensions: { keep: true }
47
- }, warehouse: {
48
- provider: "clickhouse", accountMode: "shared", scopes: [],
49
- authentication: { method: "api-key", secretRef: "env:CLICKHOUSE_PASSWORD" },
50
- settings: { httpUrl: "https://warehouse.example:8443/query/", username: "reader" }, extensions: { keep: "database" }
51
- }, slack: {
52
- provider: "slack", accountMode: "per-user", scopes: ["channels:read"], settings: { actor: "user" },
53
- authentication: { method: "oauth2", registrationRef: "slack" }, extensions: { keep: "workspace" }
54
- }, redshift: {
55
- provider: "amazon-redshift", accountMode: "shared", scopes: [],
56
- authentication: { method: "api-key", secretRef: "env:AWS_SECRET_ACCESS_KEY" },
57
- settings: { workgroup: "analytics", database: "dev", accessKeyIdRef: "env:AWS_ACCESS_KEY_ID" }, extensions: { keep: "redshift" }
58
- }, snowflake: {
59
- provider: "snowflake", accountMode: "per-user", scopes: ["refresh_token"],
60
- authentication: { method: "oauth2", registrationRef: "snowflake" },
61
- settings: { accountUrl: "https://myorg-myaccount.snowflakecomputing.com" }, extensions: { keep: "account" }
62
- }, databricks: {
63
- provider: "databricks", accountMode: "per-user", scopes: ["all-apis", "offline_access"],
64
- authentication: { method: "oauth2", registrationRef: "databricks" },
65
- settings: { workspaceUrl: "https://dbc-abc123.cloud.databricks.com" }, extensions: { keep: "workspace" }
66
- } },
67
- registrations: {
68
- snowflake: { source: "own", clientId: "snowflake-client", clientSecretRef: "env:SNOWFLAKE_SECRET", callbackUrlRef: "env:SNOWFLAKE_CALLBACK" },
69
- google: { source: "own", clientId: "original-client", clientSecretRef: "env:GOOGLE_SECRET", callbackUrlRef: "env:CALLBACK" },
70
- slack: { source: "own", clientId: "slack-client", clientSecretRef: "env:SLACK_SECRET", callbackUrlRef: "env:SLACK_CALLBACK" },
71
- databricks: { source: "own", clientId: "user-client", clientSecretRef: "env:DATABRICKS_SECRET", callbackUrlRef: "env:DATABRICKS_CALLBACK" }
72
- }
73
- });
74
- const visible = ref(true);
75
- const errors = ref({});
76
- const exported = ref("");
77
- const source = ref("");
78
- const locked = ref(false);
79
- const activeId = ref("calendar");
80
- const providers = [bigqueryDefinition, gmailDefinition, googleDriveDefinition, googleAnalyticsDefinition, contentfulDefinition, snowflakeDefinition, googleCalendarDefinition, mailgunDefinition, algoliaDefinition, clickhouseDefinition, slackDefinition, amazonRedshiftDefinition, databricksDefinition];
81
- const provider = computed(() => providers.find((entry) => entry.id === configuration.value.integrations[activeId.value].provider));
82
- function save() {
83
- try {
84
- const validated = validateIntegrationConfiguration(configuration.value, { providers });
85
- errors.value = {};
86
- exported.value = JSON.stringify(validated, null, 2);
87
- } catch (error) { errors.value = error.fieldErrors; }
88
- }
89
- function importSource() {
90
- try {
91
- configuration.value = parseIntegrationConfiguration(source.value, { providers });
92
- errors.value = {};
93
- } catch (error) { errors.value = error.fieldErrors; }
94
- }
95
- </script>
96
-
97
- <template>
98
- <v-app>
99
- <v-main>
100
- <v-container style="max-width: 900px">
101
- <v-btn class="mb-4" min-height="48" @click="visible = !visible">{{ visible ? 'Leave configuration' : 'Return to configuration' }}</v-btn>
102
- <v-switch v-model="locked" label="Lock form" />
103
- <v-btn class="mb-4" min-height="48" @click="activeId = 'content'">Edit Contentful</v-btn>
104
- <v-btn class="mb-4" min-height="48" @click="activeId = 'analytics'">Edit Analytics</v-btn>
105
- <v-btn class="mb-4" min-height="48" @click="activeId = 'drive'">Edit Drive</v-btn>
106
- <v-btn class="mb-4" min-height="48" @click="activeId = 'gmail'">Edit Gmail</v-btn>
107
- <v-btn class="mb-4" min-height="48" @click="activeId = 'bigquery'">Edit BigQuery</v-btn>
108
- <v-btn class="mb-4" min-height="48" @click="activeId = 'mail'">Edit Mailgun</v-btn>
109
- <v-btn class="mb-4" min-height="48" @click="activeId = 'search'">Edit Algolia</v-btn>
110
- <v-btn class="mb-4" min-height="48" @click="activeId = 'warehouse'">Edit ClickHouse</v-btn>
111
- <v-btn class="mb-4" min-height="48" @click="activeId = 'slack'">Edit Slack</v-btn>
112
- <v-btn class="mb-4" min-height="48" @click="activeId = 'redshift'">Edit Redshift</v-btn>
113
- <v-btn class="mb-4" min-height="48" @click="activeId = 'databricks'">Edit Databricks</v-btn>
114
- <v-btn class="mb-4" min-height="48" @click="activeId = 'snowflake'">Edit Snowflake</v-btn>
115
- <IntegrationConfigurationFields
116
- v-if="visible" v-model="configuration" :integration-id="activeId"
117
- :provider="provider" :field-errors="errors" :disabled="locked"
118
- />
119
- <v-btn class="my-4" min-height="48" color="primary" @click="save">Export configuration</v-btn>
120
- <v-textarea v-model="source" label="Import configuration JSON" />
121
- <v-btn min-height="48" @click="importSource">Import configuration</v-btn>
122
- <pre data-testid="export" class="mt-4" style="white-space: pre-wrap; overflow-wrap: anywhere">{{ exported }}</pre>
123
- </v-container>
124
- </v-main>
125
- </v-app>
126
- </template>
@@ -1,2 +0,0 @@
1
- <!doctype html>
2
- <html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Integration configuration test</title></head><body><div id="app"></div><script type="module" src="/main.js"></script></body></html>
@@ -1,8 +0,0 @@
1
- import { createApp } from "vue";
2
- import { createVuetify } from "vuetify";
3
- import * as components from "vuetify/components";
4
- import * as directives from "vuetify/directives";
5
- import "vuetify/styles";
6
- import App from "./App.vue";
7
-
8
- createApp(App).use(createVuetify({ components, directives })).mount("#app");
@@ -1,18 +0,0 @@
1
- import { defineConfig } from "@playwright/test";
2
-
3
- const managedUrl = process.env.PLAYWRIGHT_BASE_URL;
4
- export default defineConfig({
5
- testDir: ".",
6
- testMatch: "configuration.spec.js",
7
- workers: 1,
8
- use: {
9
- baseURL: managedUrl || "http://127.0.0.1:4187",
10
- storageState: process.env.VIBE64_PLAYWRIGHT_STORAGE_STATE || undefined
11
- },
12
- webServer: managedUrl ? undefined : {
13
- command: "npm exec --no -- vite --config packages/connectors-web/test/vite.config.js",
14
- cwd: new URL("../../../", import.meta.url).pathname,
15
- url: "http://127.0.0.1:4187",
16
- reuseExistingServer: false
17
- }
18
- });
@@ -1,9 +0,0 @@
1
- import { defineConfig } from "vite";
2
- import vue from "@vitejs/plugin-vue";
3
- import { fileURLToPath } from "node:url";
4
-
5
- export default defineConfig({
6
- root: fileURLToPath(new URL("./fixture", import.meta.url)),
7
- plugins: [vue()],
8
- server: { host: "127.0.0.1", port: 4187, strictPort: true }
9
- });
@@ -1,4 +0,0 @@
1
- {
2
- "status": "passed",
3
- "failedTests": []
4
- }