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/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) { <>