From d715924cd0758dfacaa766b7687286d558157d43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Syn=C3=A1=C4=8Dek?= Date: Fri, 6 Mar 2026 19:36:12 +0100 Subject: [PATCH 1/3] fix: formatting in studio (pt2) --- studio/README.md | 2 +- studio/next.config.mjs | 32 ++-- studio/sentry.client.config.ts | 18 +- studio/sentry.edge.config.ts | 4 +- ...ground-url-state-encoding-decoding.test.ts | 47 ++++-- studio/src/__tests__/schema-helpers.test.ts | 15 +- .../analytics/useSyncTableWithQuery.ts | 6 +- .../member-groups/use-group-resources.ts | 84 ++++++---- studio/src/components/playground/types.ts | 68 ++++---- studio/src/components/ui/use-toast.ts | 4 +- studio/src/env.mjs | 14 +- studio/src/hooks/use-check-user-access.ts | 52 +++--- studio/src/hooks/use-event-callback.ts | 2 +- studio/src/hooks/use-event-listener.ts | 16 +- studio/src/hooks/use-fireworks.ts | 8 +- studio/src/hooks/use-form.ts | 2 +- .../use-hydrate-playground-state-from-url.ts | 137 ++++++++++------ .../hooks/use-new-features-popup-disabled.ts | 5 +- studio/src/hooks/use-pagination-params.ts | 13 +- studio/src/hooks/use-session-storage.ts | 6 +- .../src/hooks/use-share-playground-modal.ts | 155 +++++++++++------- studio/src/hooks/use-star-banner-disabled.ts | 7 +- studio/src/hooks/use-user.ts | 8 +- studio/src/hooks/use-workspace.ts | 7 +- studio/src/lib/constants.ts | 85 +++++----- studio/src/lib/download-string-as-file.ts | 2 +- studio/src/lib/format-date.ts | 4 +- studio/src/lib/format-number.ts | 2 +- studio/src/lib/format-status.ts | 10 +- studio/src/lib/insights-helpers.ts | 10 +- studio/src/lib/playground-storage.ts | 26 +-- .../src/lib/playground-url-state-decoding.ts | 43 +++-- .../src/lib/playground-url-state-encoding.ts | 49 +++--- studio/src/lib/schema-helpers.ts | 114 ++++++++----- studio/src/lib/signup-content.ts | 7 +- studio/src/lib/utils.ts | 7 +- studio/src/middleware.ts | 54 +++--- .../src/pages/cosmo-managed-solution-terms.md | 101 ++++++------ studio/src/styles/login.css | 129 +++++++++++---- 39 files changed, 823 insertions(+), 532 deletions(-) diff --git a/studio/README.md b/studio/README.md index 97d7e18180..b846fc97ee 100644 --- a/studio/README.md +++ b/studio/README.md @@ -35,4 +35,4 @@ We use [Connect](https://connect.build/) to unify the communication between all ## Docker Info -We want runtime envs for docker for each on prem customer. Therefore we have two files to achieve this. One is .env.docker that uses a placeholder env name and an entrypoint.sh script that replaces all placeholder env name with the correct one at runtime in the .next folder. This also requires us to SSR the studio. \ No newline at end of file +We want runtime envs for docker for each on prem customer. Therefore we have two files to achieve this. One is .env.docker that uses a placeholder env name and an entrypoint.sh script that replaces all placeholder env name with the correct one at runtime in the .next folder. This also requires us to SSR the studio. diff --git a/studio/next.config.mjs b/studio/next.config.mjs index 4d5b5cf068..9217561402 100644 --- a/studio/next.config.mjs +++ b/studio/next.config.mjs @@ -52,39 +52,41 @@ if (isSentryEnabled) { const lightweightCspHeader = ` style-src 'report-sample' 'self' 'unsafe-inline' data: ${ - isPreview || isProduction ? 'https://vercel.live' : '' + isPreview || isProduction ? "https://vercel.live" : "" }; object-src 'none'; base-uri 'self'; font-src 'self' data:${ - isPreview || isProduction ? ' https://vercel.live https://assets.vercel.com' : '' + isPreview || isProduction + ? " https://vercel.live https://assets.vercel.com" + : "" }; frame-src 'self' https://js.stripe.com https://hooks.stripe.com https://www.googletagmanager.com${ - isPreview || isProduction ? ' https://vercel.live https://vercel.com' : '' + isPreview || isProduction ? " https://vercel.live https://vercel.com" : "" }; img-src 'self' https://wundergraph.com ${ isPreview || isProduction - ? ' https://vercel.live/ https://vercel.com *.pusher.com data: blob:' - : '' + ? " https://vercel.live/ https://vercel.com *.pusher.com data: blob:" + : "" } *.ads.linkedin.com *.google.com; script-src 'report-sample' 'self' 'unsafe-inline' ${ - allowUnsafeEval ? "'unsafe-eval'" : '' + allowUnsafeEval ? "'unsafe-eval'" : "" } https://*.wundergraph.com https://js.stripe.com https://maps.googleapis.com https://plausible.io https://wundergraph.com https://static.reo.dev${ - isPreview || isProduction ? ' https://vercel.live https://vercel.com' : '' + isPreview || isProduction ? " https://vercel.live https://vercel.com" : "" } ${ isProduction ? [ - 'https://www.googletagmanager.com', - 'https://snap.licdn.com', - 'https://cmp.osano.com', - 'https://googleads.g.doubleclick.net', - 'https://*.clarity.ms', - ].join(' ') - : '' + "https://www.googletagmanager.com", + "https://snap.licdn.com", + "https://cmp.osano.com", + "https://googleads.g.doubleclick.net", + "https://*.clarity.ms", + ].join(" ") + : "" }; manifest-src 'self'; media-src 'self'; - worker-src 'self'${isSentryFeatureReplayEnabled ? ' blob:' : ''}; + worker-src 'self'${isSentryFeatureReplayEnabled ? " blob:" : ""}; `; /** diff --git a/studio/sentry.client.config.ts b/studio/sentry.client.config.ts index e216ccab70..33de0072e5 100644 --- a/studio/sentry.client.config.ts +++ b/studio/sentry.client.config.ts @@ -54,15 +54,21 @@ init({ * This is independent of `sessionSampleRate`. * 1.0 will record all sessions and 0 will record none. */ - replaysOnErrorSampleRate: isSentryFeatureReplayEnabled ? parseFloat( - process.env.NEXT_PUBLIC_SENTRY_CLIENT_REPLAYS_ON_ERROR_SAMPLE_RATE || "0", - ) : 0, + replaysOnErrorSampleRate: isSentryFeatureReplayEnabled + ? parseFloat( + process.env.NEXT_PUBLIC_SENTRY_CLIENT_REPLAYS_ON_ERROR_SAMPLE_RATE || + "0", + ) + : 0, /** * The sample rate for session-long replays. * 1.0 will record all sessions and 0 will record none. */ - replaysSessionSampleRate: isSentryFeatureReplayEnabled ? parseFloat( - process.env.NEXT_PUBLIC_SENTRY_CLIENT_REPLAYS_SESSION_SAMPLE_RATE || "0", - ) : 0, + replaysSessionSampleRate: isSentryFeatureReplayEnabled + ? parseFloat( + process.env.NEXT_PUBLIC_SENTRY_CLIENT_REPLAYS_SESSION_SAMPLE_RATE || + "0", + ) + : 0, integrations, }); diff --git a/studio/sentry.edge.config.ts b/studio/sentry.edge.config.ts index f013dfcfa0..cda0c3db44 100644 --- a/studio/sentry.edge.config.ts +++ b/studio/sentry.edge.config.ts @@ -9,7 +9,9 @@ init({ dsn: process.env.NEXT_PUBLIC_SENTRY_DSN, // Adjust this value in production, or use tracesSampler for greater control - tracesSampleRate: parseFloat(process.env.NEXT_PUBLIC_SENTRY_EDGE_SAMPLE_RATE || "0"), + tracesSampleRate: parseFloat( + process.env.NEXT_PUBLIC_SENTRY_EDGE_SAMPLE_RATE || "0", + ), // Setting this option to true will print useful information to the console while you're setting up Sentry. debug: process.env.SENTRY_DEBUG === "true", diff --git a/studio/src/__tests__/playground-url-state-encoding-decoding.test.ts b/studio/src/__tests__/playground-url-state-encoding-decoding.test.ts index 31b79ec01e..e0b9e83077 100644 --- a/studio/src/__tests__/playground-url-state-encoding-decoding.test.ts +++ b/studio/src/__tests__/playground-url-state-encoding-decoding.test.ts @@ -2,7 +2,10 @@ import { compressToEncodedURIComponent } from "lz-string"; import { describe, expect, test, vi } from "vitest"; import { PlaygroundUrlState, TabState } from "../components/playground/types"; import { PLAYGROUND_STATE_QUERY_PARAM } from "../lib/constants"; -import { decompressState, extractStateFromUrl } from "../lib/playground-url-state-decoding"; +import { + decompressState, + extractStateFromUrl, +} from "../lib/playground-url-state-decoding"; import { buildStateToShare } from "../lib/playground-url-state-encoding"; describe("buildStateToShare", () => { @@ -62,7 +65,9 @@ describe("buildStateToShare", () => { describe("createCompressedStateUrl", () => { test("returns valid URL with compressed query param", async () => { - const { createCompressedStateUrl } = await import("../lib/playground-url-state-encoding"); + const { createCompressedStateUrl } = await import( + "../lib/playground-url-state-encoding" + ); const state = { operation: "query { test }" }; const BASE_URL = "https://my.studio.dev"; @@ -78,13 +83,14 @@ describe("createCompressedStateUrl", () => { }); test("throws error if compression fails", async () => { - // ensure a fresh module state + // ensure a fresh module state vi.resetModules(); // Mock lz-string's compression to simulate a failure // Note: doMock must be followed by re-import vi.doMock("lz-string", async () => { - const actual = await vi.importActual("lz-string"); + const actual = + await vi.importActual("lz-string"); return { ...actual, compressToEncodedURIComponent: () => "", @@ -92,7 +98,9 @@ describe("createCompressedStateUrl", () => { }); // Re-import the module after mocking to ensure mock is applied - const { createCompressedStateUrl } = await import("../lib/playground-url-state-encoding"); + const { createCompressedStateUrl } = await import( + "../lib/playground-url-state-encoding" + ); expect(() => { createCompressedStateUrl({ operation: "query { fail }" }); @@ -107,7 +115,9 @@ describe("decompressState", () => { variables: '{ "foo": "bar" }', }; - const compressed = compressToEncodedURIComponent(JSON.stringify(originalState)); + const compressed = compressToEncodedURIComponent( + JSON.stringify(originalState), + ); const result = decompressState(compressed); expect(result).toEqual(originalState); @@ -116,18 +126,21 @@ describe("decompressState", () => { test("throws error if decompressFromEncodedURIComponent fails", async () => { // ensure a fresh module state vi.resetModules(); - + vi.doMock("lz-string", async () => { - const actual = await vi.importActual("lz-string"); + const actual = + await vi.importActual("lz-string"); return { ...actual, decompressFromEncodedURIComponent: () => null, // simulate failure }; }); - + // re-import after mock is applied - const { decompressState } = await import("../lib/playground-url-state-decoding"); - + const { decompressState } = await import( + "../lib/playground-url-state-decoding" + ); + expect(() => { decompressState("invalid-compressed-string"); }).toThrow("Failed to decompress playground state"); @@ -136,15 +149,17 @@ describe("decompressState", () => { test("throws error if schema is invalid", async () => { // ensure a fresh module state vi.resetModules(); - + // missing required "operation" field const badState = { foo: "bar" }; const compressed = compressToEncodedURIComponent(JSON.stringify(badState)); - + // re-import decompressState after module reset // Note: lz-string doesn't need to be mocked here - const { decompressState } = await import("../lib/playground-url-state-decoding"); - + const { decompressState } = await import( + "../lib/playground-url-state-decoding" + ); + expect(() => { decompressState(compressed); }).toThrow("Failed to decompress playground state"); @@ -173,4 +188,4 @@ describe("extractStateFromUrl", () => { const result = extractStateFromUrl(); expect(result).toBeNull(); }); -}); \ No newline at end of file +}); diff --git a/studio/src/__tests__/schema-helpers.test.ts b/studio/src/__tests__/schema-helpers.test.ts index 654cc0fb81..d9ecb2c0ad 100644 --- a/studio/src/__tests__/schema-helpers.test.ts +++ b/studio/src/__tests__/schema-helpers.test.ts @@ -57,7 +57,8 @@ test("return the correct types with deprecated fields or args", () => { expect(result).not.toBeNull(); const parsedTypes = getParsedTypes(result!.doc); - const [totalDeprecatedNodesCount, deprecated] = getDeprecatedTypes(parsedTypes); + const [totalDeprecatedNodesCount, deprecated] = + getDeprecatedTypes(parsedTypes); expect(totalDeprecatedNodesCount).toEqual(2); expect(deprecated.length).toEqual(2); @@ -65,7 +66,9 @@ test("return the correct types with deprecated fields or args", () => { expect(deprecated[0].fields?.[0]?.name).toEqual("teammates"); expect(deprecated[1].fields?.length).toEqual(1); expect(deprecated[1].fields?.[0]?.name).toEqual("fullName"); - expect(deprecated[1].fields?.[0]?.deprecationReason).toEqual("Please use first and last name instead"); + expect(deprecated[1].fields?.[0]?.deprecationReason).toEqual( + "Please use first and last name instead", + ); }); test("that authentication types are read correctly", async () => { @@ -74,7 +77,8 @@ test("that authentication types are read correctly", async () => { expect(result).not.toBeNull(); const parsedTypes = getParsedTypes(result!.doc); - const [totalAuthenticatedNodesCount, authenticatedTypes] = getAuthenticatedTypes(parsedTypes); + const [totalAuthenticatedNodesCount, authenticatedTypes] = + getAuthenticatedTypes(parsedTypes); expect(totalAuthenticatedNodesCount).toEqual(4); expect(authenticatedTypes.length).toEqual(3); @@ -84,7 +88,10 @@ test("that authentication types are read correctly", async () => { expect(authenticatedTypes[2].fields?.length).toEqual(2); expect(authenticatedTypes[2].fields?.[0]?.name).toEqual("role"); expect(authenticatedTypes[2].fields?.[1]?.name).toEqual("email"); - expect(authenticatedTypes[2].fields?.[1]?.requiresScopes).toStrictEqual([["read:profile", "read:email"], ["read:all"]]); + expect(authenticatedTypes[2].fields?.[1]?.requiresScopes).toStrictEqual([ + ["read:profile", "read:email"], + ["read:all"], + ]); }); test("returns correct type counts", () => { diff --git a/studio/src/components/analytics/useSyncTableWithQuery.ts b/studio/src/components/analytics/useSyncTableWithQuery.ts index c44fa5b553..f691bbe112 100644 --- a/studio/src/components/analytics/useSyncTableWithQuery.ts +++ b/studio/src/components/analytics/useSyncTableWithQuery.ts @@ -77,7 +77,7 @@ export const useSyncTableWithQuery = ({ useEffect(() => { if (router.isReady) { const filterStateFromUrl = JSON.parse( - decodeURI((router.query.filterState as string) ?? "[]") + decodeURI((router.query.filterState as string) ?? "[]"), ); if (!isEqual(filterStateFromUrl, selectedFilters)) { @@ -91,7 +91,7 @@ export const useSyncTableWithQuery = ({ setSelectedGroup( AnalyticsViewGroupName[ router.query.group as string as keyof typeof AnalyticsViewGroupName - ] + ], ); } @@ -143,7 +143,7 @@ export const useSyncTableWithQuery = ({ Number(router.query.refreshInterval) !== refreshInterval ) { onRefreshIntervalChange( - Number(router.query.refreshInterval) || refreshIntervals[0].value + Number(router.query.refreshInterval) || refreshIntervals[0].value, ); } diff --git a/studio/src/components/member-groups/use-group-resources.ts b/studio/src/components/member-groups/use-group-resources.ts index 4283a3c5ed..1f70d3c078 100644 --- a/studio/src/components/member-groups/use-group-resources.ts +++ b/studio/src/components/member-groups/use-group-resources.ts @@ -26,10 +26,14 @@ export interface GroupResourceItem { export type GroupResource = GroupResourceSection | GroupResourceItem; -export function useGroupResources({ rule, activeRole, accessibleResources }: { - rule: UpdateOrganizationGroupRequest_GroupRule, - activeRole: (typeof roles[number]) | undefined, - accessibleResources: GetUserAccessibleResourcesResponse | undefined, +export function useGroupResources({ + rule, + activeRole, + accessibleResources, +}: { + rule: UpdateOrganizationGroupRequest_GroupRule; + activeRole: (typeof roles)[number] | undefined; + accessibleResources: GetUserAccessibleResourcesResponse | undefined; }): readonly GroupResource[] { return useMemo(() => { const result: GroupResource[] = []; @@ -64,7 +68,7 @@ export function useGroupResources({ rule, activeRole, accessibleResources }: { function mapNamespace( rule: UpdateOrganizationGroupRequest_GroupRule, namespace: GetUserAccessibleResourcesResponse_Namespace, -): Omit { +): Omit { return { type: "item", label: namespace.name, @@ -76,8 +80,10 @@ function mapNamespace( function mapGraph( rule: UpdateOrganizationGroupRequest_GroupRule, - graph: GetUserAccessibleResourcesResponse_FederatedGraph | GetUserAccessibleResourcesResponse_SubGraph -): Omit { + graph: + | GetUserAccessibleResourcesResponse_FederatedGraph + | GetUserAccessibleResourcesResponse_SubGraph, +): Omit { return { type: "item", label: graph.name, @@ -89,7 +95,7 @@ function mapGraph( function getNamespaces( rule: UpdateOrganizationGroupRequest_GroupRule, - accessibleResources: GetUserAccessibleResourcesResponse + accessibleResources: GetUserAccessibleResourcesResponse, ): GroupResourceItem[] { return accessibleResources.namespaces.map((ns) => { const namespaceResources = [ @@ -110,16 +116,18 @@ function getNamespaces( function getFederatedGraphs( rule: UpdateOrganizationGroupRequest_GroupRule, - accessibleResources: GetUserAccessibleResourcesResponse + accessibleResources: GetUserAccessibleResourcesResponse, ): GroupResourceItem[] { const fedGraphsByNamespace = Object.groupBy( accessibleResources.federatedGraphs, - (graph) => graph.namespace + (graph) => graph.namespace, ); return Object.entries(fedGraphsByNamespace) .map(([namespace, graphs]) => { - const ns = accessibleResources.namespaces.find((ns) => ns.name === namespace)!; + const ns = accessibleResources.namespaces.find( + (ns) => ns.name === namespace, + )!; const isNamespaceSelected = rule.namespaces.includes(ns.id); return { @@ -127,10 +135,13 @@ function getFederatedGraphs( isNamespaceResource: false, selected: false, disabled: isNamespaceSelected, - children: graphs?.map((graph) => ({ - ...mapGraph(rule, graph), - disabled: isNamespaceSelected - } satisfies GroupResourceItem)), + children: graphs?.map( + (graph) => + ({ + ...mapGraph(rule, graph), + disabled: isNamespaceSelected, + }) satisfies GroupResourceItem, + ), } satisfies GroupResourceItem; }) .filter((d) => d.children && d.children.length > 0); @@ -138,21 +149,23 @@ function getFederatedGraphs( function getSubGraphs( rule: UpdateOrganizationGroupRequest_GroupRule, - accessibleResources: GetUserAccessibleResourcesResponse + accessibleResources: GetUserAccessibleResourcesResponse, ): GroupResourceItem[] { const subGraphsByNamespace = Object.groupBy( accessibleResources.subgraphs, - (graph) => graph.namespace + (graph) => graph.namespace, ); return Object.entries(subGraphsByNamespace) .map(([namespace, graphs]) => { const subGraphsByFedGraph = Object.groupBy( graphs ?? [], - (graph) => graph.federatedGraphId + (graph) => graph.federatedGraphId, ); - const ns = accessibleResources.namespaces.find((ns) => ns.name === namespace)!; + const ns = accessibleResources.namespaces.find( + (ns) => ns.name === namespace, + )!; const isNamespaceSelected = rule.namespaces.includes(ns.id); return { @@ -161,17 +174,28 @@ function getSubGraphs( selected: false, disabled: isNamespaceSelected, children: Object.entries(subGraphsByFedGraph) - .map(([fedGraph, subgraphs]) => ({ - ...mapGraph(rule, accessibleResources.federatedGraphs.find((graph) => graph.targetId === fedGraph)!), - selected: false, - disabled: isNamespaceSelected, - children: (subgraphs ?? []).map((graph) => ({ - ...mapGraph(rule, graph), - disabled: isNamespaceSelected, - } satisfies GroupResourceItem)), - }) satisfies GroupResourceItem) + .map( + ([fedGraph, subgraphs]) => + ({ + ...mapGraph( + rule, + accessibleResources.federatedGraphs.find( + (graph) => graph.targetId === fedGraph, + )!, + ), + selected: false, + disabled: isNamespaceSelected, + children: (subgraphs ?? []).map( + (graph) => + ({ + ...mapGraph(rule, graph), + disabled: isNamespaceSelected, + }) satisfies GroupResourceItem, + ), + }) satisfies GroupResourceItem, + ) .filter((d) => d.children && d.children.length > 0), } satisfies GroupResourceItem; }) - .filter((d) => d.children && d.children.length > 0) -} \ No newline at end of file + .filter((d) => d.children && d.children.length > 0); +} diff --git a/studio/src/components/playground/types.ts b/studio/src/components/playground/types.ts index 456d37b065..5bf6034132 100644 --- a/studio/src/components/playground/types.ts +++ b/studio/src/components/playground/types.ts @@ -1,6 +1,6 @@ import { SHARE_OPTIONS } from "@/lib/constants"; import { createContext } from "react"; -import { z } from 'zod'; +import { z } from "zod"; export type TabState = { id: string; @@ -108,37 +108,43 @@ export type QueryPlan = QueryPlanFetchTypeNode & { children: QueryPlanFetchTypeNode[]; }; -export type ShareOptionId = typeof SHARE_OPTIONS[number]["id"]; +export type ShareOptionId = (typeof SHARE_OPTIONS)[number]["id"]; export const PlaygroundStateSchema = z.object({ - operation: z.string().min(1, 'Operation is required in playground url state'), - variables: z.string().optional(), - headers: z.string().optional(), - preFlight: z.object({ - enabled: z.boolean().optional(), - content: z.string().optional(), - id: z.string().optional(), - title: z.string().optional(), - updatedByTabId: z.string().optional(), - type: z.string().optional(), - }).optional(), - preOperation: z.object({ - enabled: z.boolean().optional(), - content: z.string().optional(), - id: z.string().optional(), - title: z.string().optional(), - updatedByTabId: z.string().optional(), - }).optional(), - postOperation: z.object({ - enabled: z.boolean().optional(), - content: z.string().optional(), - id: z.string().optional(), - title: z.string().optional(), - updatedByTabId: z.string().optional(), - }).optional(), + operation: z.string().min(1, "Operation is required in playground url state"), + variables: z.string().optional(), + headers: z.string().optional(), + preFlight: z + .object({ + enabled: z.boolean().optional(), + content: z.string().optional(), + id: z.string().optional(), + title: z.string().optional(), + updatedByTabId: z.string().optional(), + type: z.string().optional(), + }) + .optional(), + preOperation: z + .object({ + enabled: z.boolean().optional(), + content: z.string().optional(), + id: z.string().optional(), + title: z.string().optional(), + updatedByTabId: z.string().optional(), + }) + .optional(), + postOperation: z + .object({ + enabled: z.boolean().optional(), + content: z.string().optional(), + id: z.string().optional(), + title: z.string().optional(), + updatedByTabId: z.string().optional(), + }) + .optional(), }); - + export type PlaygroundUrlState = z.infer; -export type PreFlightUrlState = PlaygroundUrlState['preFlight']; -export type PreOperationUrlState = PlaygroundUrlState['preOperation']; -export type PostOperationUrlState = PlaygroundUrlState['postOperation']; \ No newline at end of file +export type PreFlightUrlState = PlaygroundUrlState["preFlight"]; +export type PreOperationUrlState = PlaygroundUrlState["preOperation"]; +export type PostOperationUrlState = PlaygroundUrlState["postOperation"]; diff --git a/studio/src/components/ui/use-toast.ts b/studio/src/components/ui/use-toast.ts index 85146bcce5..bf5b38aaf7 100644 --- a/studio/src/components/ui/use-toast.ts +++ b/studio/src/components/ui/use-toast.ts @@ -81,7 +81,7 @@ export const reducer = (state: State, action: Action): State => { return { ...state, toasts: state.toasts.map((t) => - t.id === action.toast.id ? { ...t, ...action.toast } : t + t.id === action.toast.id ? { ...t, ...action.toast } : t, ), }; @@ -106,7 +106,7 @@ export const reducer = (state: State, action: Action): State => { ...t, open: false, } - : t + : t, ), }; } diff --git a/studio/src/env.mjs b/studio/src/env.mjs index 25aa806daf..dccd01d156 100644 --- a/studio/src/env.mjs +++ b/studio/src/env.mjs @@ -2,10 +2,10 @@ import { createEnv } from "@t3-oss/env-nextjs"; // import { z } from "zod"; export const env = createEnv({ - server: { - // Example - // DATABASE_URL: z.string().url(), - }, - client: {}, - runtimeEnv: {}, -}); \ No newline at end of file + server: { + // Example + // DATABASE_URL: z.string().url(), + }, + client: {}, + runtimeEnv: {}, +}); diff --git a/studio/src/hooks/use-check-user-access.ts b/studio/src/hooks/use-check-user-access.ts index d13327faf9..967448f610 100644 --- a/studio/src/hooks/use-check-user-access.ts +++ b/studio/src/hooks/use-check-user-access.ts @@ -8,30 +8,38 @@ import { useUser } from "@/hooks/use-user"; */ export function useCheckUserAccess() { const user = useUser(); - return useCallback(({ organizationId, rolesToBe }: { - organizationId?: string; - rolesToBe: OrganizationRole[]; - }) => { - const org = organizationId - ? user?.organizations.find((o) => o.id === organizationId) - : user?.currentOrganization; + return useCallback( + ({ + organizationId, + rolesToBe, + }: { + organizationId?: string; + rolesToBe: OrganizationRole[]; + }) => { + const org = organizationId + ? user?.organizations.find((o) => o.id === organizationId) + : user?.currentOrganization; - if (!org?.groups) { - return false; - } - - if (!rolesToBe || rolesToBe.length === 0) { - // We expect at least one role to be given, if no role is given, we don't need to perform any check - return true; - } + if (!org?.groups) { + return false; + } - const roles = new Set(org.groups.flatMap((g) => g.rules?.map((r) => r.role) ?? [])); - for (const role of rolesToBe) { - if (roles.has(role)) { + if (!rolesToBe || rolesToBe.length === 0) { + // We expect at least one role to be given, if no role is given, we don't need to perform any check return true; } - } - return false; - }, [user]); -} \ No newline at end of file + const roles = new Set( + org.groups.flatMap((g) => g.rules?.map((r) => r.role) ?? []), + ); + for (const role of rolesToBe) { + if (roles.has(role)) { + return true; + } + } + + return false; + }, + [user], + ); +} diff --git a/studio/src/hooks/use-event-callback.ts b/studio/src/hooks/use-event-callback.ts index 1a08315ec1..83e92c653c 100644 --- a/studio/src/hooks/use-event-callback.ts +++ b/studio/src/hooks/use-event-callback.ts @@ -3,7 +3,7 @@ import { useCallback, useRef } from "react"; import { useIsomorphicLayoutEffect } from "./use-isomorphic-layout-effect"; export function useEventCallback( - fn: (...args: Args) => R + fn: (...args: Args) => R, ) { const ref = useRef(() => { throw new Error("Cannot call an event handler while rendering."); diff --git a/studio/src/hooks/use-event-listener.ts b/studio/src/hooks/use-event-listener.ts index 97724eec8c..944d4b105b 100644 --- a/studio/src/hooks/use-event-listener.ts +++ b/studio/src/hooks/use-event-listener.ts @@ -7,7 +7,7 @@ function useEventListener( eventName: K, handler: (event: MediaQueryListEventMap[K]) => void, element: RefObject, - options?: boolean | AddEventListenerOptions + options?: boolean | AddEventListenerOptions, ): void; // Window Event based useEventListener interface @@ -15,18 +15,18 @@ function useEventListener( eventName: K, handler: (event: WindowEventMap[K]) => void, element?: undefined, - options?: boolean | AddEventListenerOptions + options?: boolean | AddEventListenerOptions, ): void; // Element Event based useEventListener interface function useEventListener< K extends keyof HTMLElementEventMap, - T extends HTMLElement = HTMLDivElement + T extends HTMLElement = HTMLDivElement, >( eventName: K, handler: (event: HTMLElementEventMap[K]) => void, element: RefObject, - options?: boolean | AddEventListenerOptions + options?: boolean | AddEventListenerOptions, ): void; // Document Event based useEventListener interface @@ -34,14 +34,14 @@ function useEventListener( eventName: K, handler: (event: DocumentEventMap[K]) => void, element: RefObject, - options?: boolean | AddEventListenerOptions + options?: boolean | AddEventListenerOptions, ): void; function useEventListener< KW extends keyof WindowEventMap, KH extends keyof HTMLElementEventMap, KM extends keyof MediaQueryListEventMap, - T extends HTMLElement | MediaQueryList | void = void + T extends HTMLElement | MediaQueryList | void = void, >( eventName: KW | KH | KM, handler: ( @@ -49,10 +49,10 @@ function useEventListener< | WindowEventMap[KW] | HTMLElementEventMap[KH] | MediaQueryListEventMap[KM] - | Event + | Event, ) => void, element?: RefObject, - options?: boolean | AddEventListenerOptions + options?: boolean | AddEventListenerOptions, ) { // Create a ref that stores handler const savedHandler = useRef(handler); diff --git a/studio/src/hooks/use-fireworks.ts b/studio/src/hooks/use-fireworks.ts index 1f64d669d9..85dcf2cccd 100644 --- a/studio/src/hooks/use-fireworks.ts +++ b/studio/src/hooks/use-fireworks.ts @@ -1,5 +1,5 @@ -import confetti from 'canvas-confetti'; -import React from 'react'; +import confetti from "canvas-confetti"; +import React from "react"; const fireworks = () => { const duration = 2 * 1000; @@ -23,13 +23,13 @@ const fireworks = () => { Object.assign({}, defaults, { particleCount, origin: { x: randomInRange(0.1, 0.3), y: Math.random() - 0.2 }, - }) + }), ); confetti( Object.assign({}, defaults, { particleCount, origin: { x: randomInRange(0.7, 0.9), y: Math.random() - 0.2 }, - }) + }), ); }, 250); }; diff --git a/studio/src/hooks/use-form.ts b/studio/src/hooks/use-form.ts index 7588bd0efb..7cd227dc61 100644 --- a/studio/src/hooks/use-form.ts +++ b/studio/src/hooks/use-form.ts @@ -10,7 +10,7 @@ export interface UseZodForm } export const useZodForm = ( - props: UseZodForm + props: UseZodForm, ) => { const { schema, ...formProps } = props; return useForm({ diff --git a/studio/src/hooks/use-hydrate-playground-state-from-url.ts b/studio/src/hooks/use-hydrate-playground-state-from-url.ts index 64decca706..cc9b628894 100644 --- a/studio/src/hooks/use-hydrate-playground-state-from-url.ts +++ b/studio/src/hooks/use-hydrate-playground-state-from-url.ts @@ -1,9 +1,16 @@ -import { PlaygroundContext, PlaygroundUrlState, PostOperationUrlState, PreOperationUrlState, TabsState, TabState } from '@/components/playground/types'; -import { useToast } from '@/components/ui/use-toast'; -import { useLocalStorage } from '@/hooks/use-local-storage'; -import { extractStateFromUrl } from '@/lib/playground-url-state-decoding'; -import { useRouter } from 'next/router'; -import { useContext, useEffect, useState } from 'react'; +import { + PlaygroundContext, + PlaygroundUrlState, + PostOperationUrlState, + PreOperationUrlState, + TabsState, + TabState, +} from "@/components/playground/types"; +import { useToast } from "@/components/ui/use-toast"; +import { useLocalStorage } from "@/hooks/use-local-storage"; +import { extractStateFromUrl } from "@/lib/playground-url-state-decoding"; +import { useRouter } from "next/router"; +import { useContext, useEffect, useState } from "react"; type ScriptData = { id?: string; @@ -16,14 +23,14 @@ type ScriptData = { /** * Pending Items: - * + * * 1. [ENG-7093] Ensure sharing of scripts is working. Right now, after the hydration is completed, the GraphiQL - * is adding a new tab internally which overrides the script:tabsState as for the new tab, - * it is missing in the localstorage. We don't have a clean way to prevent GraphiQL from + * is adding a new tab internally which overrides the script:tabsState as for the new tab, + * it is missing in the localstorage. We don't have a clean way to prevent GraphiQL from * adding a new tab. Instead of building hacks on top of hacks, we should revisit this * and consider creating our own GraphiQL component. * 2. Add sharing of pre-flight enabled state. - * 3. For now, the customers won't be shown the scripts options (preflight, preOperation and + * 3. For now, the customers won't be shown the scripts options (preflight, preOperation and * postOperation) for sharing */ export const useHydratePlaygroundStateFromUrl = ( @@ -36,90 +43,109 @@ export const useHydratePlaygroundStateFromUrl = ( ) => { const router = useRouter(); const { toast } = useToast(); - + // `setIsHydrated` is used to avoid race conditions. - // First hydration should be done from the URL, and + // First hydration should be done from the URL, and // then only childrens of Playground should be able to update state. const { setIsHydrated } = useContext(PlaygroundContext); - const [, setScriptsTabState] = useLocalStorage<{ [key: string]: Record }>('playground:script:tabState', {}); - const [, setPreFlightSelected] = useLocalStorage('playground:pre-flight:selected', null); + const [, setScriptsTabState] = useLocalStorage<{ + [key: string]: Record; + }>("playground:script:tabState", {}); + const [, setPreFlightSelected] = useLocalStorage( + "playground:pre-flight:selected", + null, + ); // todo: add sharing of pre-flight enabled state - const [, setPreFlightEnabled] = useLocalStorage('playground:pre-flight:enabled', null); - const [, setPreOpSelected] = useLocalStorage('playground:pre-operation:selected', null); - const [, setPostOpSelected] = useLocalStorage('playground:post-operation:selected', null); - - const [pendingHydrationState, setPendingHydrationState] = useState(null); + const [, setPreFlightEnabled] = useLocalStorage( + "playground:pre-flight:enabled", + null, + ); + const [, setPreOpSelected] = useLocalStorage( + "playground:pre-operation:selected", + null, + ); + const [, setPostOpSelected] = useLocalStorage( + "playground:post-operation:selected", + null, + ); + + const [pendingHydrationState, setPendingHydrationState] = + useState(null); // On mount: extract and clear URL state useEffect(() => { const { playgroundUrlState, ...query } = router.query; try { - if (playgroundUrlState && typeof playgroundUrlState === 'string') { + if (playgroundUrlState && typeof playgroundUrlState === "string") { const state = extractStateFromUrl(); if (!state) { setIsHydrated(true); return; } - + setPendingHydrationState(state); } else { setIsHydrated(true); return; } - } - catch (err) { - if (process.env.NODE_ENV === 'development') { - console.error('[Playground] Error extracting state from URL:', (err as Error)?.message); + } catch (err) { + if (process.env.NODE_ENV === "development") { + console.error( + "[Playground] Error extracting state from URL:", + (err as Error)?.message, + ); } toast({ - title: 'Unable to Load Shared Playground State', - description: 'The shared URL may be incorrect. Please double-check and try again.', - variant: 'destructive', + title: "Unable to Load Shared Playground State", + description: + "The shared URL may be incorrect. Please double-check and try again.", + variant: "destructive", }); setIsHydrated(true); - } - finally { + } finally { // Clear the URL param immediately - router.replace({ pathname: router.pathname, query }, undefined, { shallow: true }); + router.replace({ pathname: router.pathname, query }, undefined, { + shallow: true, + }); } // eslint-disable-next-line }, []); - + const setOperationScripts = ( preOperation: PreOperationUrlState, postOperation: PostOperationUrlState, - newTabId: string + newTabId: string, ) => { - setScriptsTabState(prev => { + setScriptsTabState((prev) => { const updated = { ...prev }; updated[newTabId] = { ...(updated[newTabId] || {}) }; if (preOperation) { - updated[newTabId]['pre-operation'] = preOperation; + updated[newTabId]["pre-operation"] = preOperation; setPreOpSelected(preOperation); } if (postOperation) { - updated[newTabId]['post-operation'] = postOperation; + updated[newTabId]["post-operation"] = postOperation; setPostOpSelected(postOperation); } return updated; }); - } + }; const addNewTabForHydration = (state: PlaygroundUrlState) => { // Create a new tab with the shared state const newTabId = crypto.randomUUID(); const newTab: TabState = { id: newTabId, - title: '', // GraphiQL will set this automatically + title: "", // GraphiQL will set this automatically query: state.operation, - variables: state.variables || '', - headers: state.headers || '', - hash: '', - operationName: '', // GraphiQL will set this automatically + variables: state.variables || "", + headers: state.headers || "", + hash: "", + operationName: "", // GraphiQL will set this automatically response: null, }; @@ -131,7 +157,7 @@ export const useHydratePlaygroundStateFromUrl = ( }); return newTabId; - } + }; useEffect(() => { // We have an early bailout condition to avoid race condition with GraphiQL. @@ -139,15 +165,19 @@ export const useHydratePlaygroundStateFromUrl = ( // Once that's completed, we can hydrate the state from URL // For hydration, we avoid making changes into the active tab index. // We instead created a new tab and updated the PlaygroundContext.tabsState - if (!isGraphiqlRendered || tabsState.tabs.length === 0 || !pendingHydrationState) { + if ( + !isGraphiqlRendered || + tabsState.tabs.length === 0 || + !pendingHydrationState + ) { return; } const newTabId = addNewTabForHydration(pendingHydrationState); - if (process.env.NODE_ENV === 'development') { - console.info('[Playground] New tab added for hydration: ', newTabId); + if (process.env.NODE_ENV === "development") { + console.info("[Playground] New tab added for hydration: ", newTabId); } - + // Set the state for the new tab setQuery(pendingHydrationState.operation); if (pendingHydrationState.variables) { @@ -162,17 +192,20 @@ export const useHydratePlaygroundStateFromUrl = ( setPreFlightSelected(pendingHydrationState.preFlight); } - if (pendingHydrationState.preOperation || pendingHydrationState.postOperation) { + if ( + pendingHydrationState.preOperation || + pendingHydrationState.postOperation + ) { setOperationScripts( pendingHydrationState.preOperation, pendingHydrationState.postOperation, - newTabId + newTabId, ); } setIsHydrated(true); setPendingHydrationState(null); - - // eslint-disable-next-line react-hooks/exhaustive-deps + + // eslint-disable-next-line react-hooks/exhaustive-deps }, [pendingHydrationState, isGraphiqlRendered, tabsState.tabs.length]); -}; \ No newline at end of file +}; diff --git a/studio/src/hooks/use-new-features-popup-disabled.ts b/studio/src/hooks/use-new-features-popup-disabled.ts index eed09bd4b6..de5f05f721 100644 --- a/studio/src/hooks/use-new-features-popup-disabled.ts +++ b/studio/src/hooks/use-new-features-popup-disabled.ts @@ -1,7 +1,10 @@ import { Dispatch, SetStateAction, useEffect, useState } from "react"; import { useLocalStorage } from "./use-local-storage"; -export function useNewFeaturesPopupDisabled(): [boolean, Dispatch>] { +export function useNewFeaturesPopupDisabled(): [ + boolean, + Dispatch>, +] { const [isPopupDisabled, setIsPopupDisabled] = useState(true); const [isPopupDisabledOnClient, setDisablePopup] = useLocalStorage( "dismissHubFeaturesPopup", diff --git a/studio/src/hooks/use-pagination-params.ts b/studio/src/hooks/use-pagination-params.ts index 0f3e516364..3faf9ea8d1 100644 --- a/studio/src/hooks/use-pagination-params.ts +++ b/studio/src/hooks/use-pagination-params.ts @@ -3,8 +3,15 @@ import { clamp } from "@/lib/utils"; export const usePaginationParams = () => { const router = useRouter(); - const pageNumber = Math.max(Number.parseInt((router.query.page as string) || "1"), 1); - const pageSize = clamp(Number.parseInt((router.query.pageSize as string) || "20"), 10, 50); + const pageNumber = Math.max( + Number.parseInt((router.query.page as string) || "1"), + 1, + ); + const pageSize = clamp( + Number.parseInt((router.query.pageSize as string) || "20"), + 10, + 50, + ); const offset = (pageNumber - 1) * pageSize; const search = (router.query.search as string) || ""; @@ -14,4 +21,4 @@ export const usePaginationParams = () => { offset, search, } as const; -}; \ No newline at end of file +}; diff --git a/studio/src/hooks/use-session-storage.ts b/studio/src/hooks/use-session-storage.ts index 32416a9924..82d3c6d012 100644 --- a/studio/src/hooks/use-session-storage.ts +++ b/studio/src/hooks/use-session-storage.ts @@ -19,7 +19,7 @@ type SetValue = Dispatch>; export function useSessionStorage( key: string, - initialValue: T + initialValue: T, ): [T, SetValue] { // Get from session storage then // parse stored json or return initialValue @@ -48,7 +48,7 @@ export function useSessionStorage( // Prevent build error "window is undefined" but keeps working if (typeof window == "undefined") { console.warn( - `Tried setting sessionStorage key “${key}” even though environment is not a client` + `Tried setting sessionStorage key “${key}” even though environment is not a client`, ); } @@ -81,7 +81,7 @@ export function useSessionStorage( } setStoredValue(readValue()); }, - [key, readValue] + [key, readValue], ); // this only works for other documents, not the current one diff --git a/studio/src/hooks/use-share-playground-modal.ts b/studio/src/hooks/use-share-playground-modal.ts index 846691d2b9..fa92698019 100644 --- a/studio/src/hooks/use-share-playground-modal.ts +++ b/studio/src/hooks/use-share-playground-modal.ts @@ -1,53 +1,80 @@ -import { PlaygroundContext, ShareOptionId } from '@/components/playground/types'; -import { useToast } from '@/components/ui/use-toast'; -import { OPTION_TYPES, SHARE_OPTIONS } from '@/lib/constants'; -import { buildStateToShare, createCompressedStateUrl } from '@/lib/playground-url-state-encoding'; -import { PlaygroundUrlState } from '@/components/playground/types'; -import { useCallback, useContext, useEffect, useState, useMemo } from 'react'; -import { useLocalStorage } from '@/hooks/use-local-storage'; +import { + PlaygroundContext, + ShareOptionId, +} from "@/components/playground/types"; +import { useToast } from "@/components/ui/use-toast"; +import { OPTION_TYPES, SHARE_OPTIONS } from "@/lib/constants"; +import { + buildStateToShare, + createCompressedStateUrl, +} from "@/lib/playground-url-state-encoding"; +import { PlaygroundUrlState } from "@/components/playground/types"; +import { useCallback, useContext, useEffect, useState, useMemo } from "react"; +import { useLocalStorage } from "@/hooks/use-local-storage"; const MAX_URL_LENGTH = 2000; const WARNING_MESSAGES = { URL_TOO_LONG: { title: "Warning!", - description: "The generated URL is too long and may not work in all browsers. Consider removing some options.", + description: + "The generated URL is too long and may not work in all browsers. Consider removing some options.", }, }; -const DEFAULT_SELECTED_OPTIONS = SHARE_OPTIONS.reduce((acc, { id, isChecked }) => { +const DEFAULT_SELECTED_OPTIONS = SHARE_OPTIONS.reduce( + (acc, { id, isChecked }) => { acc[id] = isChecked; return acc; - }, {} as Record); + }, + {} as Record, +); export const useSharePlaygroundModal = (isOpen: boolean) => { - const [selectedOptions, setSelectedOptions] = useState>( - () => DEFAULT_SELECTED_OPTIONS - ); + const [selectedOptions, setSelectedOptions] = useState< + Record + >(() => DEFAULT_SELECTED_OPTIONS); const { toast } = useToast(); const [shareableUrl, setShareableUrl] = useState(""); - const [warning, setWarning] = useState<{ title: string; description: string } | null>(null); + const [warning, setWarning] = useState<{ + title: string; + description: string; + } | null>(null); // sharing state only for the active tab const { tabsState } = useContext(PlaygroundContext); - const currentActiveTab = useMemo(() => tabsState.tabs[tabsState.activeTabIndex] ?? {}, [tabsState]); + const currentActiveTab = useMemo( + () => tabsState.tabs[tabsState.activeTabIndex] ?? {}, + [tabsState], + ); // Use useLocalStorage for scripts - const [scriptsTabState] = useLocalStorage<{ [key: string]: Record }>('playground:script:tabState', {}); - const [preFlightScript] = useLocalStorage('playground:pre-flight:selected', null); + const [scriptsTabState] = useLocalStorage<{ + [key: string]: Record; + }>("playground:script:tabState", {}); + const [preFlightScript] = useLocalStorage( + "playground:pre-flight:selected", + null, + ); // Helper to check if a script is valid - const isValidScript = (script: any) => script && typeof script === 'object' && typeof script.id === 'string' && !!script.id && typeof script.content === 'string' && !!script.content; + const isValidScript = (script: any) => + script && + typeof script === "object" && + typeof script.id === "string" && + !!script.id && + typeof script.content === "string" && + !!script.content; // Compute which options should be disabled based on available values const optionsWithDisabledState = useMemo(() => { - return SHARE_OPTIONS.map(option => { + return SHARE_OPTIONS.map((option) => { let isDisabled = option.isDisabled; - + if (!isDisabled) { switch (option.id) { case OPTION_TYPES.OPERATION: break; - + case OPTION_TYPES.VARIABLES: isDisabled = !currentActiveTab.variables; break; @@ -62,13 +89,17 @@ export const useSharePlaygroundModal = (isOpen: boolean) => { } case OPTION_TYPES.PRE_OPERATION: { - const script = currentActiveTab.id ? scriptsTabState[currentActiveTab.id]?.['pre-operation'] : null; + const script = currentActiveTab.id + ? scriptsTabState[currentActiveTab.id]?.["pre-operation"] + : null; isDisabled = !currentActiveTab.id || !isValidScript(script); break; } case OPTION_TYPES.POST_OPERATION: { - const script = currentActiveTab.id ? scriptsTabState[currentActiveTab.id]?.['post-operation'] : null; + const script = currentActiveTab.id + ? scriptsTabState[currentActiveTab.id]?.["post-operation"] + : null; isDisabled = !currentActiveTab.id || !isValidScript(script); break; } @@ -93,33 +124,40 @@ export const useSharePlaygroundModal = (isOpen: boolean) => { generateShareableUrl(DEFAULT_SELECTED_OPTIONS); }, [isOpen]); - const generateShareableUrl = useCallback((options: Record) => { - try { - const stateToShare: PlaygroundUrlState = buildStateToShare(options, currentActiveTab); - - const newUrl = createCompressedStateUrl(stateToShare); - setShareableUrl(newUrl); - - if (newUrl.length > MAX_URL_LENGTH) { - // todo: add a button in error message to easily remove scripts - setWarning({ - title: WARNING_MESSAGES.URL_TOO_LONG.title, - description: WARNING_MESSAGES.URL_TOO_LONG.description, + const generateShareableUrl = useCallback( + (options: Record) => { + try { + const stateToShare: PlaygroundUrlState = buildStateToShare( + options, + currentActiveTab, + ); + + const newUrl = createCompressedStateUrl(stateToShare); + setShareableUrl(newUrl); + + if (newUrl.length > MAX_URL_LENGTH) { + // todo: add a button in error message to easily remove scripts + setWarning({ + title: WARNING_MESSAGES.URL_TOO_LONG.title, + description: WARNING_MESSAGES.URL_TOO_LONG.description, + }); + } else { + setWarning(null); + } + } catch (error) { + toast({ + variant: "destructive", + title: "Something went wrong", + description: + "We couldn't generate the shareable URL. Please try again later", }); - } else { - setWarning(null); - } - } catch (error) { - toast({ - variant: "destructive", - title: "Something went wrong", - description: "We couldn't generate the shareable URL. Please try again later", - }); - if (process.env.NODE_ENV === "development") { - console.error(error); + if (process.env.NODE_ENV === "development") { + console.error(error); + } } - } - }, [toast, currentActiveTab]); + }, + [toast, currentActiveTab], + ); const handleCopyLink = useCallback(() => { try { @@ -144,16 +182,19 @@ export const useSharePlaygroundModal = (isOpen: boolean) => { } }, [shareableUrl, toast]); - const handleOptionChange = useCallback((id: string, checked: boolean) => { - setSelectedOptions(prev => { - const updatedOptions = { ...prev, [id]: !!checked }; + const handleOptionChange = useCallback( + (id: string, checked: boolean) => { + setSelectedOptions((prev) => { + const updatedOptions = { ...prev, [id]: !!checked }; - // generate a new shareable URL on every change - generateShareableUrl(updatedOptions); + // generate a new shareable URL on every change + generateShareableUrl(updatedOptions); - return updatedOptions; - }); - }, [generateShareableUrl]); + return updatedOptions; + }); + }, + [generateShareableUrl], + ); return { options: optionsWithDisabledState, @@ -163,4 +204,4 @@ export const useSharePlaygroundModal = (isOpen: boolean) => { handleCopyLink, handleOptionChange, }; -} \ No newline at end of file +}; diff --git a/studio/src/hooks/use-star-banner-disabled.ts b/studio/src/hooks/use-star-banner-disabled.ts index c13a9ca90d..80740f71a0 100644 --- a/studio/src/hooks/use-star-banner-disabled.ts +++ b/studio/src/hooks/use-star-banner-disabled.ts @@ -1,7 +1,10 @@ import { Dispatch, SetStateAction, useEffect, useState } from "react"; import { useLocalStorage } from "@/hooks/use-local-storage"; -export function useStarBannerDisabled(): [boolean, Dispatch>] { +export function useStarBannerDisabled(): [ + boolean, + Dispatch>, +] { const [isStarBannerDisabled, setIsStarBannerDisabled] = useState(true); const [isStarBannerDisabledOnClient, setDisableStarBanner] = useLocalStorage( "disableStarBanner", @@ -13,4 +16,4 @@ export function useStarBannerDisabled(): [boolean, Dispatch { - return useContext(UserContext) -} \ No newline at end of file + return useContext(UserContext); +}; diff --git a/studio/src/hooks/use-workspace.ts b/studio/src/hooks/use-workspace.ts index d97b636492..8be89dae8d 100644 --- a/studio/src/hooks/use-workspace.ts +++ b/studio/src/hooks/use-workspace.ts @@ -1,5 +1,8 @@ import { useContext } from "react"; -import { WorkspaceContext, WorkspaceContextType } from "@/components/dashboard/workspace-provider"; +import { + WorkspaceContext, + WorkspaceContextType, +} from "@/components/dashboard/workspace-provider"; export function useWorkspace(): WorkspaceContextType { const context = useContext(WorkspaceContext); @@ -8,4 +11,4 @@ export function useWorkspace(): WorkspaceContextType { } return context; -} \ No newline at end of file +} diff --git a/studio/src/lib/constants.ts b/studio/src/lib/constants.ts index f24bfe2379..e163e92f70 100644 --- a/studio/src/lib/constants.ts +++ b/studio/src/lib/constants.ts @@ -119,64 +119,71 @@ export const graphPruningRules = [ ]; export const OPTION_TYPES = { - OPERATION: 'operation', - VARIABLES: 'variables', - HEADERS: 'headers', - PRE_FLIGHT: 'preFlight', - PRE_OPERATION: 'preOperation', - POST_OPERATION: 'postOperation', + OPERATION: "operation", + VARIABLES: "variables", + HEADERS: "headers", + PRE_FLIGHT: "preFlight", + PRE_OPERATION: "preOperation", + POST_OPERATION: "postOperation", } as const; export const hideScriptsSharing = true; export const SHARE_OPTIONS = [ // operation is always checked and disabled - { + { id: OPTION_TYPES.OPERATION, label: "Operation", - description: "The GraphQL operation (query, mutation, or subscription) to be shared", + description: + "The GraphQL operation (query, mutation, or subscription) to be shared", isChecked: true, - isDisabled: true + isDisabled: true, }, - { + { id: OPTION_TYPES.VARIABLES, label: "Variables", description: "The variables used in the GraphQL operation", isChecked: false, - isDisabled: false + isDisabled: false, }, - { + { id: OPTION_TYPES.HEADERS, label: "Headers", description: "The HTTP headers to include in the shared request", isChecked: false, - isDisabled: false + isDisabled: false, }, // [ENG-7093] hiding scripts sharing for now - ...!hideScriptsSharing ? [{ - id: OPTION_TYPES.PRE_FLIGHT, - label: "Pre-Flight Script", - description: "A script that runs before the GraphQL operation is executed", - isChecked: false, - isDisabled: false - }, - { - id: OPTION_TYPES.PRE_OPERATION, - label: "Pre-Operation Script", - description: "A script that runs before sending the GraphQL request", - isChecked: false, - isDisabled: false - }, - { - id: OPTION_TYPES.POST_OPERATION, - label: "Post-Operation Script", - description: "A script that runs after the GraphQL request is completed", - isChecked: false, - isDisabled: false - }] : [], + ...(!hideScriptsSharing + ? [ + { + id: OPTION_TYPES.PRE_FLIGHT, + label: "Pre-Flight Script", + description: + "A script that runs before the GraphQL operation is executed", + isChecked: false, + isDisabled: false, + }, + { + id: OPTION_TYPES.PRE_OPERATION, + label: "Pre-Operation Script", + description: "A script that runs before sending the GraphQL request", + isChecked: false, + isDisabled: false, + }, + { + id: OPTION_TYPES.POST_OPERATION, + label: "Post-Operation Script", + description: + "A script that runs after the GraphQL request is completed", + isChecked: false, + isDisabled: false, + }, + ] + : []), ] as const; -export const PLAYGROUND_STATE_QUERY_PARAM = 'playgroundUrlState'; +export const PLAYGROUND_STATE_QUERY_PARAM = "playgroundUrlState"; export const PLAYGROUND_DEFAULT_QUERY_TEMPLATE = `# Welcome to WunderGraph Studio # @@ -217,7 +224,8 @@ export const roles = [ key: "organization-admin", category: "organization", displayName: "Admin", - description: "Grants full access to the organization and all its resources.", + description: + "Grants full access to the organization and all its resources.", }, { key: "organization-developer", @@ -229,7 +237,8 @@ export const roles = [ key: "organization-apikey-manager", category: "organization", displayName: "API Key Manager", - description: "Grants access to creating, updating and deleting API keys in the organization.", + description: + "Grants access to creating, updating and deleting API keys in the organization.", }, { key: "organization-viewer", @@ -287,4 +296,4 @@ export const roles = [ }, ]; -export type OrganizationRole = typeof roles[number]["key"]; \ No newline at end of file +export type OrganizationRole = (typeof roles)[number]["key"]; diff --git a/studio/src/lib/download-string-as-file.ts b/studio/src/lib/download-string-as-file.ts index 71df681955..d450f24508 100644 --- a/studio/src/lib/download-string-as-file.ts +++ b/studio/src/lib/download-string-as-file.ts @@ -1,7 +1,7 @@ export const downloadStringAsFile = ( content: string, filename: string, - contentType: string + contentType: string, ) => { // Create a Blob object with the content, and the specified content type let blob = new Blob([content], { type: contentType }); diff --git a/studio/src/lib/format-date.ts b/studio/src/lib/format-date.ts index 5118403ba8..6c3f5f85f1 100644 --- a/studio/src/lib/format-date.ts +++ b/studio/src/lib/format-date.ts @@ -1,13 +1,13 @@ export const formatDate = ( value: number | Date, - options?: Intl.DateTimeFormatOptions + options?: Intl.DateTimeFormatOptions, ) => { return Intl.DateTimeFormat(undefined, options).format(value); }; export const formatDateTime = ( value: number | Date, - options?: Intl.DateTimeFormatOptions + options?: Intl.DateTimeFormatOptions, ) => { return formatDate(value, { dateStyle: "medium", diff --git a/studio/src/lib/format-number.ts b/studio/src/lib/format-number.ts index 21260c14c8..4e3a99b661 100644 --- a/studio/src/lib/format-number.ts +++ b/studio/src/lib/format-number.ts @@ -1,6 +1,6 @@ export const formatNumber = ( value: number, - options?: Intl.NumberFormatOptions + options?: Intl.NumberFormatOptions, ) => { return Intl.NumberFormat(undefined, options).format(value).toString(); }; diff --git a/studio/src/lib/format-status.ts b/studio/src/lib/format-status.ts index 0cd2b5e76a..2b47c75a7e 100644 --- a/studio/src/lib/format-status.ts +++ b/studio/src/lib/format-status.ts @@ -1,8 +1,8 @@ -import { sentenceCase } from 'change-case'; +import { sentenceCase } from "change-case"; export const formatStatus = (status: string) => { - if (status === 'success') { - return 'Ready'; - } - return sentenceCase(status); + if (status === "success") { + return "Ready"; + } + return sentenceCase(status); }; diff --git a/studio/src/lib/insights-helpers.ts b/studio/src/lib/insights-helpers.ts index 2fb23da8f5..ca9c649674 100644 --- a/studio/src/lib/insights-helpers.ts +++ b/studio/src/lib/insights-helpers.ts @@ -82,16 +82,16 @@ export const useChartData = ( ...t, value: Number.parseFloat(t.value) || 0, previousValue: Number.parseFloat(t.previousValue) || 0, - p99: 'p99' in t ? Number.parseFloat(t.p99) || 0 : undefined, - p90: 'p90' in t ? Number.parseFloat(t.p90) || 0 : undefined, - p50: 'p50' in t ? Number.parseFloat(t.p50) || 0 : undefined, + p99: "p99" in t ? Number.parseFloat(t.p99) || 0 : undefined, + p90: "p90" in t ? Number.parseFloat(t.p90) || 0 : undefined, + p50: "p50" in t ? Number.parseFloat(t.p50) || 0 : undefined, // We use millisecond timestamp everywhere timestamp: t.timestamp instanceof Date ? new Date(t.timestamp).getTime() : typeof t.timestamp === "string" - ? Number.parseInt(t.timestamp) - : t.timestamp, + ? Number.parseInt(t.timestamp) + : t.timestamp, })); const ticks = data.map((d) => d.timestamp); diff --git a/studio/src/lib/playground-storage.ts b/studio/src/lib/playground-storage.ts index c1eba937fe..a9e1e3e14f 100644 --- a/studio/src/lib/playground-storage.ts +++ b/studio/src/lib/playground-storage.ts @@ -3,40 +3,40 @@ */ export const getPreFlightScript = () => { - const selected = localStorage.getItem('playground:pre-flight:selected'); - const enabled = localStorage.getItem('playground:pre-flight:enabled'); - - if (!selected || selected === 'undefined') return undefined; + const selected = localStorage.getItem("playground:pre-flight:selected"); + const enabled = localStorage.getItem("playground:pre-flight:enabled"); + + if (!selected || selected === "undefined") return undefined; try { return { ...JSON.parse(selected), - enabled: enabled === 'true', + enabled: enabled === "true", }; } catch (e) { - if (process.env.NODE_ENV === 'development') { - console.warn('Failed to parse pre-flight script:', e); + if (process.env.NODE_ENV === "development") { + console.warn("Failed to parse pre-flight script:", e); } return undefined; } -} - +}; + export const getScriptTabState = ( tabId: string, - key: 'pre-operation' | 'post-operation' + key: "pre-operation" | "post-operation", ) => { - const tabState = localStorage.getItem('playground:script:tabState'); + const tabState = localStorage.getItem("playground:script:tabState"); if (!tabState) return undefined; try { const parsed = JSON.parse(tabState); return parsed?.[tabId]?.[key]; } catch (e) { - if (process.env.NODE_ENV === 'development') { + if (process.env.NODE_ENV === "development") { console.warn(`Failed to parse script tab state:${key}:`, e); } return undefined; } -} \ No newline at end of file +}; diff --git a/studio/src/lib/playground-url-state-decoding.ts b/studio/src/lib/playground-url-state-decoding.ts index a88dea0224..9ed78247f8 100644 --- a/studio/src/lib/playground-url-state-decoding.ts +++ b/studio/src/lib/playground-url-state-decoding.ts @@ -1,38 +1,45 @@ /** * This lib focuses on deserialization logic for sharing state */ -import { PlaygroundStateSchema, PlaygroundUrlState } from '../components/playground/types'; -import { decompressFromEncodedURIComponent } from 'lz-string'; -import { PLAYGROUND_STATE_QUERY_PARAM } from './constants'; +import { + PlaygroundStateSchema, + PlaygroundUrlState, +} from "../components/playground/types"; +import { decompressFromEncodedURIComponent } from "lz-string"; +import { PLAYGROUND_STATE_QUERY_PARAM } from "./constants"; /** * Decompresses a URL-safe string into a playground state object - * + * * @param compressedState - The compressed state string to decompress * @returns The decompressed and validated playground state * @throws Error if decompression fails or validation fails */ -export const decompressState = (compressedState: string): PlaygroundUrlState => { +export const decompressState = ( + compressedState: string, +): PlaygroundUrlState => { const decompressed = decompressFromEncodedURIComponent(compressedState); - + if (!decompressed) { - throw new Error('Failed to decompress playground state'); + throw new Error("Failed to decompress playground state"); } - + const parsedState = JSON.parse(decompressed); // Validate using Zod schema const result = PlaygroundStateSchema.safeParse(parsedState); - + if (!result.success) { - throw new Error(`Invalid playground state: ${result.error.errors.map((e) => e.toString()).join('\n')}`); + throw new Error( + `Invalid playground state: ${result.error.errors.map((e) => e.toString()).join("\n")}`, + ); } - - if (process.env.NODE_ENV === 'development') { - console.log('[Playground] decompressed state:', result.data); + + if (process.env.NODE_ENV === "development") { + console.log("[Playground] decompressed state:", result.data); } return result.data; -} +}; /** * Helper function to get the playground state parameter from the current URL @@ -40,10 +47,10 @@ export const decompressState = (compressedState: string): PlaygroundUrlState => export const extractStateFromUrl = (): PlaygroundUrlState | null => { const params = new URLSearchParams(window.location.search); const stateParam = params.get(PLAYGROUND_STATE_QUERY_PARAM); - + if (!stateParam) { - return null; + return null; } - + return decompressState(stateParam); -} \ No newline at end of file +}; diff --git a/studio/src/lib/playground-url-state-encoding.ts b/studio/src/lib/playground-url-state-encoding.ts index f379c3d337..347295380e 100644 --- a/studio/src/lib/playground-url-state-encoding.ts +++ b/studio/src/lib/playground-url-state-encoding.ts @@ -1,17 +1,21 @@ /** * This lib focuses on serialization logic for sharing state */ -import { compressToEncodedURIComponent } from 'lz-string'; -import { PlaygroundUrlState, ShareOptionId, TabState } from '../components/playground/types'; -import { hideScriptsSharing, PLAYGROUND_STATE_QUERY_PARAM } from './constants'; -import { getPreFlightScript, getScriptTabState } from './playground-storage'; +import { compressToEncodedURIComponent } from "lz-string"; +import { + PlaygroundUrlState, + ShareOptionId, + TabState, +} from "../components/playground/types"; +import { hideScriptsSharing, PLAYGROUND_STATE_QUERY_PARAM } from "./constants"; +import { getPreFlightScript, getScriptTabState } from "./playground-storage"; /** * Helper which generates the state to share based on the selected options */ export const buildStateToShare = ( - selectedOptions: Record, - currentTab: TabState + selectedOptions: Record, + currentTab: TabState, ): PlaygroundUrlState => { const { query, variables, headers, id } = currentTab; @@ -36,18 +40,18 @@ export const buildStateToShare = ( const preFlight = getPreFlightScript(); if (preFlight) stateToShare.preFlight = preFlight; } - + if (selectedOptions.preOperation && id) { - stateToShare.preOperation = getScriptTabState(id, 'pre-operation'); + stateToShare.preOperation = getScriptTabState(id, "pre-operation"); } - + if (selectedOptions.postOperation && id) { - stateToShare.postOperation = getScriptTabState(id, 'post-operation'); + stateToShare.postOperation = getScriptTabState(id, "post-operation"); } } - if (process.env.NODE_ENV === 'development') { - console.log('[Playground] compressed state:', stateToShare); + if (process.env.NODE_ENV === "development") { + console.log("[Playground] compressed state:", stateToShare); } return stateToShare; @@ -55,24 +59,29 @@ export const buildStateToShare = ( /** * Creates a URL with the compressed playground state embedded - * + * * @param state - The playground state to embed * @param baseUrl - The base URL to use (defaults to current URL) * @returns A URL with the playground state as a query parameter */ -export const createCompressedStateUrl = (state: PlaygroundUrlState, baseUrl?: string): string => { +export const createCompressedStateUrl = ( + state: PlaygroundUrlState, + baseUrl?: string, +): string => { const compressState = (state: PlaygroundUrlState): string => { - const compressedState = compressToEncodedURIComponent(JSON.stringify(state)); - + const compressedState = compressToEncodedURIComponent( + JSON.stringify(state), + ); + if (!compressedState) { - throw new Error('Failed to compress playground state'); + throw new Error("Failed to compress playground state"); } - + return compressedState; - } + }; const compressedState = compressState(state); const url = new URL(baseUrl || window.location.href); url.searchParams.set(PLAYGROUND_STATE_QUERY_PARAM, compressedState); return url.toString(); -} +}; diff --git a/studio/src/lib/schema-helpers.ts b/studio/src/lib/schema-helpers.ts index b9816abcfd..d1b151e2e7 100644 --- a/studio/src/lib/schema-helpers.ts +++ b/studio/src/lib/schema-helpers.ts @@ -28,7 +28,7 @@ import { visit, getArgumentValues, GraphQLDeprecatedDirective, -} from 'graphql'; +} from "graphql"; import babelPlugin from "prettier/plugins/babel"; import estreePlugin from "prettier/plugins/estree"; import graphQLPlugin from "prettier/plugins/graphql"; @@ -113,7 +113,9 @@ export const mapGraphQLType = ( graphqlType instanceof GraphQLObjectType ? "objects" : "interfaces", interfaces: graphqlType.getInterfaces?.().map((iface) => iface.name) || [], - fields: Object.values(graphqlType.getFields()).map(field => parseField(field.astNode!)), + fields: Object.values(graphqlType.getFields()).map((field) => + parseField(field.astNode!), + ), }; } @@ -121,7 +123,9 @@ export const mapGraphQLType = ( return { ...common, category: "inputs", - fields: Object.values(graphqlType.getFields()).map(field => parseField(field.astNode!)), + fields: Object.values(graphqlType.getFields()).map((field) => + parseField(field.astNode!), + ), }; } @@ -436,7 +440,9 @@ export const getRootDescription = (name: string) => { return getCategoryDescription(noCase(name) as GraphQLTypeCategory); }; -export const parseSchema = (schema?: string): { ast: GraphQLSchema, doc: DocumentNode } | null => { +export const parseSchema = ( + schema?: string, +): { ast: GraphQLSchema; doc: DocumentNode } | null => { if (!schema) return null; try { @@ -475,7 +481,10 @@ export const formatAndParseSchema = async (schema?: string) => { export const useParseSchema = (schema?: string) => { const [isParsing, setIsParsing] = useState(true); - const [astAndDoc, setAstAndDoc] = useState<{ ast: GraphQLSchema | null, doc: DocumentNode | null }>({ ast: null, doc: null }); + const [astAndDoc, setAstAndDoc] = useState<{ + ast: GraphQLSchema | null; + doc: DocumentNode | null; + }>({ ast: null, doc: null }); useEffect(() => { let t: NodeJS.Timeout; @@ -553,15 +562,15 @@ const getTypeName = (ast: TypeNode): string => { case Kind.NON_NULL_TYPE: return `${getTypeName(ast.type)}!`; } -} +}; const parseField = ( field: FieldDefinitionNode | InputValueDefinitionNode, - directives?: ExtractedDirectives + directives?: ExtractedDirectives, ): ParsedGraphQLField => { directives ??= extractDirectives(field); - let args: ParsedGraphQLField['args'] = undefined; + let args: ParsedGraphQLField["args"] = undefined; if (field.kind === Kind.FIELD_DEFINITION && field.arguments) { args = field.arguments.map((arg) => ({ name: arg.name.value, @@ -584,16 +593,18 @@ const parseField = ( args, loc: field.loc, }; -} +}; type ExtractedDirectives = { authenticated: boolean; requiresScopes?: string[][]; deprecationReason: string | null; tags: string[]; -} +}; -export const extractDirectives = (node: ASTNode | undefined | null): ExtractedDirectives => { +export const extractDirectives = ( + node: ASTNode | undefined | null, +): ExtractedDirectives => { const result: ExtractedDirectives = { authenticated: false, requiresScopes: undefined, @@ -602,8 +613,7 @@ export const extractDirectives = (node: ASTNode | undefined | null): ExtractedDi }; if ( - ( - node?.kind !== Kind.OBJECT_TYPE_DEFINITION && + (node?.kind !== Kind.OBJECT_TYPE_DEFINITION && node?.kind !== Kind.INTERFACE_TYPE_DEFINITION && node?.kind !== Kind.ENUM_TYPE_DEFINITION && node?.kind !== Kind.SCALAR_TYPE_DEFINITION && @@ -611,8 +621,7 @@ export const extractDirectives = (node: ASTNode | undefined | null): ExtractedDi node?.kind !== Kind.INPUT_OBJECT_TYPE_DEFINITION && node?.kind !== Kind.FIELD_DEFINITION && node?.kind !== Kind.ENUM_VALUE_DEFINITION && - node?.kind !== Kind.INPUT_VALUE_DEFINITION - ) || + node?.kind !== Kind.INPUT_VALUE_DEFINITION) || !node?.directives?.length ) { return result; @@ -623,25 +632,39 @@ export const extractDirectives = (node: ASTNode | undefined | null): ExtractedDi case "deprecated": // In case the deprecation reason isn't set, we should fall back to the default message so we // properly display a reason and don't exclude the field by accident - const deprecatedDirValues = getArgumentValues(GraphQLDeprecatedDirective, directive); - result.deprecationReason = (deprecatedDirValues.reason || "No longer supported") as string; + const deprecatedDirValues = getArgumentValues( + GraphQLDeprecatedDirective, + directive, + ); + result.deprecationReason = (deprecatedDirValues.reason || + "No longer supported") as string; break; case "authenticated": result.authenticated = true; break; case "requiresScopes": const scopesArg = directive.arguments?.[0]; - if (scopesArg?.name.value === "scopes" && scopesArg?.value.kind === Kind.LIST) { + if ( + scopesArg?.name.value === "scopes" && + scopesArg?.value.kind === Kind.LIST + ) { result.requiresScopes = scopesArg.value.values .filter((value) => value.kind === Kind.LIST) .map((value) => value.values) - .map((value) => value.filter((sv) => sv.kind === Kind.STRING).map((sv) => sv.value)); + .map((value) => + value + .filter((sv) => sv.kind === Kind.STRING) + .map((sv) => sv.value), + ); } break; case "tags": const nameArg = directive.arguments?.[0]; - if (nameArg?.name.value === "name" && nameArg?.value.kind === Kind.STRING) { + if ( + nameArg?.name.value === "name" && + nameArg?.value.kind === Kind.STRING + ) { result.tags.push(nameArg.value.value); } @@ -650,30 +673,32 @@ export const extractDirectives = (node: ASTNode | undefined | null): ExtractedDi } return result; -} +}; -export const getParsedTypes = (document: DocumentNode): GraphQLTypeDefinition[] => { +export const getParsedTypes = ( + document: DocumentNode, +): GraphQLTypeDefinition[] => { let currentType: TypeDefinitionNode | undefined; const types: Record = {}; const getTypeCategory = (node: ASTNode): GraphQLTypeCategory | undefined => { switch (node.kind) { case Kind.OBJECT_TYPE_DEFINITION: - return 'objects'; + return "objects"; case Kind.INTERFACE_TYPE_DEFINITION: - return 'interfaces'; + return "interfaces"; case Kind.ENUM_TYPE_DEFINITION: - return 'enums'; + return "enums"; case Kind.SCALAR_TYPE_DEFINITION: - return 'scalars'; + return "scalars"; case Kind.UNION_TYPE_DEFINITION: - return 'unions'; + return "unions"; case Kind.INPUT_OBJECT_TYPE_DEFINITION: - return 'inputs'; + return "inputs"; } return undefined; - } + }; const ensureTypeExists = (node: TypeDefinitionNode) => { const typeName = node.name.value; @@ -697,7 +722,7 @@ export const getParsedTypes = (document: DocumentNode): GraphQLTypeDefinition[] interfaces, loc: node.loc, }; - } + }; const processType = (node: TypeDefinitionNode) => { currentType = node; @@ -751,16 +776,17 @@ export const getParsedTypes = (document: DocumentNode): GraphQLTypeDefinition[] }); return Object.values(types); -} +}; export const getDeprecatedTypes = (types: GraphQLTypeDefinition[]) => { let count = 0; const result: GraphQLTypeDefinition[] = []; for (const typeDefinition of types) { - const fields = typeDefinition.fields?.filter((field) => - field.deprecationReason !== null || - field.args?.some((arg) => arg.deprecationReason !== null) + const fields = typeDefinition.fields?.filter( + (field) => + field.deprecationReason !== null || + field.args?.some((arg) => arg.deprecationReason !== null), ); if (!fields?.length) { @@ -772,21 +798,24 @@ export const getDeprecatedTypes = (types: GraphQLTypeDefinition[]) => { } return [count, result] as const; -} +}; export const getAuthenticatedTypes = (types: GraphQLTypeDefinition[]) => { let count = 0; const result: GraphQLTypeDefinition[] = []; for (const typeDefinition of types) { - if (typeDefinition.authenticated || !!typeDefinition.requiresScopes?.length) { + if ( + typeDefinition.authenticated || + !!typeDefinition.requiresScopes?.length + ) { count++; result.push(typeDefinition); continue; } const fields = typeDefinition.fields?.filter( - (field) => field.authenticated || !!field.requiresScopes?.length + (field) => field.authenticated || !!field.requiresScopes?.length, ); if (!fields?.length) { @@ -868,7 +897,9 @@ export const searchSchema = (searchValue: string, schema: GraphQLSchema) => { } const typeMap = schema.getTypeMap(); - let typeNames = Object.keys(typeMap).filter((typeName) => !typeName.startsWith("__")); + let typeNames = Object.keys(typeMap).filter( + (typeName) => !typeName.startsWith("__"), + ); for (const typeName of typeNames) { if (matches.types.length + matches.fields.length >= 100) { @@ -917,7 +948,12 @@ export const searchSchema = (searchValue: string, schema: GraphQLSchema) => { matches["fields"].push( ...(matchingArgs - ? matchingArgs.map((argument) => ({ type, field, parsed: parseField(field.astNode!), argument })) + ? matchingArgs.map((argument) => ({ + type, + field, + parsed: parseField(field.astNode!), + argument, + })) : [{ type, field, parsed: parseField(field.astNode!) }]), ); } diff --git a/studio/src/lib/signup-content.ts b/studio/src/lib/signup-content.ts index 9770dff6d7..a36e4cce13 100644 --- a/studio/src/lib/signup-content.ts +++ b/studio/src/lib/signup-content.ts @@ -55,7 +55,8 @@ const signupContentMap: Record = { }, apollo: { heading: "Sign up for free", - description: "Try Cosmo managed and migrate from Apollo GraphOS in minutes. No credit card required.", + description: + "Try Cosmo managed and migrate from Apollo GraphOS in minutes. No credit card required.", marketingTitle: "Migrate to Cosmo\nfrom Apollo GraphOS", marketingDescription: "Escape the vendor lock. 100% open source GraphQL Federation with full control. A drop-in GraphOS replacement.", @@ -93,7 +94,9 @@ const signupContentMap: Record = { * @param variant - The signup variant (from URL parameter) * @returns The content configuration for the variant */ -export const getSignupContent = (variant: SignupVariant = "default"): SignupContent => { +export const getSignupContent = ( + variant: SignupVariant = "default", +): SignupContent => { return signupContentMap[variant] || signupContentMap.default; }; diff --git a/studio/src/lib/utils.ts b/studio/src/lib/utils.ts index 5663663146..2eed458e49 100644 --- a/studio/src/lib/utils.ts +++ b/studio/src/lib/utils.ts @@ -12,7 +12,10 @@ export function clamp(value: number, min: number, max: number): number { return Number.isNaN(result) ? min : result; } -export function distinctBy(source: T[], keySelector: (item: T) => TKey) { +export function distinctBy( + source: T[], + keySelector: (item: T) => TKey, +) { const keys = new Set(); return source.filter((item) => { const key = keySelector(item); @@ -82,4 +85,4 @@ export const countLintConfigsByCategory = (lintConfigs: LintConfig[]) => { countAlphabeticalSortRules, countOtherRules, ]; -}; \ No newline at end of file +}; diff --git a/studio/src/middleware.ts b/studio/src/middleware.ts index cbdee68fdb..e9f82303f1 100644 --- a/studio/src/middleware.ts +++ b/studio/src/middleware.ts @@ -1,25 +1,25 @@ -import { NextResponse, NextRequest } from 'next/server'; +import { NextResponse, NextRequest } from "next/server"; const TRACKING_KEYS = [ - 'utm_campaign', - 'utm_content', - 'utm_id', - 'utm_icid', - 'utm_ICID', - 'utm_medium', - 'utm_source', - 'utm_term', - 'dclid', - 'fbclid', - 'gbraid', - 'gclid', - 'ko_click_id', - 'li_fat_id', - 'msclkid', - 'rtd_cid', - 'ttclid', - 'twclid', - 'wbraid', + "utm_campaign", + "utm_content", + "utm_id", + "utm_icid", + "utm_ICID", + "utm_medium", + "utm_source", + "utm_term", + "dclid", + "fbclid", + "gbraid", + "gclid", + "ko_click_id", + "li_fat_id", + "msclkid", + "rtd_cid", + "ttclid", + "twclid", + "wbraid", ]; function setCookie(res: NextResponse, key: string, value: string) { @@ -29,10 +29,10 @@ function setCookie(res: NextResponse, key: string, value: string) { } res.cookies.set(key, value, { - path: '/', + path: "/", domain, maxAge: 30 * 24 * 60 * 60, // 1 month - sameSite: 'lax', + sameSite: "lax", secure: true, httpOnly: false, // Client-side scripts need to be able to read the cookie value }); @@ -45,7 +45,7 @@ export function middleware(req: NextRequest) { let paramValue = searchParams.get(key); if (paramValue) { setCookie(res, key, paramValue); - } else if (['utm_medium', 'utm_source'].includes(key)) { + } else if (["utm_medium", "utm_source"].includes(key)) { const existingValue = req.cookies.get(key); if (existingValue) { // Do not overwrite existing cookies @@ -53,10 +53,10 @@ export function middleware(req: NextRequest) { } let forcedValue: string; - if (key === 'utm_medium') { - forcedValue = 'website'; + if (key === "utm_medium") { + forcedValue = "website"; } else { - forcedValue = 'direct'; + forcedValue = "direct"; } setCookie(res, key, forcedValue); @@ -68,6 +68,6 @@ export function middleware(req: NextRequest) { export const config = { matcher: [ - '/((?!_next/static|_next/image|.well-known|favicon.ico|favicon/manifest.json|manifest.json|.*\\.(?:svg|png|jpg|jpeg|gif|webp|woff2)$).*)', + "/((?!_next/static|_next/image|.well-known|favicon.ico|favicon/manifest.json|manifest.json|.*\\.(?:svg|png|jpg|jpeg|gif|webp|woff2)$).*)", ], }; diff --git a/studio/src/pages/cosmo-managed-solution-terms.md b/studio/src/pages/cosmo-managed-solution-terms.md index cfd0adf3e9..472eba5248 100644 --- a/studio/src/pages/cosmo-managed-solution-terms.md +++ b/studio/src/pages/cosmo-managed-solution-terms.md @@ -4,70 +4,71 @@ Last modified: August 1st, 2023 These WunderGraph, Inc. Cosmo Managed Service Terms of Use (**“Terms”**), together with the sign-up information provided by you (as **“Customer”**), the WunderGraph Website Terms of Use which are incorporated herein by reference and available at: https://wundergraph.com/terms, the WunderGraph Website Privacy Policy which is incorporated herein by reference and available at: https://wundergraph.com/privacy-policy, and the WunderGraph Cosmo order form (**“Order Form”**) executed by WunderGraph and Customer, the terms of which are fully incorporated by reference herein (collectively, the **“Agreement”**) are the only terms that govern WunderGraph, Inc.’s (**“WunderGraph”**) provision to Customer of the WunderGraph Cosmo Service (as defined below). In the event of a conflict between the terms of these Terms and the terms of an applicable Order Form, the terms of the applicable Order Form shall take precedence. WunderGraph’s provision of the Service hereunder is expressly conditioned upon Customer’s assent to these Terms and the Agreement. WunderGraph and Customer may be referred to herein collectively as the **“Parties”** or individually as a **“Party”**. -### 1. Definitions. +### 1. Definitions. - a. **“Customer Data”** means any and all data and other information of any kind collected, uploaded, transmitted, submitted, posted or otherwise received from Customer and hosted on the Service or hosted on Customer’s device(s), either locally or on a Customer-hosted environment. +a. **“Customer Data”** means any and all data and other information of any kind collected, uploaded, transmitted, submitted, posted or otherwise received from Customer and hosted on the Service or hosted on Customer’s device(s), either locally or on a Customer-hosted environment. - b. **“Service”** means the WunderGraph Cosmo service, which is a suite of applications that are delivered to Customer as a Software (as defined below) download or cloud-provided solution or combination thereof as a paid subscription, as further described in the WunderGraph Cosmo subscription level plans and features overview available at: https://wundergraph.com/pricing; provided, that the Service shall not include Customer Data. +b. **“Service”** means the WunderGraph Cosmo service, which is a suite of applications that are delivered to Customer as a Software (as defined below) download or cloud-provided solution or combination thereof as a paid subscription, as further described in the WunderGraph Cosmo subscription level plans and features overview available at: https://wundergraph.com/pricing; provided, that the Service shall not include Customer Data. - c. **“Software”** means any software or documentation related to the Service. +c. **“Software”** means any software or documentation related to the Service. - d. **“Updates”** means any bug fixes, patches, or other error corrections to the Software or Service that, in the sole discretion of WunderGraph, WunderGraph generally makes available free of charge to all licensees of the Software or Service unless such Update requires an upgrade in Service subscription level; provided, that Updates shall not include any new features to the Software or Service. +d. **“Updates”** means any bug fixes, patches, or other error corrections to the Software or Service that, in the sole discretion of WunderGraph, WunderGraph generally makes available free of charge to all licensees of the Software or Service unless such Update requires an upgrade in Service subscription level; provided, that Updates shall not include any new features to the Software or Service. -### 2. Eligibility; License. +### 2. Eligibility; License. - a. **Eligibility.** To sign up for the Service, Customer must have a functioning account and valid e-mail address for receiving billing information and Service alerts. +a. **Eligibility.** To sign up for the Service, Customer must have a functioning account and valid e-mail address for receiving billing information and Service alerts. - b. **License Grant.** Subject to and conditioned on Customer's compliance with all the terms and conditions of this Agreement and payment of Fees (as defined below), WunderGraph hereby grants Customer a limited, worldwide, non-exclusive, revocable, non-sublicensable, and non-transferable license during the Service subscription term to use the Software and Service solely for the purposes authorized under Customer’s Service subscription plan and level. Upon payment of Fees, WunderGraph will provide to Customer a license key and allow access to the Software and Service. +b. **License Grant.** Subject to and conditioned on Customer's compliance with all the terms and conditions of this Agreement and payment of Fees (as defined below), WunderGraph hereby grants Customer a limited, worldwide, non-exclusive, revocable, non-sublicensable, and non-transferable license during the Service subscription term to use the Software and Service solely for the purposes authorized under Customer’s Service subscription plan and level. Upon payment of Fees, WunderGraph will provide to Customer a license key and allow access to the Software and Service. - c. **Use Restrictions.** Customer shall not use the Software and/or Service for any purposes beyond the scope of the license granted in this Agreement. Without limiting the foregoing and except as otherwise expressly set forth in this Agreement, Customer shall not at any time, directly or indirectly: (i) copy, modify, or create derivative works of the Software or Service, in whole or in part; (ii) rent, lease, lend, sell, sublicense, assign, distribute, publish, transfer, or otherwise make available the Software or Service to others; (iii) reverse engineer, disassemble, decompile, decode, adapt, or otherwise attempt to derive or gain access to the source code of the Software or Service, in whole or in part, for any purpose including, without limitation, developing, selling or providing any software or service in competition with the Service; (iv) remove any proprietary notices from the Software or Service; or (v) use the Software or Service in any manner or for any purpose that infringes, misappropriates, or otherwise violates any intellectual property right or other right of any person, or that violates any applicable law. +c. **Use Restrictions.** Customer shall not use the Software and/or Service for any purposes beyond the scope of the license granted in this Agreement. Without limiting the foregoing and except as otherwise expressly set forth in this Agreement, Customer shall not at any time, directly or indirectly: (i) copy, modify, or create derivative works of the Software or Service, in whole or in part; (ii) rent, lease, lend, sell, sublicense, assign, distribute, publish, transfer, or otherwise make available the Software or Service to others; (iii) reverse engineer, disassemble, decompile, decode, adapt, or otherwise attempt to derive or gain access to the source code of the Software or Service, in whole or in part, for any purpose including, without limitation, developing, selling or providing any software or service in competition with the Service; (iv) remove any proprietary notices from the Software or Service; or (v) use the Software or Service in any manner or for any purpose that infringes, misappropriates, or otherwise violates any intellectual property right or other right of any person, or that violates any applicable law. - d. **Reservation of Rights.** WunderGraph reserves all rights not expressly granted to Customer in this Agreement. Except for the limited rights and licenses expressly granted under this Agreement, nothing in this Agreement grants, by implication, waiver, estoppel, or otherwise, to Customer or any third party any intellectual property rights or other right, title, or interest in or to the Software or Service. +d. **Reservation of Rights.** WunderGraph reserves all rights not expressly granted to Customer in this Agreement. Except for the limited rights and licenses expressly granted under this Agreement, nothing in this Agreement grants, by implication, waiver, estoppel, or otherwise, to Customer or any third party any intellectual property rights or other right, title, or interest in or to the Software or Service. - e. **Customer Data.** Customer is and shall remain the sole and exclusive owner of all Customer Data. To the extent Customer chooses to host Customer Data on the cloud-based Service provided by WunderGraph, Customer hereby grants to WunderGraph a non-exclusive, non-transferable, non-sublicensable license to access and/or use the Customer Data solely as necessary to provide the Service for Customer’s benefit as provided in this Agreement. Except for the limited license provided in this Section, nothing contained in this Agreement shall be construed as granting WunderGraph or any third party any right, title or interest in or to any Customer Data, whether by implication, estoppel or otherwise. +e. **Customer Data.** Customer is and shall remain the sole and exclusive owner of all Customer Data. To the extent Customer chooses to host Customer Data on the cloud-based Service provided by WunderGraph, Customer hereby grants to WunderGraph a non-exclusive, non-transferable, non-sublicensable license to access and/or use the Customer Data solely as necessary to provide the Service for Customer’s benefit as provided in this Agreement. Except for the limited license provided in this Section, nothing contained in this Agreement shall be construed as granting WunderGraph or any third party any right, title or interest in or to any Customer Data, whether by implication, estoppel or otherwise. - f. **Customer Responsibilities.** Customer is responsible and liable for all uses of the Software and/or Service under Customer’s subscription, regardless of whether such access or use is permitted by or in violation of this Agreement. +f. **Customer Responsibilities.** Customer is responsible and liable for all uses of the Software and/or Service under Customer’s subscription, regardless of whether such access or use is permitted by or in violation of this Agreement. -### 3. Discontinuance or Modification of Service. +### 3. Discontinuance or Modification of Service. WunderGraph may at its sole discretion discontinue or modify the Software or Service or any features thereof in whole or in part, modify the Software or Service specifications in whole or in part, or replace the Software or Service with similar WunderGraph or third party products at any time upon providing advance written Notice (as defined below) to Customer with a description of such discontinuation, modification or replacement. -### 4. No Support; Suspension of Services; Usage Limits; Extensions. +### 4. No Support; Suspension of Services; Usage Limits; Extensions. - a. WunderGraph provides no support for the Software or Service except and unless Customer purchases a separate support contract available only upon Customer request. WunderGraph will provide Updates to the Software or Service as they become available. Customer assumes all risks and WunderGraph assumes no liability whatsoever if Customer fails to install Updates to Software to the latest version when made available by WunderGraph. +a. WunderGraph provides no support for the Software or Service except and unless Customer purchases a separate support contract available only upon Customer request. WunderGraph will provide Updates to the Software or Service as they become available. Customer assumes all risks and WunderGraph assumes no liability whatsoever if Customer fails to install Updates to Software to the latest version when made available by WunderGraph. - b. WunderGraph may suspend the Service in any one of the following cases: (i) when it is unavoidable due to maintenance; (ii) when it is unavoidable to remedy errors in telecommunication facilities at WunderGraph’s premises; (iii) when the Service cannot be provided because of suspension, outage or unavailability of service providers WunderGraph relies upon to provide the Service, including hosting providers, telecommunications circuit facility providers or internet access providers to whom WunderGraph is connected; (iv) when an illegal connection from Customer or third party is detected and there is no other means to prevent it; (v) when WunderGraph in its sole discretion is required to do so for the public interest, to protect against or prevent harm to WunderGraph and/or its users, or as required by law; (vi) when and for so long as Customer fails to pay any amounts due hereunder in a timely manner; or (vii) when Customer exceeds its allowed usage limits for its Service subscription plan level. Customer will be notified of the date and duration of such suspension in advance except in emergency situations or when Customer fails to make timely payment or exceeds its usage limits for its Service subscription plan level. +b. WunderGraph may suspend the Service in any one of the following cases: (i) when it is unavoidable due to maintenance; (ii) when it is unavoidable to remedy errors in telecommunication facilities at WunderGraph’s premises; (iii) when the Service cannot be provided because of suspension, outage or unavailability of service providers WunderGraph relies upon to provide the Service, including hosting providers, telecommunications circuit facility providers or internet access providers to whom WunderGraph is connected; (iv) when an illegal connection from Customer or third party is detected and there is no other means to prevent it; (v) when WunderGraph in its sole discretion is required to do so for the public interest, to protect against or prevent harm to WunderGraph and/or its users, or as required by law; (vi) when and for so long as Customer fails to pay any amounts due hereunder in a timely manner; or (vii) when Customer exceeds its allowed usage limits for its Service subscription plan level. Customer will be notified of the date and duration of such suspension in advance except in emergency situations or when Customer fails to make timely payment or exceeds its usage limits for its Service subscription plan level. - c. Customer acknowledges and agrees that each Service subscription plan level contains feature and/or usage limits. Usage limits for the Service apply on a monthly basis, even in an annual Service subscription plan. Customer may purchase usage limit extensions (**“Extensions”**) at a fixed size according to the price list currently in effect and as amended from time to time. All Extensions are non-refundable, and are applicable only to the then-current month. At the end of the month any remaining usage credit under the Extension will become void and Customer’s usage limit will revert to its regular level under its Service subscription plan. In the event Customer exceeds the usage limits applicable to its Service subscription plan, WunderGraph reserves the right to suspend Customer’s usage of Services without prior notice until the beginning of the next month of the Customer’s Service subscription plan or Customer’s purchase of an Extension, whichever occurs first. +c. Customer acknowledges and agrees that each Service subscription plan level contains feature and/or usage limits. Usage limits for the Service apply on a monthly basis, even in an annual Service subscription plan. Customer may purchase usage limit extensions (**“Extensions”**) at a fixed size according to the price list currently in effect and as amended from time to time. All Extensions are non-refundable, and are applicable only to the then-current month. At the end of the month any remaining usage credit under the Extension will become void and Customer’s usage limit will revert to its regular level under its Service subscription plan. In the event Customer exceeds the usage limits applicable to its Service subscription plan, WunderGraph reserves the right to suspend Customer’s usage of Services without prior notice until the beginning of the next month of the Customer’s Service subscription plan or Customer’s purchase of an Extension, whichever occurs first. -### 5. Fees and Payment; Service Upgrades. - a. **Fees.** As a condition of using the Service, Customer consents to WunderGraph’s use of third-party merchants of record (**“Merchant of Record”**) to facilitate any payments hereunder. Customer shall pay WunderGraph or the applicable Merchant of Record designated by WunderGraph (as the case may be) the subscription fee amount (**“Fees”**) for the applicable Service level plan according to the official WunderGraph Service price list at the time of sign-up and as amended from time to time, without offset or deduction. WunderGraph reserves the right to increase Fees at the time of each Service renewal to reflect the price levels in effect on the official WunderGraph Service price list at the time of renewal. Customer shall make all payments hereunder in U.S. dollars or other applicable currency offered by the applicable Merchant of Record. Fees for monthly and annual Service plans are due and paid in full at the start of the subscription term. If Customer fails to make any payment when due, in addition to all other remedies that may be available: (i) WunderGraph may suspend Customer’s access to the Service until payment is made; and (ii) WunderGraph may charge Customer a reactivation fee for restoring use of the Service. +### 5. Fees and Payment; Service Upgrades. - b. **Taxes.** All Fees and other amounts payable by Customer under this Agreement are exclusive of taxes and similar assessments. Customer is responsible for all sales, use, and excise taxes, and any other similar taxes, duties, and charges of any kind imposed by any national, federal, state, or local governmental or regulatory authority on any amounts payable by Customer hereunder. +a. **Fees.** As a condition of using the Service, Customer consents to WunderGraph’s use of third-party merchants of record (**“Merchant of Record”**) to facilitate any payments hereunder. Customer shall pay WunderGraph or the applicable Merchant of Record designated by WunderGraph (as the case may be) the subscription fee amount (**“Fees”**) for the applicable Service level plan according to the official WunderGraph Service price list at the time of sign-up and as amended from time to time, without offset or deduction. WunderGraph reserves the right to increase Fees at the time of each Service renewal to reflect the price levels in effect on the official WunderGraph Service price list at the time of renewal. Customer shall make all payments hereunder in U.S. dollars or other applicable currency offered by the applicable Merchant of Record. Fees for monthly and annual Service plans are due and paid in full at the start of the subscription term. If Customer fails to make any payment when due, in addition to all other remedies that may be available: (i) WunderGraph may suspend Customer’s access to the Service until payment is made; and (ii) WunderGraph may charge Customer a reactivation fee for restoring use of the Service. - c. **Service Upgrades.** Customer may upgrade (but not downgrade) its Service subscription plan level at any time upon written Notice to WunderGraph. Upon upgrading, the Fees for the new Service subscription plan level are due immediately. In the event Customer has paid for an annual Service subscription plan level, upon upgrading, the amount paid by Customer for its previous annual Service subscription plan level will be refunded to Customer in a separate transaction on a pro rata basis to the extent any unused months are left in such Service subscription plan. +b. **Taxes.** All Fees and other amounts payable by Customer under this Agreement are exclusive of taxes and similar assessments. Customer is responsible for all sales, use, and excise taxes, and any other similar taxes, duties, and charges of any kind imposed by any national, federal, state, or local governmental or regulatory authority on any amounts payable by Customer hereunder. -### 6. Intellectual Property Ownership. +c. **Service Upgrades.** Customer may upgrade (but not downgrade) its Service subscription plan level at any time upon written Notice to WunderGraph. Upon upgrading, the Fees for the new Service subscription plan level are due immediately. In the event Customer has paid for an annual Service subscription plan level, upon upgrading, the amount paid by Customer for its previous annual Service subscription plan level will be refunded to Customer in a separate transaction on a pro rata basis to the extent any unused months are left in such Service subscription plan. -Customer acknowledges that, as between Customer and WunderGraph, WunderGraph and/or its licensors exclusively own all right, title, and interest, including all intellectual property rights, in and to the Software and Service. No rights to the Software or Service are granted to Customer except as expressly provided in this Agreement. +### 6. Intellectual Property Ownership. -### 7. Warranty Disclaimer; Beta Disclaimer. +Customer acknowledges that, as between Customer and WunderGraph, WunderGraph and/or its licensors exclusively own all right, title, and interest, including all intellectual property rights, in and to the Software and Service. No rights to the Software or Service are granted to Customer except as expressly provided in this Agreement. - a. **THE SOFTWARE AND SERVICE ARE PROVIDED "AS IS" AND “AS AVAILABLE” AND WUNDERGRAPH HEREBY DISCLAIMS ALL WARRANTIES, WHETHER EXPRESS, IMPLIED, STATUTORY, OR OTHERWISE. WUNDERGRAPH SPECIFICALLY DISCLAIMS ALL IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE, AND NON-INFRINGEMENT, AND ALL WARRANTIES ARISING FROM COURSE OF DEALING, USAGE, OR TRADE PRACTICE. WUNDERGRAPH MAKES NO WARRANTY OF ANY KIND THAT THE SOFTWARE AND/OR SERVICE, OR ANY PRODUCTS OR RESULTS OF THE USE THEREOF, WILL MEET CUSTOMER'S OR ANY OTHER PERSON'S REQUIREMENTS, OPERATE WITHOUT INTERRUPTION, ACHIEVE ANY INTENDED RESULT, BE COMPATIBLE OR WORK WITH ANY PRODUCTS, SYSTEMS OR OTHER SERVICES, OR BE SECURE, ACCURATE, COMPLETE, FREE OF HARMFUL CODE, OR ERROR FREE.** +### 7. Warranty Disclaimer; Beta Disclaimer. - b. **BETA DISCLAIMER. FEATURES OF THE SOFTWARE OR SERVICE THAT ARE IDENTIFED BY WUNDERGRAPH AS “BETA” FORM MAY CONTAIN DEFECTS. A PRIMARY PURPOSE OF SUCH BETA PROGRAMS IS TO OBTAIN FEEDBACK ON SERVICE PERFORMANCE AND THE IDENTIFICATION OF DEFECTS. CUSTOMER IS ADVISED TO SAFEGUARD IMPORTANT DATA, TO USE CAUTION AND NOT TO RELY IN ANY WAY ON THE CORRECT FUNCTIONING OR PERFORMANCE OF THE BETA SOFTWARE OR SERVICE FEATURE. THE BETA SOFTWARE AND SERVICE FEATURES ARE PROVIDED BY WUNDERGRAPH ON AN "AS IS" AND "AS AVAILABLE" BASIS, WITHOUT ANY WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED.** +a. **THE SOFTWARE AND SERVICE ARE PROVIDED "AS IS" AND “AS AVAILABLE” AND WUNDERGRAPH HEREBY DISCLAIMS ALL WARRANTIES, WHETHER EXPRESS, IMPLIED, STATUTORY, OR OTHERWISE. WUNDERGRAPH SPECIFICALLY DISCLAIMS ALL IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE, AND NON-INFRINGEMENT, AND ALL WARRANTIES ARISING FROM COURSE OF DEALING, USAGE, OR TRADE PRACTICE. WUNDERGRAPH MAKES NO WARRANTY OF ANY KIND THAT THE SOFTWARE AND/OR SERVICE, OR ANY PRODUCTS OR RESULTS OF THE USE THEREOF, WILL MEET CUSTOMER'S OR ANY OTHER PERSON'S REQUIREMENTS, OPERATE WITHOUT INTERRUPTION, ACHIEVE ANY INTENDED RESULT, BE COMPATIBLE OR WORK WITH ANY PRODUCTS, SYSTEMS OR OTHER SERVICES, OR BE SECURE, ACCURATE, COMPLETE, FREE OF HARMFUL CODE, OR ERROR FREE.** + +b. **BETA DISCLAIMER. FEATURES OF THE SOFTWARE OR SERVICE THAT ARE IDENTIFED BY WUNDERGRAPH AS “BETA” FORM MAY CONTAIN DEFECTS. A PRIMARY PURPOSE OF SUCH BETA PROGRAMS IS TO OBTAIN FEEDBACK ON SERVICE PERFORMANCE AND THE IDENTIFICATION OF DEFECTS. CUSTOMER IS ADVISED TO SAFEGUARD IMPORTANT DATA, TO USE CAUTION AND NOT TO RELY IN ANY WAY ON THE CORRECT FUNCTIONING OR PERFORMANCE OF THE BETA SOFTWARE OR SERVICE FEATURE. THE BETA SOFTWARE AND SERVICE FEATURES ARE PROVIDED BY WUNDERGRAPH ON AN "AS IS" AND "AS AVAILABLE" BASIS, WITHOUT ANY WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED.** **THE FOREGOING DOES NOT AFFECT ANY WARRANTIES THAT CANNOT BE EXCLUDED OR LIMITED UNDER APPLICABLE LAW.** -### 8. Indemnification. +### 8. Indemnification. Customer shall indemnify, hold harmless, and, at WunderGraph's option, defend WunderGraph, its affiliates, directors, officers, employees and agents (collectively, **“WunderGraph Indemnitees”**) from and against any and all claims, losses, damages, liabilities, penalties, and costs (including attorneys' fees) (collectively, **“Claims”**) incurred by WunderGraph Indemnitees arising out of or relating to Customer's: (i) negligence or willful misconduct; (ii) use of the Software or Service in a manner not authorized or contemplated by this Agreement; (iii) use of the Software or Service in combination with data, software, hardware, equipment or technology not provided by WunderGraph or authorized by WunderGraph in writing; (iv) modifications to the Software or Service not made by WunderGraph; (v) misuse of the Software or Service causing damage to WunderGraph’s or a third party’s network, servers or reputation; (vi) infringement or misappropriation of the intellectual property rights of a third party; (vii) violation of applicable law or regulation; or (viii) use of any version other than the most current version of the Software and/or Service provided to Customer, provided that Customer may not settle any such Claim against WunderGraph Indemnitees unless WunderGraph consents to such settlement, and further provided that WunderGraph will have the right, at its option, to defend itself against any such such Claim or to participate in the defense thereof by counsel of its own choice. -### 9. Assumption of Risk. +### 9. Assumption of Risk. The entire risk of the use, results and performance of the Service is borne by Customer. WunderGraph assumes no responsibility whatsoever for Customer’s use of the Software and/or Service, including without limitation use of any Customer-hosted environments, and WunderGraph shall not be liable for any losses incurred by Customer or any third party arising out of or relating to Customer’s use of the Software and/or Service. Customer uses and relies on the Software and/or Service at its own risk. If any deficiencies are noted in the Software and/or Service, Customer shall immediately cease all use of the Software and/or Service and not rely upon the same in any capacity. Customer’s sole and exclusive remedy relative to any defects or deficiencies in the Software and/or Service shall be as set forth in Section 10 hereof. -## 10. Limitations of Liability.## +## 10. Limitations of Liability.## **IN NO EVENT SHALL WUNDERGRAPH BE LIABLE UNDER OR IN CONNECTION WITH THIS AGREEMENT, THE SOFTWARE AND/OR SERVICE UNDER ANY LEGAL OR EQUITABLE THEORY, INCLUDING BREACH OF CONTRACT, TORT (INCLUDING NEGLIGENCE), STRICT LIABILITY, AND OTHERWISE, FOR ANY: (a) CONSEQUENTIAL, INCIDENTAL, INDIRECT, EXEMPLARY, SPECIAL, ENHANCED, OR PUNITIVE DAMAGES; (b) INCREASED COSTS, DIMINUTION IN VALUE OR LOST BUSINESS, PRODUCTION, REVENUES, OR PROFITS; (c) LOSS OF GOODWILL OR REPUTATION; (d) USE, INABILITY TO USE, LOSS, INTERRUPTION, DELAY OR RECOVERY OF ANY DATA, OR BREACH OF DATA OR SYSTEM SECURITY; OR (e) COST OF REPLACEMENT PRODUCTS OR SERVICES, IN EACH CASE REGARDLESS OF WHETHER WUNDERGRAPH WAS ADVISED OF THE POSSIBILITY OF SUCH LOSSES OR DAMAGES OR SUCH LOSSES OR DAMAGES WERE OTHERWISE FORESEEABLE. IN NO EVENT SHALL WUNDERGRAPH’S AGGREGATE LIABILITY ARISING OUT OF OR RELATED TO THIS AGREEMENT, THE SOFTWARE AND/OR SERVICE UNDER ANY LEGAL OR EQUITABLE THEORY, INCLUDING BREACH OF CONTRACT, TORT (INCLUDING NEGLIGENCE), STRICT LIABILITY, AND OTHERWISE EXCEED THE AMOUNT OF ONE (1) MONTH’S FEES FOR THE SERVICE PROVIDED BY WUNDERGRAPH.** @@ -77,38 +78,38 @@ WunderGraph will not be liable if Customer or any third party suffers damage res **THE FOREGOING DOES NOT AFFECT ANY LIABILITY THAT CANNOT BE EXCLUDED OR LIMITED UNDER APPLICABLE LAW.** -### 11. Term and Termination. +### 11. Term and Termination. - a. **Term.** This Agreement and the license granted hereunder are effective on the date Customer first uses the Software and/or Service and shall continue for as long as Customer uses the Software and/or Service and pays all Fees, unless this Agreement is terminated under this section. Customer’s obligations under these Terms will survive any termination of this Agreement and will remain in effect as long as the Software remains on any device owned or used by Customer. The Service subscription term will be mutually agreed by the Parties in the applicable Order Form. This Agreement will automatically renew for additional renewal periods of the same duration of the Service subscription term as originally set forth in the applicable Order Form. Either Party may terminate this Agreement by providing written Notice of non-renewal to the other Party at least thirty (30) days prior to the expiration of the then-current term, at which time this Agreement shall terminate at the end of such term. No refunds of Fees will be given. +a. **Term.** This Agreement and the license granted hereunder are effective on the date Customer first uses the Software and/or Service and shall continue for as long as Customer uses the Software and/or Service and pays all Fees, unless this Agreement is terminated under this section. Customer’s obligations under these Terms will survive any termination of this Agreement and will remain in effect as long as the Software remains on any device owned or used by Customer. The Service subscription term will be mutually agreed by the Parties in the applicable Order Form. This Agreement will automatically renew for additional renewal periods of the same duration of the Service subscription term as originally set forth in the applicable Order Form. Either Party may terminate this Agreement by providing written Notice of non-renewal to the other Party at least thirty (30) days prior to the expiration of the then-current term, at which time this Agreement shall terminate at the end of such term. No refunds of Fees will be given. - b. **Termination.** In addition to any other express termination right set forth in this Agreement: -(i) WunderGraph may terminate this Agreement, effective on written Notice to Customer, if Customer: (A) fails to pay any amount when due hereunder, and such failure continues more than ten (10) days after WunderGraph’s written Notice thereof; or (B) breaches any of its obligations under Section 2(c); -(ii) either Party may terminate this Agreement, effective on written Notice to the other Party, if the other Party materially breaches this Agreement, and such breach: (A) is incapable of cure; or (B) being capable of cure, remains uncured thirty (30) days after the non-breaching Party provides the breaching Party with written Notice of such breach; or -(iii) either Party may terminate this Agreement, effective immediately upon written Notice to the other Party, if the other Party: (A) becomes insolvent or is generally unable to pay, or fails to pay, its debts as they become due; (B) files or has filed against it, a petition for voluntary or involuntary bankruptcy or otherwise becomes subject, voluntarily or involuntarily, to any proceeding under any domestic or foreign bankruptcy or insolvency law; (C) makes or seeks to make a general assignment for the benefit of its creditors; or (D) applies for or has appointed a receiver, trustee, custodian, or similar agent appointed by order of any court of competent jurisdiction to take charge of or sell any material portion of its property or business. +b. **Termination.** In addition to any other express termination right set forth in this Agreement: +(i) WunderGraph may terminate this Agreement, effective on written Notice to Customer, if Customer: (A) fails to pay any amount when due hereunder, and such failure continues more than ten (10) days after WunderGraph’s written Notice thereof; or (B) breaches any of its obligations under Section 2(c); +(ii) either Party may terminate this Agreement, effective on written Notice to the other Party, if the other Party materially breaches this Agreement, and such breach: (A) is incapable of cure; or (B) being capable of cure, remains uncured thirty (30) days after the non-breaching Party provides the breaching Party with written Notice of such breach; or +(iii) either Party may terminate this Agreement, effective immediately upon written Notice to the other Party, if the other Party: (A) becomes insolvent or is generally unable to pay, or fails to pay, its debts as they become due; (B) files or has filed against it, a petition for voluntary or involuntary bankruptcy or otherwise becomes subject, voluntarily or involuntarily, to any proceeding under any domestic or foreign bankruptcy or insolvency law; (C) makes or seeks to make a general assignment for the benefit of its creditors; or (D) applies for or has appointed a receiver, trustee, custodian, or similar agent appointed by order of any court of competent jurisdiction to take charge of or sell any material portion of its property or business. - c. **Effect of Termination.** Upon termination of this Agreement, Customer’s access to the Service and all licenses granted hereunder will also terminate. Subject to Section 5(c), no termination will affect Customer's obligation to pay all Fees that may have become due before such termination, or entitle Customer to any refund. +c. **Effect of Termination.** Upon termination of this Agreement, Customer’s access to the Service and all licenses granted hereunder will also terminate. Subject to Section 5(c), no termination will affect Customer's obligation to pay all Fees that may have become due before such termination, or entitle Customer to any refund. - d. **Survival.** This Section 11 and Sections 1 (Definitions), 2(c) (Use Restrictions), 5 (Fees and Payment; Service Upgrades), 6 (Intellectual Property Ownership), 7 (Warranty Disclaimer; Beta Disclaimer), 8 (Indemnification), 9 (Assumption of Risk), 10 (Limitations of Liability), and 12 (Miscellaneous) shall survive the termination of this Agreement. +d. **Survival.** This Section 11 and Sections 1 (Definitions), 2(c) (Use Restrictions), 5 (Fees and Payment; Service Upgrades), 6 (Intellectual Property Ownership), 7 (Warranty Disclaimer; Beta Disclaimer), 8 (Indemnification), 9 (Assumption of Risk), 10 (Limitations of Liability), and 12 (Miscellaneous) shall survive the termination of this Agreement. -### 12. Miscellaneous. +### 12. Miscellaneous. - a. **Entire Agreement.** This Agreement, together with any other documents incorporated herein by reference and all attachments, exhibits or appendices, constitutes the sole and entire agreement of the Parties with respect to the subject matter of this Agreement and supersedes all prior and contemporaneous understandings, agreements, and representations and warranties, both written and oral, with respect to such subject matter. +a. **Entire Agreement.** This Agreement, together with any other documents incorporated herein by reference and all attachments, exhibits or appendices, constitutes the sole and entire agreement of the Parties with respect to the subject matter of this Agreement and supersedes all prior and contemporaneous understandings, agreements, and representations and warranties, both written and oral, with respect to such subject matter. - b. **Notices.** Except for Service upgrade requests under Section 5(c) which may be made by electronic mail, all notices, requests, consents, claims, demands, waivers, and other communications hereunder (each, a **“Notice”**) to WunderGraph must be in writing and addressed to WunderGraph at the following address: Wundergraph, Inc., 66 W Flagler St Ste. 900, Miami, FL 33130 U.S.A. All Notices to WunderGraph must be delivered by personal delivery, nationally recognized overnight courier (with all fees pre-paid), or certified or registered mail (in each case, return receipt requested, postage pre-paid). Customer consents to Notice by nationally recognized overnight courier, certified or registered mail, or by electronic mail. +b. **Notices.** Except for Service upgrade requests under Section 5(c) which may be made by electronic mail, all notices, requests, consents, claims, demands, waivers, and other communications hereunder (each, a **“Notice”**) to WunderGraph must be in writing and addressed to WunderGraph at the following address: Wundergraph, Inc., 66 W Flagler St Ste. 900, Miami, FL 33130 U.S.A. All Notices to WunderGraph must be delivered by personal delivery, nationally recognized overnight courier (with all fees pre-paid), or certified or registered mail (in each case, return receipt requested, postage pre-paid). Customer consents to Notice by nationally recognized overnight courier, certified or registered mail, or by electronic mail. - c. **Force Majeure.** In no event shall WunderGraph be liable to Customer, or be deemed to have breached this Agreement, for any failure or delay in performing its obligations under this Agreement, if and to the extent such failure or delay is caused by any circumstances beyond WunderGraph’s reasonable control, including but not limited to: (i) acts of God; (ii) flood, fire, earthquake, epidemics, pandemics or explosion; (iii) war, invasion, hostilities (whether war is declared or not), terrorist threats or acts, riot or other civil unrest; (iv) government order, law, or actions; (v) embargoes or blockades in effect on or after the date of this Agreement; (vi) national or regional emergency; and (vii) power or telecommunications outage or shortage of adequate power, communications or transportation facilities, whether belonging to WunderGraph or any third party. +c. **Force Majeure.** In no event shall WunderGraph be liable to Customer, or be deemed to have breached this Agreement, for any failure or delay in performing its obligations under this Agreement, if and to the extent such failure or delay is caused by any circumstances beyond WunderGraph’s reasonable control, including but not limited to: (i) acts of God; (ii) flood, fire, earthquake, epidemics, pandemics or explosion; (iii) war, invasion, hostilities (whether war is declared or not), terrorist threats or acts, riot or other civil unrest; (iv) government order, law, or actions; (v) embargoes or blockades in effect on or after the date of this Agreement; (vi) national or regional emergency; and (vii) power or telecommunications outage or shortage of adequate power, communications or transportation facilities, whether belonging to WunderGraph or any third party. - d. **Amendment and Modification; Waiver.** No amendment to or modification of this Agreement is effective unless it is in writing and signed by an authorized representative of each Party. No waiver by WunderGraph of any of the provisions hereof will be effective unless explicitly set forth in writing and signed by an authorized representative of WunderGraph. +d. **Amendment and Modification; Waiver.** No amendment to or modification of this Agreement is effective unless it is in writing and signed by an authorized representative of each Party. No waiver by WunderGraph of any of the provisions hereof will be effective unless explicitly set forth in writing and signed by an authorized representative of WunderGraph. - e. **Severability.** If any provision of this Agreement is invalid, illegal, or unenforceable in any jurisdiction, such invalidity, illegality, or unenforceability will not affect any other term or provision of this Agreement or invalidate or render unenforceable such term or provision in any other jurisdiction. +e. **Severability.** If any provision of this Agreement is invalid, illegal, or unenforceable in any jurisdiction, such invalidity, illegality, or unenforceability will not affect any other term or provision of this Agreement or invalidate or render unenforceable such term or provision in any other jurisdiction. - f. **Governing Law; Jurisdiction.** - i. This Agreement and any matters arising out of or relating to this Agreement are governed by and construed in accordance with the internal laws of the State of Florida, United States of America, without giving effect to any choice or conflict of law provision or rule that would require or permit the application of the laws of any jurisdiction other than those of the State of Florida. The United Nations Convention on Contracts for the International Sale of Goods shall not apply to this Agreement. - ii. Any suit, action or proceeding arising out of or relating to this Agreement, the purchase and sale of the Service, and the relationship of the Parties shall be instituted in a state or federal court located in the County of Miami-Dade, State of Florida, and Customer irrevocably consents and waives all objections to the exclusive jurisdiction and venue of such courts in any such suit, action or proceeding. In the event WunderGraph brings any proceeding to enforce its rights hereunder including for any breach of any of the provisions of this Agreement, WunderGraph will be entitled in such proceeding to recover its reasonable attorneys' fees together with the costs and expenses of such proceeding. **CUSTOMER AND WUNDERGRAPH HEREBY AGREE TO WAIVE ANY RIGHT TO TRIAL BY JURY WITH RESPECT TO ANY DISPUTE CONCERNING THIS AGREEMENT AND/OR THE SERVICE.** - iii. Any dispute or other action arising out of or relating to this Agreement must be brought by Customer within one (1) year of the date the event giving rise to the cause of action occurred or it shall be forever barred. +f. **Governing Law; Jurisdiction.** +i. This Agreement and any matters arising out of or relating to this Agreement are governed by and construed in accordance with the internal laws of the State of Florida, United States of America, without giving effect to any choice or conflict of law provision or rule that would require or permit the application of the laws of any jurisdiction other than those of the State of Florida. The United Nations Convention on Contracts for the International Sale of Goods shall not apply to this Agreement. +ii. Any suit, action or proceeding arising out of or relating to this Agreement, the purchase and sale of the Service, and the relationship of the Parties shall be instituted in a state or federal court located in the County of Miami-Dade, State of Florida, and Customer irrevocably consents and waives all objections to the exclusive jurisdiction and venue of such courts in any such suit, action or proceeding. In the event WunderGraph brings any proceeding to enforce its rights hereunder including for any breach of any of the provisions of this Agreement, WunderGraph will be entitled in such proceeding to recover its reasonable attorneys' fees together with the costs and expenses of such proceeding. **CUSTOMER AND WUNDERGRAPH HEREBY AGREE TO WAIVE ANY RIGHT TO TRIAL BY JURY WITH RESPECT TO ANY DISPUTE CONCERNING THIS AGREEMENT AND/OR THE SERVICE.** +iii. Any dispute or other action arising out of or relating to this Agreement must be brought by Customer within one (1) year of the date the event giving rise to the cause of action occurred or it shall be forever barred. - g. **Assignment.** WunderGraph may assign its rights or obligations hereunder without restriction. Customer may not assign or transfer any of its rights or delegate any of its obligations hereunder, in each case whether voluntarily, involuntarily, by operation of law or otherwise, without the prior written consent of WunderGraph. This Agreement is binding upon and inures to the benefit of the Parties hereto and their respective permitted successors and assigns. +g. **Assignment.** WunderGraph may assign its rights or obligations hereunder without restriction. Customer may not assign or transfer any of its rights or delegate any of its obligations hereunder, in each case whether voluntarily, involuntarily, by operation of law or otherwise, without the prior written consent of WunderGraph. This Agreement is binding upon and inures to the benefit of the Parties hereto and their respective permitted successors and assigns. - h. **Export Regulation.** The Software and/or Service may be subject to U.S. export control laws, including the Export Control Reform Act and its associated regulations. Subject to the terms of this Agreement, Customer shall comply with all applicable federal laws, regulations, and rules, and complete all required undertakings (including obtaining any necessary export license or other governmental approval), prior to exporting, re-exporting, releasing, or otherwise making the Software and/or Service available outside the U.S. +h. **Export Regulation.** The Software and/or Service may be subject to U.S. export control laws, including the Export Control Reform Act and its associated regulations. Subject to the terms of this Agreement, Customer shall comply with all applicable federal laws, regulations, and rules, and complete all required undertakings (including obtaining any necessary export license or other governmental approval), prior to exporting, re-exporting, releasing, or otherwise making the Software and/or Service available outside the U.S. - i. **Equitable Relief.** Notwithstanding Section 12(f)(ii) herein, Customer acknowledges and agrees that a breach or threatened breach by Customer of any of its obligations under Section 2(c) would cause WunderGraph irreparable harm for which monetary damages would not be an adequate remedy and agrees that, in the event of such breach or threatened breach, WunderGraph will be entitled to equitable relief from any court with jurisdiction, without any requirement to post a bond or other security, or to prove actual damages. Such remedies are not exclusive and are in addition to all other remedies that may be available at law, in equity, or otherwise. +i. **Equitable Relief.** Notwithstanding Section 12(f)(ii) herein, Customer acknowledges and agrees that a breach or threatened breach by Customer of any of its obligations under Section 2(c) would cause WunderGraph irreparable harm for which monetary damages would not be an adequate remedy and agrees that, in the event of such breach or threatened breach, WunderGraph will be entitled to equitable relief from any court with jurisdiction, without any requirement to post a bond or other security, or to prove actual damages. Such remedies are not exclusive and are in addition to all other remedies that may be available at law, in equity, or otherwise. diff --git a/studio/src/styles/login.css b/studio/src/styles/login.css index 68046434d7..43cbffa567 100644 --- a/studio/src/styles/login.css +++ b/studio/src/styles/login.css @@ -1,7 +1,13 @@ .circle-glow { - box-shadow: inset 0 0 50px #fff, inset 20px 0 80px #f0f, - inset -20px 0 80px #0ff, inset 20px 0 300px #f0f, inset -20px 0 300px #0ff, - 0 0 50px #fff, -10px 0 80px #f0f, 10px 0 80px #0ff; + box-shadow: + inset 0 0 50px #fff, + inset 20px 0 80px #f0f, + inset -20px 0 80px #0ff, + inset 20px 0 300px #f0f, + inset -20px 0 300px #0ff, + 0 0 50px #fff, + -10px 0 80px #f0f, + 10px 0 80px #0ff; } .stars { @@ -12,33 +18,90 @@ width: 1px; background-color: #fff; border-radius: 50%; - box-shadow: 24vw 9vh 1px 0px #fff, 12vw -24vh 0px 1px #fff, - -45vw -22vh 0px 0px #fff, -37vw -40vh 0px 1px #fff, 29vw 19vh 0px 1px #fff, - 4vw -8vh 0px 1px #fff, -5vw 21vh 1px 1px #fff, -27vw 26vh 1px 1px #fff, - -47vw -3vh 1px 1px #fff, -28vw -30vh 0px 1px #fff, -43vw -27vh 0px 1px #fff, - 4vw 22vh 1px 1px #fff, 36vw 23vh 0px 0px #fff, -21vw 24vh 1px 1px #fff, - -16vw 2vh 1px 0px #fff, -16vw -6vh 0px 0px #fff, 5vw 26vh 0px 0px #fff, - -34vw 41vh 0px 0px #fff, 1vw 42vh 1px 1px #fff, 11vw -13vh 1px 1px #fff, - 48vw -8vh 1px 0px #fff, 22vw -15vh 0px 0px #fff, 45vw 49vh 0px 0px #fff, - 43vw -27vh 1px 1px #fff, 20vw -2vh 0px 0px #fff, 8vw 22vh 0px 1px #fff, - 39vw 48vh 1px 1px #fff, -21vw -11vh 0px 1px #fff, -40vw 45vh 0px 1px #fff, - 11vw -30vh 1px 0px #fff, 26vw 30vh 1px 0px #fff, 45vw -29vh 0px 1px #fff, - -2vw 18vh 0px 0px #fff, -29vw -45vh 1px 0px #fff, -7vw -27vh 1px 1px #fff, - 42vw 24vh 0px 0px #fff, 45vw -48vh 1px 0px #fff, -36vw -18vh 0px 0px #fff, - -44vw 13vh 0px 1px #fff, 36vw 16vh 0px 1px #fff, 40vw 24vh 0px 0px #fff, - 18vw 11vh 0px 0px #fff, -15vw -23vh 1px 0px #fff, -24vw 48vh 0px 1px #fff, - 27vw -45vh 1px 0px #fff, -2vw -24vh 0px 1px #fff, -15vw -28vh 0px 0px #fff, - -43vw 13vh 1px 0px #fff, 7vw 27vh 1px 0px #fff, 47vw 5vh 0px 0px #fff, - -45vw 15vh 1px 1px #fff, -5vw -28vh 0px 1px #fff, 38vw 25vh 1px 1px #fff, - -39vw -1vh 1px 0px #fff, 5vw 0vh 1px 0px #fff, 49vw 13vh 0px 0px #fff, - 48vw 10vh 0px 1px #fff, 19vw -28vh 0px 0px #fff, 4vw 7vh 0px 0px #fff, - 21vw 21vh 1px 1px #fff, -15vw -15vh 0px 1px #fff, -6vw -42vh 1px 0px #fff, - -15vw 48vh 1px 1px #fff, -23vw 25vh 1px 1px #fff, -48vw 25vh 0px 1px #fff, - -31vw -19vh 0px 1px #fff, 4vw 37vh 1px 1px #fff, -43vw 28vh 0px 0px #fff, - 3vw -25vh 0px 1px #fff, -39vw 14vh 0px 1px #fff, -40vw 31vh 0px 1px #fff, - 35vw -36vh 1px 1px #fff, 16vw 49vh 0px 0px #fff, 6vw 39vh 0px 0px #fff, - 3vw -35vh 0px 1px #fff, -44vw -2vh 1px 0px #fff, -6vw 21vh 1px 0px #fff, - 48vw 9vh 1px 1px #fff, -43vw 30vh 1px 1px #fff, 29vw -12vh 1px 1px #fff, - -48vw 13vh 1px 0px #fff, -42vw 32vh 1px 1px #fff, 34vw 15vh 1px 1px #fff, - 29vw -37vh 1px 1px #fff, 28vw 2vh 0px 0px #fff; -} \ No newline at end of file + box-shadow: + 24vw 9vh 1px 0px #fff, + 12vw -24vh 0px 1px #fff, + -45vw -22vh 0px 0px #fff, + -37vw -40vh 0px 1px #fff, + 29vw 19vh 0px 1px #fff, + 4vw -8vh 0px 1px #fff, + -5vw 21vh 1px 1px #fff, + -27vw 26vh 1px 1px #fff, + -47vw -3vh 1px 1px #fff, + -28vw -30vh 0px 1px #fff, + -43vw -27vh 0px 1px #fff, + 4vw 22vh 1px 1px #fff, + 36vw 23vh 0px 0px #fff, + -21vw 24vh 1px 1px #fff, + -16vw 2vh 1px 0px #fff, + -16vw -6vh 0px 0px #fff, + 5vw 26vh 0px 0px #fff, + -34vw 41vh 0px 0px #fff, + 1vw 42vh 1px 1px #fff, + 11vw -13vh 1px 1px #fff, + 48vw -8vh 1px 0px #fff, + 22vw -15vh 0px 0px #fff, + 45vw 49vh 0px 0px #fff, + 43vw -27vh 1px 1px #fff, + 20vw -2vh 0px 0px #fff, + 8vw 22vh 0px 1px #fff, + 39vw 48vh 1px 1px #fff, + -21vw -11vh 0px 1px #fff, + -40vw 45vh 0px 1px #fff, + 11vw -30vh 1px 0px #fff, + 26vw 30vh 1px 0px #fff, + 45vw -29vh 0px 1px #fff, + -2vw 18vh 0px 0px #fff, + -29vw -45vh 1px 0px #fff, + -7vw -27vh 1px 1px #fff, + 42vw 24vh 0px 0px #fff, + 45vw -48vh 1px 0px #fff, + -36vw -18vh 0px 0px #fff, + -44vw 13vh 0px 1px #fff, + 36vw 16vh 0px 1px #fff, + 40vw 24vh 0px 0px #fff, + 18vw 11vh 0px 0px #fff, + -15vw -23vh 1px 0px #fff, + -24vw 48vh 0px 1px #fff, + 27vw -45vh 1px 0px #fff, + -2vw -24vh 0px 1px #fff, + -15vw -28vh 0px 0px #fff, + -43vw 13vh 1px 0px #fff, + 7vw 27vh 1px 0px #fff, + 47vw 5vh 0px 0px #fff, + -45vw 15vh 1px 1px #fff, + -5vw -28vh 0px 1px #fff, + 38vw 25vh 1px 1px #fff, + -39vw -1vh 1px 0px #fff, + 5vw 0vh 1px 0px #fff, + 49vw 13vh 0px 0px #fff, + 48vw 10vh 0px 1px #fff, + 19vw -28vh 0px 0px #fff, + 4vw 7vh 0px 0px #fff, + 21vw 21vh 1px 1px #fff, + -15vw -15vh 0px 1px #fff, + -6vw -42vh 1px 0px #fff, + -15vw 48vh 1px 1px #fff, + -23vw 25vh 1px 1px #fff, + -48vw 25vh 0px 1px #fff, + -31vw -19vh 0px 1px #fff, + 4vw 37vh 1px 1px #fff, + -43vw 28vh 0px 0px #fff, + 3vw -25vh 0px 1px #fff, + -39vw 14vh 0px 1px #fff, + -40vw 31vh 0px 1px #fff, + 35vw -36vh 1px 1px #fff, + 16vw 49vh 0px 0px #fff, + 6vw 39vh 0px 0px #fff, + 3vw -35vh 0px 1px #fff, + -44vw -2vh 1px 0px #fff, + -6vw 21vh 1px 0px #fff, + 48vw 9vh 1px 1px #fff, + -43vw 30vh 1px 1px #fff, + 29vw -12vh 1px 1px #fff, + -48vw 13vh 1px 0px #fff, + -42vw 32vh 1px 1px #fff, + 34vw 15vh 1px 1px #fff, + 29vw -37vh 1px 1px #fff, + 28vw 2vh 0px 0px #fff; +} From 38e637b119e7c50b9c20c2b6f1e6d622cd1ee5db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Syn=C3=A1=C4=8Dek?= Date: Fri, 6 Mar 2026 19:44:03 +0100 Subject: [PATCH 2/3] fix: formatting in studio (pt3) --- studio/src/components/analytics/charts.tsx | 4 +- .../src/components/analytics/delta-badge.tsx | 2 +- studio/src/components/analytics/filters.tsx | 4 +- studio/src/components/analytics/toolbar.tsx | 4 +- .../components/analytics/trace-details.tsx | 118 ++++++--- studio/src/components/app-provider.tsx | 10 +- studio/src/components/audit-log-table.tsx | 24 +- .../src/components/auth/auth-components.tsx | 41 ++- .../components/cache/cache-details-sheet.tsx | 8 +- .../components/cache/cache-warmer-config.tsx | 12 +- .../src/components/cache/operations-table.tsx | 12 +- studio/src/components/changelog/changelog.tsx | 4 +- .../check-extensions-config.tsx | 245 +++++++++++------- .../src/components/checks/changes-table.tsx | 73 +++--- .../src/components/checks/checks-config.tsx | 49 ++-- .../components/checks/checks-filter-menu.tsx | 10 +- .../checks/graph-pruning-issues-table.tsx | 9 +- .../components/checks/lint-issues-table.tsx | 8 +- studio/src/components/checks/operations.tsx | 32 +-- studio/src/components/checks/override.tsx | 42 +-- .../checks/selected-checks-filters.tsx | 28 +- .../checks/subgraph-check-extension.tsx | 26 +- studio/src/components/compose-status-bulb.tsx | 4 +- studio/src/components/create-graph.tsx | 21 +- .../dashboard/graph-command-group.tsx | 108 ++++---- .../components/dashboard/graph-selector.tsx | 31 ++- .../dashboard/namespace-selector.tsx | 62 +++-- .../dashboard/workspace-command-wrapper.tsx | 40 +-- .../dashboard/workspace-provider.tsx | 94 ++++--- .../dashboard/workspace-selector.tsx | 51 ++-- studio/src/components/empty-state.tsx | 2 +- .../src/components/feature-flag-details.tsx | 6 +- studio/src/components/feature-flags-table.tsx | 6 +- .../src/components/federatedgraphs-cards.tsx | 16 +- studio/src/components/group-select.tsx | 52 ++-- .../analytics/active-campaign-script.tsx | 7 +- .../layout/analytics/gtm-script.tsx | 18 +- .../components/layout/dashboard-layout.tsx | 66 +++-- studio/src/components/layout/sidenav.tsx | 15 +- .../src/components/layout/subgraph-layout.tsx | 12 +- studio/src/components/layout/title-layout.tsx | 7 +- .../lint-policy/graph-pruning-config.tsx | 19 +- .../member-groups/create-group-dialog.tsx | 53 ++-- .../member-groups/delete-group-dialog.tsx | 79 ++++-- .../member-groups/group-members-sheet.tsx | 84 +++--- .../member-groups/group-resource-selector.tsx | 128 ++++++--- .../member-groups/group-roles-command.tsx | 79 ++++-- .../components/member-groups/group-row.tsx | 37 ++- .../member-groups/group-rule-builder.tsx | 78 +++--- .../components/member-groups/group-sheet.tsx | 118 ++++++--- .../members/update-member-group-dialog.tsx | 27 +- studio/src/components/multi-group-select.tsx | 88 ++++--- .../components/notifications/components.tsx | 8 +- .../operations/client-usage-table.tsx | 2 +- .../components/overview/OverviewToolbar.tsx | 4 +- .../playground/share-playground-modal.tsx | 71 +++-- ...opover-content-with-scrollable-content.tsx | 10 +- .../components/proposal/proposal-config.tsx | 4 +- .../src/components/reactflow-graph-node.tsx | 2 +- studio/src/components/safe-markdown.tsx | 13 +- .../components/schema/empty-schema-state.tsx | 4 +- studio/src/components/schema/toolbar.tsx | 4 +- .../settings/delete-organization.tsx | 56 ++-- .../settings/restore-organization.tsx | 30 ++- studio/src/components/subgraphs-table.tsx | 4 +- studio/src/components/ui/accordion.tsx | 28 +- studio/src/components/ui/alert-dialog.tsx | 58 ++--- studio/src/components/ui/calendar.tsx | 22 +- studio/src/components/ui/checkbox.tsx | 18 +- studio/src/components/ui/dialog2.tsx | 4 +- studio/src/components/ui/form.tsx | 101 ++++---- studio/src/components/ui/label.tsx | 18 +- studio/src/components/ui/loader.tsx | 2 +- studio/src/components/ui/popover.tsx | 18 +- studio/src/components/ui/resizable.tsx | 22 +- studio/src/components/ui/scroll-area.tsx | 26 +- studio/src/components/ui/separator.tsx | 18 +- studio/src/components/ui/skeleton.tsx | 8 +- .../src/components/ui/tag-input/tag-input.tsx | 107 ++++---- studio/src/components/ui/textarea.tsx | 14 +- studio/src/components/ui/toast.tsx | 12 +- studio/src/components/ui/toggle.tsx | 18 +- studio/src/components/ui/tooltip.tsx | 2 +- studio/src/components/user-menu.tsx | 24 +- .../components/webhook-delivery-details.tsx | 51 ++-- 85 files changed, 1796 insertions(+), 1160 deletions(-) diff --git a/studio/src/components/analytics/charts.tsx b/studio/src/components/analytics/charts.tsx index 5ba2624a22..1c02fa79c8 100644 --- a/studio/src/components/analytics/charts.tsx +++ b/studio/src/components/analytics/charts.tsx @@ -18,8 +18,8 @@ const labelFormatter = (label: number, utc?: boolean) => { return utc ? new Date(label).toUTCString() : label - ? formatDateTime(label) - : label; + ? formatDateTime(label) + : label; }; export const valueFormatter = (tick: number) => diff --git a/studio/src/components/analytics/delta-badge.tsx b/studio/src/components/analytics/delta-badge.tsx index bfafa0ccd7..52e4d862be 100644 --- a/studio/src/components/analytics/delta-badge.tsx +++ b/studio/src/components/analytics/delta-badge.tsx @@ -23,7 +23,7 @@ const deltaBadgeVariants = cva( defaultVariants: { type: "neutral", }, - } + }, ); export interface DeltaBadgeProps diff --git a/studio/src/components/analytics/filters.tsx b/studio/src/components/analytics/filters.tsx index eae4a47470..7b096a5a71 100644 --- a/studio/src/components/analytics/filters.tsx +++ b/studio/src/components/analytics/filters.tsx @@ -29,7 +29,9 @@ export const AnalyticsFilters: React.FC = (props) => { return ( <> - {filters.length > 0 && } + {filters.length > 0 && ( + + )} ); }; diff --git a/studio/src/components/analytics/toolbar.tsx b/studio/src/components/analytics/toolbar.tsx index e88dcea443..47fa96f00d 100644 --- a/studio/src/components/analytics/toolbar.tsx +++ b/studio/src/components/analytics/toolbar.tsx @@ -25,7 +25,9 @@ export const AnalyticsToolbar: React.FC<{ children?: React.ReactNode; }> = (props) => { const router = useRouter(); - const { namespace: { name: namespace } } = useWorkspace(); + const { + namespace: { name: namespace }, + } = useWorkspace(); const organizationSlug = useCurrentOrganization()?.slug; const query: ParsedUrlQueryInput = { diff --git a/studio/src/components/analytics/trace-details.tsx b/studio/src/components/analytics/trace-details.tsx index ee6f44e154..07dba28b60 100644 --- a/studio/src/components/analytics/trace-details.tsx +++ b/studio/src/components/analytics/trace-details.tsx @@ -1,36 +1,40 @@ -import Trace from '@/components/analytics/trace'; -import { CodeViewer } from '@/components/code-viewer'; -import { EmptyState } from '@/components/empty-state'; -import { Button } from '@/components/ui/button'; -import { Loader } from '@/components/ui/loader'; -import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'; -import { docsBaseURL } from '@/lib/constants'; -import { extractVariablesFromGraphQL } from '@/lib/schema-helpers'; -import { ExclamationTriangleIcon } from '@heroicons/react/24/outline'; -import { PlayIcon } from '@radix-ui/react-icons'; -import { useQuery } from '@connectrpc/connect-query'; -import { EnumStatusCode } from '@wundergraph/cosmo-connect/dist/common/common_pb'; +import Trace from "@/components/analytics/trace"; +import { CodeViewer } from "@/components/code-viewer"; +import { EmptyState } from "@/components/empty-state"; +import { Button } from "@/components/ui/button"; +import { Loader } from "@/components/ui/loader"; +import { + Tooltip, + TooltipContent, + TooltipTrigger, +} from "@/components/ui/tooltip"; +import { docsBaseURL } from "@/lib/constants"; +import { extractVariablesFromGraphQL } from "@/lib/schema-helpers"; +import { ExclamationTriangleIcon } from "@heroicons/react/24/outline"; +import { PlayIcon } from "@radix-ui/react-icons"; +import { useQuery } from "@connectrpc/connect-query"; +import { EnumStatusCode } from "@wundergraph/cosmo-connect/dist/common/common_pb"; import { getOperationContent, getTrace, -} from '@wundergraph/cosmo-connect/dist/platform/v1/platform-PlatformService_connectquery'; -import { GraphQLSchema } from 'graphql'; -import Link from 'next/link'; -import { useRouter } from 'next/router'; -import parserBabel from 'prettier/plugins/babel'; -import * as prettierPluginEstree from 'prettier/plugins/estree'; -import graphQLPlugin from 'prettier/plugins/graphql'; -import * as prettier from 'prettier/standalone'; -import { useContext, useEffect, useState } from 'react'; -import { GraphContext } from '../layout/graph-layout'; +} from "@wundergraph/cosmo-connect/dist/platform/v1/platform-PlatformService_connectquery"; +import { GraphQLSchema } from "graphql"; +import Link from "next/link"; +import { useRouter } from "next/router"; +import parserBabel from "prettier/plugins/babel"; +import * as prettierPluginEstree from "prettier/plugins/estree"; +import graphQLPlugin from "prettier/plugins/graphql"; +import * as prettier from "prettier/standalone"; +import { useContext, useEffect, useState } from "react"; +import { GraphContext } from "../layout/graph-layout"; export const TraceDetails = ({ ast }: { ast: GraphQLSchema | null }) => { const { query } = useRouter(); const organizationSlug = query.organizationSlug as string; const namespace = query.namespace as string; const slug = query.slug as string; - const [content, setContent] = useState(''); - const [variables, setVariables] = useState(''); + const [content, setContent] = useState(""); + const [variables, setVariables] = useState(""); const [isTruncated, setTruncated] = useState(false); const graphContext = useContext(GraphContext); @@ -56,7 +60,9 @@ export const TraceDetails = ({ ast }: { ast: GraphQLSchema | null }) => { ); // Find the operation hash from any span that provides it - const operationHash = traceData?.spans.find((span) => !!span.attributes?.operationHash)?.attributes.operationHash; + const operationHash = traceData?.spans.find( + (span) => !!span.attributes?.operationHash, + )?.attributes.operationHash; const { data: operationContentData, @@ -77,8 +83,8 @@ export const TraceDetails = ({ ast }: { ast: GraphQLSchema | null }) => { useEffect(() => { if (!traceData || !operationContentData) { - setVariables(''); - setContent(''); + setVariables(""); + setContent(""); return; } @@ -86,7 +92,7 @@ export const TraceDetails = ({ ast }: { ast: GraphQLSchema | null }) => { const checkValidity = async (content: string, variables: string) => { try { await prettier.format(content, { - parser: 'graphql', + parser: "graphql", plugins: [graphQLPlugin], }); } catch { @@ -95,7 +101,7 @@ export const TraceDetails = ({ ast }: { ast: GraphQLSchema | null }) => { try { await prettier.format(variables, { - parser: 'json', + parser: "json", plugins: [parserBabel, prettierPluginEstree], }); } catch { @@ -108,7 +114,9 @@ export const TraceDetails = ({ ast }: { ast: GraphQLSchema | null }) => { const content = operationContentData.operationContent; setContent(content); - const routerSpan = traceData.spans.find((span) => !!span.attributes?.operationVariables); + const routerSpan = traceData.spans.find( + (span) => !!span.attributes?.operationVariables, + ); if (routerSpan) { const variables = routerSpan.attributes?.operationVariables; @@ -131,7 +139,11 @@ export const TraceDetails = ({ ast }: { ast: GraphQLSchema | null }) => { className="order-2 h-72 border lg:order-last" icon={} title="Could not retrieve request information" - description={traceData?.response?.details || traceError?.message || 'Please try again'} + description={ + traceData?.response?.details || + traceError?.message || + "Please try again" + } actions={} /> ); @@ -146,27 +158,34 @@ export const TraceDetails = ({ ast }: { ast: GraphQLSchema | null }) => { ) : ( <> - {operationContentError || operationContentData?.response?.code !== EnumStatusCode.OK ? ( + {operationContentError || + operationContentData?.response?.code !== EnumStatusCode.OK ? ( } title="Could not retrieve operation contents" description={ - operationContentData?.response?.details || operationContentError?.message || 'Please try again' + operationContentData?.response?.details || + operationContentError?.message || + "Please try again" + } + actions={ + } - actions={} /> ) : ( <>
Operation and Variables
- Content is truncated to 3KB. To view the GraphQL variables of the operation, please enable variable - export in the router.{' '} + Content is truncated to 3KB. To view the GraphQL variables of + the operation, please enable variable export in the router.{" "} Learn more. @@ -190,12 +209,20 @@ export const TraceDetails = ({ ast }: { ast: GraphQLSchema | null }) => { {isTruncated ? ( - - Run in playground with truncated / invalid content + + Run in playground with truncated / invalid content + ) : ( @@ -211,9 +240,12 @@ export const TraceDetails = ({ ast }: { ast: GraphQLSchema | null }) => {
diff --git a/studio/src/components/checks/selected-checks-filters.tsx b/studio/src/components/checks/selected-checks-filters.tsx index f80735bf6b..d517e8e170 100644 --- a/studio/src/components/checks/selected-checks-filters.tsx +++ b/studio/src/components/checks/selected-checks-filters.tsx @@ -6,12 +6,10 @@ import { Cross2Icon } from "@radix-ui/react-icons"; import { DataTableFacetedFilter } from "@/components/analytics/data-table-faceted-filter"; export function SelectedChecksFilters({ - selectedSubgraphs -} : - { - selectedSubgraphs: string[]; - } -) { + selectedSubgraphs, +}: { + selectedSubgraphs: string[]; +}) { const applyParams = useApplyParams(); const { subgraphs = [] } = useContext(GraphContext) ?? {}; @@ -30,15 +28,17 @@ export function SelectedChecksFilters({ subgraphs.find((sg) => sg.id === id)!) - .filter(Boolean) - .map((sg) => JSON.stringify({ label: sg.name, value: sg.id })) - } + selectedOptions={selectedSubgraphs + .map((id) => subgraphs.find((sg) => sg.id === id)!) + .filter(Boolean) + .map((sg) => JSON.stringify({ label: sg.name, value: sg.id }))} onSelect={(value) => { applyParams({ - subgraphs: value?.map(JSON.parse).map((sg: { value: string; }) => sg.value).join(',') ?? null, + subgraphs: + value + ?.map(JSON.parse) + .map((sg: { value: string }) => sg.value) + .join(",") ?? null, }); }} options={subgraphOptions} @@ -54,4 +54,4 @@ export function SelectedChecksFilters({ ); -} \ No newline at end of file +} diff --git a/studio/src/components/checks/subgraph-check-extension.tsx b/studio/src/components/checks/subgraph-check-extension.tsx index 23110f2ac7..5d7bf0da62 100644 --- a/studio/src/components/checks/subgraph-check-extension.tsx +++ b/studio/src/components/checks/subgraph-check-extension.tsx @@ -15,16 +15,22 @@ export interface SubgraphCheckExtensionProps { errorMessage: string | undefined; } -export function SubgraphCheckExtension({ enabled, deliveryId, errorMessage }: SubgraphCheckExtensionProps) { +export function SubgraphCheckExtension({ + enabled, + deliveryId, + errorMessage, +}: SubgraphCheckExtensionProps) { const router = useRouter(); const user = useUser(); const graphContext = useContext(GraphContext); - const [selectedDeliveryId, setSelectedDeliveryId] = useState(undefined); + const [selectedDeliveryId, setSelectedDeliveryId] = useState< + string | undefined + >(undefined); if (!deliveryId) { return ( } + icon={} title="Subgraph Check Extension Skipped" description={ !enabled @@ -58,7 +64,7 @@ export function SubgraphCheckExtension({ enabled, deliveryId, errorMessage }: Su

)} + actions={ + deliveryId && ( + + ) + } /> )} @@ -87,4 +99,4 @@ export function SubgraphCheckExtension({ enabled, deliveryId, errorMessage }: Su /> ); -} \ No newline at end of file +} diff --git a/studio/src/components/compose-status-bulb.tsx b/studio/src/components/compose-status-bulb.tsx index ecd877503b..a8f8e78d2f 100644 --- a/studio/src/components/compose-status-bulb.tsx +++ b/studio/src/components/compose-status-bulb.tsx @@ -14,8 +14,8 @@ export const ComposeStatusBulb = ({ validGraph ? "bg-success" : emptyGraph - ? "bg-warning" - : "bg-destructive", + ? "bg-warning" + : "bg-destructive", )} /> ); diff --git a/studio/src/components/create-graph.tsx b/studio/src/components/create-graph.tsx index cabe325db0..a0f9b0e135 100644 --- a/studio/src/components/create-graph.tsx +++ b/studio/src/components/create-graph.tsx @@ -27,7 +27,7 @@ import { EnumStatusCode } from "@wundergraph/cosmo-connect/dist/common/common_pb import { createFederatedGraph, createMonograph, - getWorkspace + getWorkspace, } from "@wundergraph/cosmo-connect/dist/platform/v1/platform-PlatformService_connectquery"; import Link from "next/link"; import { useRouter } from "next/router"; @@ -49,7 +49,9 @@ export const CreateGraphForm = ({ }) => { const router = useRouter(); const user = useUser(); - const { namespace: { name: namespace } } = useWorkspace(); + const { + namespace: { name: namespace }, + } = useWorkspace(); const queryClient = useQueryClient(); const [tags, setTags] = useState([]); @@ -135,7 +137,9 @@ export const CreateGraphForm = ({ ) => { if (d.response?.code === EnumStatusCode.OK) { // We need to refresh the workspace after creating a graph - await queryClient.refetchQueries({ queryKey: createConnectQueryKey(getWorkspace) }); + await queryClient.refetchQueries({ + queryKey: createConnectQueryKey(getWorkspace), + }); router.replace( `/${user?.currentOrganization.slug}/${namespace}/graph/${data.name}`, ); @@ -284,10 +288,13 @@ export const CreateGraphForm = ({ /> - Label matchers are used to select which subgraphs participate in this federated graph composition. - Enter space-separated key-value pairs in the format key=value. - To specify multiple values for the same key (OR condition), use commas within a single matcher (e.g., team=A,team=B matches subgraphs where team is either A or B). - {" "} + Label matchers are used to select which subgraphs + participate in this federated graph composition. Enter + space-separated key-value pairs in the format{" "} + key=value. To specify multiple values for + the same key (OR condition), use commas within a single + matcher (e.g., team=A,team=B matches + subgraphs where team is either A or B).{" "} - {(isFiltering || activeSubgraphId) && subgraphs.map((subgraph, subgraphIndex) => ( - - ))} + {(isFiltering || activeSubgraphId) && + subgraphs.map((subgraph, subgraphIndex) => ( + + ))} ))} @@ -69,11 +70,11 @@ interface GraphLinkProps { const defaultGraphTemplate = `/[organizationSlug]/[namespace]/graph/[slug]`; const graphAreasWithParameters: readonly string[] = [ - 'change-log', - 'checks', - 'compositions', - 'feature-flags', - 'proposals' + "change-log", + "checks", + "compositions", + "feature-flags", + "proposals", ]; function GraphCommandItem({ @@ -89,60 +90,63 @@ function GraphCommandItem({ const router = useRouter(); const organizationSlug = useCurrentOrganization()?.slug; - const pathname = useMemo( - () => { - const segmentSplit = router.pathname.split('/'); - const segment = segmentSplit[3]?.toLowerCase(); - if (isSubgraph) { - return segment === 'subgraph' - ? router.pathname - : `/[organizationSlug]/[namespace]/subgraph/[subgraphSlug]`; - } + const pathname = useMemo(() => { + const segmentSplit = router.pathname.split("/"); + const segment = segmentSplit[3]?.toLowerCase(); + if (isSubgraph) { + return segment === "subgraph" + ? router.pathname + : `/[organizationSlug]/[namespace]/subgraph/[subgraphSlug]`; + } - if (segment !== 'graph') { - return defaultGraphTemplate; - } + if (segment !== "graph") { + return defaultGraphTemplate; + } - const areaSegment = segmentSplit[5]?.toLowerCase(); - return areaSegment && graphAreasWithParameters.includes(areaSegment) && segmentSplit.length > 5 - ? `${defaultGraphTemplate}/${areaSegment}` - : router.pathname; - }, - [router.pathname, isSubgraph], - ); + const areaSegment = segmentSplit[5]?.toLowerCase(); + return areaSegment && + graphAreasWithParameters.includes(areaSegment) && + segmentSplit.length > 5 + ? `${defaultGraphTemplate}/${areaSegment}` + : router.pathname; + }, [router.pathname, isSubgraph]); return ( { - router.push({ - pathname, - query: { - organizationSlug, - namespace: namespace.name, - ...(isSubgraph ? { subgraphSlug: name } : { slug: name }), - } - }).finally(() => setNamespace(namespace.name)); + router + .push({ + pathname, + query: { + organizationSlug, + namespace: namespace.name, + ...(isSubgraph ? { subgraphSlug: name } : { slug: name }), + }, + }) + .finally(() => setNamespace(namespace.name)); }} > - + {name} {!isSubgraph && isContract && ( - contract + + contract + )} ); -} \ No newline at end of file +} diff --git a/studio/src/components/dashboard/graph-selector.tsx b/studio/src/components/dashboard/graph-selector.tsx index 8e174a7bf4..71cf05af18 100644 --- a/studio/src/components/dashboard/graph-selector.tsx +++ b/studio/src/components/dashboard/graph-selector.tsx @@ -1,18 +1,24 @@ import { useState } from "react"; import { Popover, PopoverTrigger } from "@/components/ui/popover"; -import { WorkspaceCommandWrapper } from "./workspace-command-wrapper" +import { WorkspaceCommandWrapper } from "./workspace-command-wrapper"; import { Button } from "@/components/ui/button"; import { CaretSortIcon } from "@radix-ui/react-icons"; import * as React from "react"; -import { WorkspaceFederatedGraph, WorkspaceSubgraph } from "@wundergraph/cosmo-connect/dist/platform/v1/platform_pb"; +import { + WorkspaceFederatedGraph, + WorkspaceSubgraph, +} from "@wundergraph/cosmo-connect/dist/platform/v1/platform_pb"; interface GraphSelectorProps { activeGraph: WorkspaceFederatedGraph | undefined; activeSubgraph: WorkspaceSubgraph | undefined; } -export function GraphSelector({ activeGraph, activeSubgraph }: GraphSelectorProps) { - const [filter, setFilter] = useState(''); +export function GraphSelector({ + activeGraph, + activeSubgraph, +}: GraphSelectorProps) { + const [filter, setFilter] = useState(""); const [isOpen, setOpen] = useState(false); if (!activeGraph && !activeSubgraph) { return null; @@ -20,7 +26,7 @@ export function GraphSelector({ activeGraph, activeSubgraph }: GraphSelectorProp return ( <> - / + / - @@ -49,10 +56,10 @@ export function GraphSelector({ activeGraph, activeSubgraph }: GraphSelectorProp setFilter={setFilter} close={() => { setOpen(false); - setFilter(''); + setFilter(""); }} /> ); -} \ No newline at end of file +} diff --git a/studio/src/components/dashboard/namespace-selector.tsx b/studio/src/components/dashboard/namespace-selector.tsx index a7abf058e2..6123026178 100644 --- a/studio/src/components/dashboard/namespace-selector.tsx +++ b/studio/src/components/dashboard/namespace-selector.tsx @@ -1,4 +1,8 @@ -import { CommandItem, CommandGroup, CommandSeparator } from "@/components/ui/command"; +import { + CommandItem, + CommandGroup, + CommandSeparator, +} from "@/components/ui/command"; import { Popover, PopoverTrigger } from "@/components/ui/popover"; import { useWorkspace } from "@/hooks/use-workspace"; import { useRouter } from "next/router"; @@ -8,7 +12,7 @@ import { cn } from "@/lib/utils"; import * as React from "react"; import { CheckIcon, CaretSortIcon } from "@radix-ui/react-icons"; import { docsBaseURL } from "@/lib/constants"; -import { WorkspaceCommandWrapper } from "./workspace-command-wrapper" +import { WorkspaceCommandWrapper } from "./workspace-command-wrapper"; import { useCurrentOrganization } from "@/hooks/use-current-organization"; interface NamespaceSelectorProps { @@ -16,27 +20,32 @@ interface NamespaceSelectorProps { truncateNamespace: boolean; } -export function NamespaceSelector({ isViewingGraphOrSubgraph, truncateNamespace }: NamespaceSelectorProps) { - const [filter, setFilter] = useState(''); +export function NamespaceSelector({ + isViewingGraphOrSubgraph, + truncateNamespace, +}: NamespaceSelectorProps) { + const [filter, setFilter] = useState(""); const [isOpen, setOpen] = useState(false); - const { isLoading, namespace, namespaceByName, setNamespace } = useWorkspace(); + const { isLoading, namespace, namespaceByName, setNamespace } = + useWorkspace(); const router = useRouter(); const organizationSlug = useCurrentOrganization()?.slug; const pathname = useMemo( - () => router.pathname.split('/').length === 3 ? router.pathname : '/[organizationSlug]/graphs', - [router.pathname] + () => + router.pathname.split("/").length === 3 + ? router.pathname + : "/[organizationSlug]/graphs", + [router.pathname], ); const namespaces = Array.from(namespaceByName.keys()); if (isLoading) { return ( - + {namespace.name} @@ -56,14 +65,14 @@ export function NamespaceSelector({ isViewingGraphOrSubgraph, truncateNamespace query: { organizationSlug, namespace: namespace.name }, }} className={cn( - "bg-primary/15 hover:bg-primary/30 text-primary transition-colors duration-150 pl-3 pr-2 py-1.5 rounded-l-lg text-sm flex-shrink-0", - truncateNamespace && "max-w-[180px] lg:max-w-xs truncate" + "flex-shrink-0 rounded-l-lg bg-primary/15 py-1.5 pl-3 pr-2 text-sm text-primary transition-colors duration-150 hover:bg-primary/30", + truncateNamespace && "max-w-[180px] truncate lg:max-w-xs", )} onClick={() => setNamespace(namespace.name, false)} > {namespace.name} -

+
)} @@ -81,15 +90,17 @@ export function NamespaceSelector({ isViewingGraphOrSubgraph, truncateNamespace ); @@ -45,7 +54,9 @@ export function GroupSelect({ id, value, disabled = false, groups, onValueChange ); -} \ No newline at end of file +} diff --git a/studio/src/components/layout/analytics/active-campaign-script.tsx b/studio/src/components/layout/analytics/active-campaign-script.tsx index 92da8e2018..a0bea157f1 100644 --- a/studio/src/components/layout/analytics/active-campaign-script.tsx +++ b/studio/src/components/layout/analytics/active-campaign-script.tsx @@ -1,10 +1,11 @@ import Script from "next/script"; -const ACTIVE_CAMPAIGN_SCRIPT_SRC = 'https://diffuser-cdn.app-us1.com/diffuser/diffuser.js'; +const ACTIVE_CAMPAIGN_SCRIPT_SRC = + "https://diffuser-cdn.app-us1.com/diffuser/diffuser.js"; export function ActiveCampaignScript() { const acAccount = process.env.NEXT_PUBLIC_ACTIVE_CAMPAIGN_ACCOUNT; - if (!acAccount || process.env.NODE_ENV !== 'production') { + if (!acAccount || process.env.NODE_ENV !== "production") { return null; } @@ -57,4 +58,4 @@ export function ActiveCampaignScript() { })(); `} ); -} \ No newline at end of file +} diff --git a/studio/src/components/layout/analytics/gtm-script.tsx b/studio/src/components/layout/analytics/gtm-script.tsx index eaf2919348..08b75f2844 100644 --- a/studio/src/components/layout/analytics/gtm-script.tsx +++ b/studio/src/components/layout/analytics/gtm-script.tsx @@ -1,6 +1,6 @@ export interface GtmScriptProps { gtmId: string | undefined; -}; +} export function GtmScript({ gtmId }: GtmScriptProps) { if (!gtmId) { @@ -11,7 +11,8 @@ export function GtmScript({ gtmId }: GtmScriptProps) { <>