History

Keep fanned-out presets out of the custom Graph bundle

When presets are checked and custom scopes entered in the same submit, the workloads already fan out through addWorkloads, so the legacy addGraph call now carries only the custom scopes. Empty presetIds with custom scopes no longer falls back to the default workload selection.

Rhys Sullivan committed 4 months ago

Commit e0009531 parent 75d5c0c4 files · +76 −12Browse files

Unified · Split

4 files
  1. packages/plugins/microsoft/src/react/AddMicrosoftSource.test.ts +22 −0
  2. packages/plugins/microsoft/src/react/AddMicrosoftSource.tsx +20 −10
  3. packages/plugins/microsoft/src/sdk/graph.ts +7 −2
  4. packages/plugins/microsoft/src/sdk/plugin.test.ts +27 −0
packages/plugins/microsoft/src/react/AddMicrosoftSource.test.ts+22−0
@@ -7,6 +7,7 @@ import type { ReactElement, ReactNode } from "react"; · 6 unmodified lines
77 import {
88 MicrosoftWorkloadResultPanel,
99 microsoftAddWorkloadsPayload,
10+ microsoftCustomGraphPayload,
1011 submitMicrosoftWorkloadsSelection,
1112 type AddMicrosoftWorkloadsMutation,
1213 } from "./AddMicrosoftSource";
@@ -169,4 +170,25 @@ describe("AddMicrosoftSource per-workload submit", () => { · 156 unmodified lines
169170 ],
170171 });
171172 });
173+
174+ it("mixed flow: the custom Graph payload carries only custom scopes, never the fanned-out presets", () => {
175+ // Checked workloads go through addWorkloads as their own integrations in
176+ // the same submit; if the custom addGraph payload repeated their preset
177+ // ids, the custom bundle would duplicate the same tools.
178+ expect(
179+ microsoftCustomGraphPayload({
180+ customScopes: ["Sites.Read.All", "Custom.Scope"],
181+ slug: "microsoft_graph_custom",
182+ name: "Custom Microsoft Graph",
183+ description: "Custom Microsoft Graph scopes.",
184+ baseUrl: "",
185+ }),
186+ ).toEqual({
187+ presetIds: [],
188+ customScopes: ["Sites.Read.All", "Custom.Scope"],
189+ slug: "microsoft_graph_custom",
190+ name: "Custom Microsoft Graph",
191+ description: "Custom Microsoft Graph scopes.",
192+ });
193+ });
172194 });
packages/plugins/microsoft/src/react/AddMicrosoftSource.tsx+20−10
@@ -273,14 +273,25 @@ type MicrosoftGraphAddPayload = MicrosoftGraphConfig & { · 272 unmodified lines
273273 readonly presetIds: readonly string[];
274274 };
275275
276−const microsoftGraphPayload = (config: MicrosoftGraphAddPayload): MicrosoftGraphAddPayload => {
277− const baseUrl = config.baseUrl?.trim() ?? "";
278− const description = config.description?.trim() ?? "";
276+// The payload for the legacy addGraph call that carries the custom scopes.
277+// The checked workloads already fan out as their own integrations through
278+// addWorkloads in the same submit, so the custom bundle must carry only the
279+// custom scopes (presetIds stays empty): folding the preset ids in again
280+// would create a second integration containing the same tools.
281+export const microsoftCustomGraphPayload = (input: {
282+ readonly customScopes: readonly string[];
283+ readonly slug: string;
284+ readonly name: string;
285+ readonly description: string;
286+ readonly baseUrl: string;
287+}): MicrosoftGraphAddPayload => {
288+ const baseUrl = input.baseUrl.trim();
289+ const description = input.description.trim();
279290 return {
280− presetIds: config.presetIds,
281− ...(config.customScopes !== undefined ? { customScopes: config.customScopes } : {}),
282− ...(config.slug !== undefined ? { slug: config.slug } : {}),
283− ...(config.name !== undefined ? { name: config.name } : {}),
291+ presetIds: [],
292+ customScopes: [...input.customScopes],
293+ slug: input.slug,
294+ name: input.name,
284295 ...(description.length > 0 ? { description } : {}),
285296 ...(baseUrl.length > 0 ? { baseUrl } : {}),
286297 };
@@ -366,9 +377,8 @@ export default function AddMicrosoftSource(props: { · 79 unmodified lines
366377 const addCustomGraphScopes = async (): Promise<boolean> => {
367378 if (customScopes.length === 0) return true;
368379 const exit = await doAddGraph({
369− payload: microsoftGraphPayload({
370− presetIds: selectedIds,
371− customScopes: [...customScopes],
380+ payload: microsoftCustomGraphPayload({
381+ customScopes,
372382 slug: resolvedSourceId,
373383 name: resolvedDisplayName,
374384 description: resolvedDescription,
packages/plugins/microsoft/src/sdk/graph.ts+7−2
@@ -124,12 +124,17 @@ const uniqueStrings = (values: Iterable<string>): readonly string[] => { · 123 unmodified lines
124124 };
125125
126126 const normalizeSelection = (input: MicrosoftGraphSelectionInput) => {
127+ const customScopes = uniqueStrings(input.customScopes ?? []);
128+ // Explicitly empty presetIds alongside custom scopes means "custom scopes
129+ // only": the add flow fans checked workloads out as their own integrations
130+ // through addWorkloads, so folding the default presets back into the custom
131+ // bundle would duplicate their tools. An empty selection without custom
132+ // scopes still falls back to the default workloads.
127133 const presetIds = uniqueStrings(
128− input.presetIds && input.presetIds.length > 0
134+ input.presetIds && (input.presetIds.length > 0 || customScopes.length > 0)
129135 ? input.presetIds
130136 : MICROSOFT_GRAPH_DEFAULT_PRESET_IDS,
131137 );
132− const customScopes = uniqueStrings(input.customScopes ?? []);
133138 const scopes = microsoftGraphScopesForPresetIds(presetIds, customScopes);
134139 const exactPaths = microsoftGraphExactPathsForPresetIds(presetIds);
135140 const pathPrefixes = microsoftGraphPathPrefixesForPresetIds(presetIds);
packages/plugins/microsoft/src/sdk/plugin.test.ts+27−0
@@ -407,6 +407,33 @@ describe("Microsoft Graph provider", () => { · 406 unmodified lines
407407 ),
408408 );
409409
410+ it.effect("empty presetIds with custom scopes stays custom-only, no default workloads", () =>
411+ Effect.scoped(
412+ Effect.gen(function* () {
413+ const executor = yield* createExecutor(makeTestConfig({ plugins: graphPlugins() }));
414+
415+ // The add flow fans checked workloads out through addWorkloads and
416+ // sends the custom scopes here with presetIds: []. Folding the
417+ // defaults back in would duplicate the fanned-out workloads' tools.
418+ yield* executor.microsoft.addGraph({
419+ slug: "microsoft_graph_custom",
420+ presetIds: [],
421+ customScopes: ["Custom.Scope"],
422+ });
423+
424+ const config = yield* executor.microsoft.getConfig("microsoft_graph_custom");
425+ expect(config?.microsoftGraphPresetIds).toEqual([]);
426+ expect(config?.microsoftGraphCustomScopes).toEqual(["Custom.Scope"]);
427+ expect(config?.microsoftGraphCoversFullGraph).toBe(false);
428+ expect(config?.microsoftGraphScopes).toEqual([
429+ "offline_access",
430+ "User.Read",
431+ "Custom.Scope",
432+ ]);
433+ }),
434+ ),
435+ );
436+
410437 it.effect("uses the app registration default scope when every Graph workload is selected", () =>
411438 Effect.scoped(
412439 Effect.gen(function* () {