diff --git a/apps/hash-ai-worker-ts/scripts/flow-scenarios/run-flow-scenario.ts b/apps/hash-ai-worker-ts/scripts/flow-scenarios/run-flow-scenario.ts index fb4fadd1b0c..3da8ca5296e 100644 --- a/apps/hash-ai-worker-ts/scripts/flow-scenarios/run-flow-scenario.ts +++ b/apps/hash-ai-worker-ts/scripts/flow-scenarios/run-flow-scenario.ts @@ -294,7 +294,7 @@ const getUserFlowDefinitionId = ( ) => uuidv5(userId, scenarioFlowDefinitionId) as EntityUuid; /** - * Runs load their definition from the `Flow Definition` entity whose uuid is the definition's id. Migrations only + * Runs load their definition from its `Flow Definition` entity, in the user's web here. Migrations only * seed the flows HASH provides, so the scenario's definition is written to the user's web: created if it doesn't * exist, and updated if it differs, like `seedFlowDefinitions` does for seeded flows. */ @@ -318,7 +318,10 @@ const ensureFlowDefinitionEntity = async ({ const properties = mapFlowDefinitionToEntityProperties(flowDefinition); const existingEntity = await getFlowDefinitionEntityById({ - flowDefinitionId, + flowDefinitionEntityId: entityIdFromComponents( + userId as WebId, + flowDefinitionId, + ), graphApiClient: graphApi, userAuthentication: authentication, }); @@ -446,6 +449,10 @@ const startRun = async ({ }) => { const workflowId = generateUuid() as EntityUuid; const webId = userId as WebId; + const flowDefinitionEntityId = entityIdFromComponents( + webId, + flow.flowDefinitionId, + ); const params: RunFlowWorkflowParams = { ...(taskQueue === "ai" ? { @@ -460,7 +467,7 @@ const startRun = async ({ : {}), flowRunEntityUuid: workflowId, flowInputs: inputs, - flowDefinitionId: flow.flowDefinitionId, + flowDefinitionEntityId, userAuthentication: { actorId: userId }, webId, }; @@ -469,7 +476,7 @@ const startRun = async ({ taskQueue, args: [params], memo: { - flowDefinitionId: flow.flowDefinitionId, + flowDefinitionEntityId, userAccountId: userId, webId, }, @@ -902,7 +909,10 @@ const main = async () => { const workflowError = await workflowErrorWhenClosed; const flowRun = await getFlowRunFromTemporalWorkflowId({ - flowDefinitionId, + flowDefinitionEntityId: entityIdFromComponents( + userId as WebId, + flowDefinitionId, + ), flowRunEntityId: entityIdFromComponents(userId as WebId, workflowId), name: flowDefinition.name, temporalClient: client, diff --git a/apps/hash-ai-worker-ts/src/activities/flow-activities/generate-flow-run-name-activity.ts b/apps/hash-ai-worker-ts/src/activities/flow-activities/generate-flow-run-name-activity.ts index 01df5f7e3dd..7df0be988b1 100644 --- a/apps/hash-ai-worker-ts/src/activities/flow-activities/generate-flow-run-name-activity.ts +++ b/apps/hash-ai-worker-ts/src/activities/flow-activities/generate-flow-run-name-activity.ts @@ -1,6 +1,7 @@ import { flowDefinitionIds, - isGoalFlowDefinitionId, + isGoalFlowDefinitionEntityId, + isHashFlowDefinitionEntityId, } from "@local/hash-isomorphic-utils/flows/flow-definition-ids"; import { getFlowContext } from "../shared/get-flow-context.js"; @@ -9,7 +10,7 @@ import { getTextContentFromLlmMessage } from "../shared/get-llm-response/llm-mes import { graphApiClient } from "../shared/graph-api-client.js"; import type { UsageTrackingParams } from "../shared/get-llm-response.js"; -import type { EntityUuid } from "@blockprotocol/type-system"; +import type { EntityId, EntityUuid } from "@blockprotocol/type-system"; import type { AutomaticInferenceInputName, ManualInferenceInputName, @@ -25,7 +26,7 @@ import type { type GenerateFlowRunNameActivityParams = { flowDefinition: FlowDefinition; - flowDefinitionId: EntityUuid; + flowDefinitionEntityId: EntityId; flowInputs: FlowInputValues; }; @@ -95,13 +96,14 @@ const inputKindsToIgnore: PayloadKind[] = [ export const generateFlowRunName = async ( params: GenerateFlowRunNameActivityParams, ) => { - const { flowDefinition, flowDefinitionId, flowInputs } = params; + const { flowDefinition, flowDefinitionEntityId, flowInputs } = params; + + const isHashFlow = (flowDefinitionId: EntityUuid) => + isHashFlowDefinitionEntityId(flowDefinitionEntityId, flowDefinitionId); if ( - [ - flowDefinitionIds.automaticBrowserInference, - flowDefinitionIds.manualBrowserInference, - ].includes(flowDefinitionId) + isHashFlow(flowDefinitionIds.automaticBrowserInference) || + isHashFlow(flowDefinitionIds.manualBrowserInference) ) { const webPage = flowInputs[ "visitedWebPage" satisfies AutomaticInferenceInputName & @@ -113,7 +115,7 @@ export const generateFlowRunName = async ( } return `${ - flowDefinitionId === flowDefinitionIds.automaticBrowserInference + isHashFlow(flowDefinitionIds.automaticBrowserInference) ? "Auto-analyze" : "Analyze" } webpage: ${webPage.url}`; @@ -130,7 +132,7 @@ export const generateFlowRunName = async ( webId, }; - if (isGoalFlowDefinitionId(flowDefinitionId)) { + if (isGoalFlowDefinitionEntityId(flowDefinitionEntityId)) { const researchBrief = flowInputs[ "researchGuidance" satisfies GoalFlowInputName ]?.value as PayloadKindValues["Text"] | undefined; diff --git a/apps/hash-ai-worker-ts/src/shared/testing-utilities/mock-get-flow-context.ts b/apps/hash-ai-worker-ts/src/shared/testing-utilities/mock-get-flow-context.ts index 013f57a5cfe..e34b38abb68 100644 --- a/apps/hash-ai-worker-ts/src/shared/testing-utilities/mock-get-flow-context.ts +++ b/apps/hash-ai-worker-ts/src/shared/testing-utilities/mock-get-flow-context.ts @@ -1,6 +1,7 @@ // eslint-disable-next-line import/no-extraneous-dependencies import { vi } from "vitest"; +import { entityIdFromComponents } from "@blockprotocol/type-system"; import { HashEntity } from "@local/hash-graph-sdk/entity"; import { mapFlowRunToEntityProperties } from "@local/hash-isomorphic-utils/flows/mappings"; import { generateUuid } from "@local/hash-isomorphic-utils/generate-uuid"; @@ -34,7 +35,10 @@ const createDummyFlow = async (params: { actorId: ActorEntityUuid }) => { const dummyFlowRunProperties = mapFlowRunToEntityProperties({ name: "dummy-name", flowInputs: {}, - flowDefinitionId: generateUuid() as EntityUuid, + flowDefinitionEntityId: entityIdFromComponents( + actorId as WebId, + generateUuid() as EntityUuid, + ), steps: [], temporalWorkflowId: generateUuid(), }); @@ -132,7 +136,10 @@ vi.mock("@local/hash-backend-utils/temporal", async (importOriginal) => { }, }, }, - flowDefinitionId: generateUuid() as EntityUuid, + flowDefinitionEntityId: entityIdFromComponents( + aliceUserAccountId as WebId, + generateUuid() as EntityUuid, + ), flowInputs: {}, webId: aliceUserAccountId, userAuthentication: { diff --git a/apps/hash-ai-worker-ts/src/workflows/run-flow-workflow.ts b/apps/hash-ai-worker-ts/src/workflows/run-flow-workflow.ts index 714b026c3d8..70478c1656b 100644 --- a/apps/hash-ai-worker-ts/src/workflows/run-flow-workflow.ts +++ b/apps/hash-ai-worker-ts/src/workflows/run-flow-workflow.ts @@ -10,7 +10,7 @@ import { heartbeatTimeoutSeconds } from "../shared/heartbeats.js"; import { setQueryAndSignalHandlers } from "./run-flow-workflow/set-query-and-signal-handlers.js"; import type { createFlowActivities } from "../activities/flow-activities.js"; -import type { EntityUuid } from "@blockprotocol/type-system"; +import type { EntityId } from "@blockprotocol/type-system"; import type { ProxyFlowActivity } from "@local/hash-backend-utils/flows"; import type { RunAiFlowWorkflowParams, @@ -84,7 +84,7 @@ const proxyFlowActivity: ProxyFlowActivity< const generateFlowRunName = (params: { flowDefinition: FlowDefinition; - flowDefinitionId: EntityUuid; + flowDefinitionEntityId: EntityId; flowInputs: FlowInputValues; }) => { const { generateFlowRunName: generateFlowRunNameActivity } = proxyActivities< diff --git a/apps/hash-api/src/ai/infer-entities-websocket/handle-infer-entities-request.ts b/apps/hash-api/src/ai/infer-entities-websocket/handle-infer-entities-request.ts index e556d53c9c1..8f1b9263577 100644 --- a/apps/hash-api/src/ai/infer-entities-websocket/handle-infer-entities-request.ts +++ b/apps/hash-api/src/ai/infer-entities-websocket/handle-infer-entities-request.ts @@ -1,5 +1,9 @@ import { getFlowRuns } from "@local/hash-backend-utils/flows"; -import { flowDefinitionIds } from "@local/hash-isomorphic-utils/flows/flow-definition-ids"; +import { getHashWebId } from "@local/hash-backend-utils/flows/shared/get-hash-web-id"; +import { + flowDefinitionIds, + generateHashFlowDefinitionEntityId, +} from "@local/hash-isomorphic-utils/flows/flow-definition-ids"; import { FlowRunStatus } from "../../graphql/api-types.gen"; @@ -41,20 +45,28 @@ export const handleInferEntitiesRequest = async ({ payload: { webId, ...flowInputs }, } = message; - const flowDefinitionId = + const hashWebId = await getHashWebId(graphApiClient, { + actorId: user.accountId, + }); + + const flowDefinitionEntityId = generateHashFlowDefinitionEntityId( + hashWebId, message.type === "manual-inference-request" ? flowDefinitionIds.manualBrowserInference - : flowDefinitionIds.automaticBrowserInference; + : flowDefinitionIds.automaticBrowserInference, + ); if (message.type === "automatic-inference-request") { const openFlowRuns = await getFlowRuns({ authentication: { actorId: user.accountId }, filters: { executionStatus: FlowRunStatus.Running, - flowDefinitionIds: [ + flowDefinitionEntityIds: [ flowDefinitionIds.automaticBrowserInference, flowDefinitionIds.manualBrowserInference, - ], + ].map((flowDefinitionId) => + generateHashFlowDefinitionEntityId(hashWebId, flowDefinitionId), + ), }, graphApiClient, includeDetails: true, @@ -91,7 +103,7 @@ export const handleInferEntitiesRequest = async ({ }, }, }, - flowDefinitionId, + flowDefinitionEntityId, flowInputs, /* So that the run's entity id can be worked out from the request, as the browser plugin does. */ flowRunEntityUuid: requestUuid as EntityUuid, @@ -100,7 +112,7 @@ export const handleInferEntitiesRequest = async ({ }, ], memo: { - flowDefinitionId, + flowDefinitionEntityId, userAccountId: user.accountId, webId, }, diff --git a/apps/hash-api/src/graph/knowledge/system-types/flow-schedule.ts b/apps/hash-api/src/graph/knowledge/system-types/flow-schedule.ts index f34194ca172..c22294b7c8d 100644 --- a/apps/hash-api/src/graph/knowledge/system-types/flow-schedule.ts +++ b/apps/hash-api/src/graph/knowledge/system-types/flow-schedule.ts @@ -39,10 +39,7 @@ const isEntityFlowScheduleEntity = ( ); export const createFlowSchedule: ImpureGraphFunction< - CreateFlowScheduleInput & { - /** The `Flow Definition` entity the schedule starts runs of. */ - flowDefinitionEntityId: EntityId; - }, + CreateFlowScheduleInput, Promise> > = async (context, authentication, params) => { const { diff --git a/apps/hash-api/src/graphql/resolvers/flows/flow-schedule.ts b/apps/hash-api/src/graphql/resolvers/flows/flow-schedule.ts index 2f9f87ef262..f12f3e1140b 100644 --- a/apps/hash-api/src/graphql/resolvers/flows/flow-schedule.ts +++ b/apps/hash-api/src/graphql/resolvers/flows/flow-schedule.ts @@ -43,11 +43,11 @@ export const createFlowScheduleResolver: ResolverFn< const context = graphQLContextToImpureGraphContext(graphQLContext); const { authentication } = graphQLContext; - const { flowDefinitionId } = input; + const { flowDefinitionEntityId } = input; - const { flowDefinitionEntity, flowType } = await getRunnableFlowDefinition( + const { flowType } = await getRunnableFlowDefinition( graphQLContext, - flowDefinitionId, + flowDefinitionEntityId, ); if (flowType === "ai" && !input.dataSources) { @@ -56,10 +56,11 @@ export const createFlowScheduleResolver: ResolverFn< assertClientFlowInputs(input.flowInputs); - const schedule = await createFlowScheduleEntity(context, authentication, { - ...input, - flowDefinitionEntityId: flowDefinitionEntity.metadata.recordId.entityId, - }); + const schedule = await createFlowScheduleEntity( + context, + authentication, + input, + ); const props = simplifyProperties(schedule.properties); const scheduleId = extractEntityUuidFromEntityId( @@ -72,7 +73,7 @@ export const createFlowScheduleResolver: ResolverFn< ...(flowType === "ai" && input.dataSources ? { dataSources: input.dataSources } : {}), - flowDefinitionId, + flowDefinitionEntityId, flowRunName: input.name, flowInputs: input.flowInputs, flowTriggerEntityId: schedule.metadata.recordId.entityId, @@ -90,7 +91,7 @@ export const createFlowScheduleResolver: ResolverFn< taskQueue, args: [workflowParams], memo: { - flowDefinitionId, + flowDefinitionEntityId, userAccountId: user.accountId, webId: input.webId, }, diff --git a/apps/hash-api/src/graphql/resolvers/flows/get-flow-runs.ts b/apps/hash-api/src/graphql/resolvers/flows/get-flow-runs.ts index 8d27ac2f0e2..19993be5047 100644 --- a/apps/hash-api/src/graphql/resolvers/flows/get-flow-runs.ts +++ b/apps/hash-api/src/graphql/resolvers/flows/get-flow-runs.ts @@ -24,12 +24,12 @@ export const getFlowRunsResolver: ResolverFn< const { authentication, dataSources, temporal } = context; - const { flowDefinitionIds, executionStatus, cursor, limit } = args; + const { flowDefinitionEntityIds, executionStatus, cursor, limit } = args; return await getFlowRuns({ authentication, filters: { - flowDefinitionIds, + flowDefinitionEntityIds, executionStatus, cursor, limit, diff --git a/apps/hash-api/src/graphql/resolvers/flows/shared/get-runnable-flow-definition.ts b/apps/hash-api/src/graphql/resolvers/flows/shared/get-runnable-flow-definition.ts index 85a9df34dd1..d116dd1e064 100644 --- a/apps/hash-api/src/graphql/resolvers/flows/shared/get-runnable-flow-definition.ts +++ b/apps/hash-api/src/graphql/resolvers/flows/shared/get-runnable-flow-definition.ts @@ -5,7 +5,7 @@ import { getFlowDefinitionFromEntity } from "@local/hash-isomorphic-utils/flows/ import * as GraphQLError from "../../../error"; import type { LoggedInGraphQLContext } from "../../../context"; -import type { EntityUuid } from "@blockprotocol/type-system"; +import type { EntityId } from "@blockprotocol/type-system"; /** * Loads a flow's definition from its `Flow Definition` entity, and checks that the user can run it. @@ -15,19 +15,19 @@ import type { EntityUuid } from "@blockprotocol/type-system"; */ export const getRunnableFlowDefinition = async ( graphQLContext: LoggedInGraphQLContext, - flowDefinitionId: EntityUuid, + flowDefinitionEntityId: EntityId, ) => { const { dataSources, authentication, user } = graphQLContext; const flowDefinitionEntity = await getFlowDefinitionEntityById({ - flowDefinitionId, + flowDefinitionEntityId, graphApiClient: dataSources.graphApi, userAuthentication: authentication, }); if (!flowDefinitionEntity) { throw GraphQLError.notFound( - `No flow definition with id ${flowDefinitionId} exists, or you can't view it`, + `No flow definition with id ${flowDefinitionEntityId} exists, or you can't view it`, ); } @@ -38,7 +38,7 @@ export const getRunnableFlowDefinition = async ( } catch (error) { /* The definition is stored data, so an invalid one is a fault here, not in the request. */ throw GraphQLError.internal( - `Flow definition ${flowDefinitionId} is invalid: ${(error as Error).message}`, + `Flow definition ${flowDefinitionEntityId} is invalid: ${(error as Error).message}`, ); } diff --git a/apps/hash-api/src/graphql/resolvers/flows/start-flow.ts b/apps/hash-api/src/graphql/resolvers/flows/start-flow.ts index 0e5580ac104..7d473c12766 100644 --- a/apps/hash-api/src/graphql/resolvers/flows/start-flow.ts +++ b/apps/hash-api/src/graphql/resolvers/flows/start-flow.ts @@ -23,7 +23,7 @@ export const startFlow: ResolverFn< MutationStartFlowArgs > = async ( _, - { dataSources, flowInputs, flowDefinitionId, webId }, + { dataSources, flowInputs, flowDefinitionEntityId, webId }, graphQLContext, ) => { const { temporal, user } = graphQLContext; @@ -33,7 +33,7 @@ export const startFlow: ResolverFn< */ const { flowType } = await getRunnableFlowDefinition( graphQLContext, - flowDefinitionId, + flowDefinitionEntityId, ); assertClientFlowInputs(flowInputs); @@ -48,7 +48,7 @@ export const startFlow: ResolverFn< ...(flowType === "ai" ? { dataSources } : {}), flowRunEntityUuid: workflowId, flowInputs, - flowDefinitionId, + flowDefinitionEntityId, userAuthentication: { actorId: user.accountId }, webId, }; @@ -59,7 +59,7 @@ export const startFlow: ResolverFn< taskQueue: flowType, args: [params], memo: { - flowDefinitionId, + flowDefinitionEntityId, userAccountId: user.accountId, webId, }, diff --git a/apps/hash-api/src/graphql/resolvers/knowledge/file/create-file-from-url.ts b/apps/hash-api/src/graphql/resolvers/knowledge/file/create-file-from-url.ts index 069a8641ebe..b5bbf15f73d 100644 --- a/apps/hash-api/src/graphql/resolvers/knowledge/file/create-file-from-url.ts +++ b/apps/hash-api/src/graphql/resolvers/knowledge/file/create-file-from-url.ts @@ -44,6 +44,7 @@ export const createFileFromUrl: ResolverFn< if (user.enabledFeatureFlags.includes("ai")) { await triggerPdfAnalysisWorkflow({ entity, + graphApiClient: context.graphApi, temporalClient: temporal, userAccountId: user.accountId, webId: extractWebIdFromEntityId(entity.entityId), diff --git a/apps/hash-api/src/graphql/resolvers/knowledge/file/request-file-upload.ts b/apps/hash-api/src/graphql/resolvers/knowledge/file/request-file-upload.ts index bb422e189b0..d40ede68484 100644 --- a/apps/hash-api/src/graphql/resolvers/knowledge/file/request-file-upload.ts +++ b/apps/hash-api/src/graphql/resolvers/knowledge/file/request-file-upload.ts @@ -67,6 +67,7 @@ export const requestFileUpload: ResolverFn< if (user.enabledFeatureFlags.includes("ai")) { await triggerPdfAnalysisWorkflow({ entity, + graphApiClient: context.graphApi, temporalClient: temporal, userAccountId: user.accountId, webId: extractWebIdFromEntityId(entity.entityId), diff --git a/apps/hash-api/src/graphql/resolvers/knowledge/file/shared.ts b/apps/hash-api/src/graphql/resolvers/knowledge/file/shared.ts index 7a09a7a4f8b..5fc99e0de20 100644 --- a/apps/hash-api/src/graphql/resolvers/knowledge/file/shared.ts +++ b/apps/hash-api/src/graphql/resolvers/knowledge/file/shared.ts @@ -1,8 +1,13 @@ -import { flowDefinitionIds } from "@local/hash-isomorphic-utils/flows/flow-definition-ids"; +import { getHashWebId } from "@local/hash-backend-utils/flows/shared/get-hash-web-id"; +import { + flowDefinitionIds, + generateHashFlowDefinitionEntityId, +} from "@local/hash-isomorphic-utils/flows/flow-definition-ids"; import { generateUuid } from "@local/hash-isomorphic-utils/generate-uuid"; import type { Entity, UserId, WebId } from "@blockprotocol/type-system"; import type { TemporalClient } from "@local/hash-backend-utils/temporal"; +import type { GraphApi } from "@local/hash-graph-client"; import type { RunFlowWorkflowParams, RunFlowWorkflowResponse, @@ -11,11 +16,13 @@ import type { File } from "@local/hash-isomorphic-utils/system-types/shared"; export const triggerPdfAnalysisWorkflow = async ({ entity, + graphApiClient, temporalClient, userAccountId, webId, }: { entity: Entity; + graphApiClient: GraphApi; temporalClient: TemporalClient; userAccountId: UserId; webId: WebId; @@ -31,6 +38,11 @@ export const triggerPdfAnalysisWorkflow = async ({ return; } + const flowDefinitionEntityId = generateHashFlowDefinitionEntityId( + await getHashWebId(graphApiClient, { actorId: userAccountId }), + flowDefinitionIds.inferMetadataFromDocument, + ); + const params: RunFlowWorkflowParams = { dataSources: { files: { fileEntityIds: [] }, @@ -42,7 +54,7 @@ export const triggerPdfAnalysisWorkflow = async ({ }, }, }, - flowDefinitionId: flowDefinitionIds.inferMetadataFromDocument, + flowDefinitionEntityId, flowInputs: { fileEntityId: { kind: "EntityId", value: entityId }, }, @@ -58,7 +70,7 @@ export const triggerPdfAnalysisWorkflow = async ({ taskQueue: "ai", args: [params], memo: { - flowDefinitionId: flowDefinitionIds.inferMetadataFromDocument, + flowDefinitionEntityId, userAccountId, webId, }, diff --git a/apps/hash-frontend/src/graphql/queries/knowledge/flow.queries.ts b/apps/hash-frontend/src/graphql/queries/knowledge/flow.queries.ts index ed1c80643b9..8e2dca1ee8c 100644 --- a/apps/hash-frontend/src/graphql/queries/knowledge/flow.queries.ts +++ b/apps/hash-frontend/src/graphql/queries/knowledge/flow.queries.ts @@ -3,13 +3,13 @@ import { gql } from "@apollo/client"; export const startFlowMutation = gql` mutation startFlow( $dataSources: FlowDataSources - $flowDefinitionId: EntityUuid! + $flowDefinitionEntityId: EntityId! $flowInputs: FlowInputValues! $webId: WebId! ) { startFlow( dataSources: $dataSources - flowDefinitionId: $flowDefinitionId + flowDefinitionEntityId: $flowDefinitionEntityId flowInputs: $flowInputs webId: $webId ) diff --git a/apps/hash-frontend/src/pages/@/[shortname]/flows/[flow-def-id].page.tsx b/apps/hash-frontend/src/pages/@/[shortname]/flows/[flow-def-id].page.tsx index ecafb83d3f8..677a5f2a844 100644 --- a/apps/hash-frontend/src/pages/@/[shortname]/flows/[flow-def-id].page.tsx +++ b/apps/hash-frontend/src/pages/@/[shortname]/flows/[flow-def-id].page.tsx @@ -1,9 +1,16 @@ import { useRouter } from "next/router"; +import { entityIdFromComponents } from "@blockprotocol/type-system"; + import { getLayoutWithSidebar } from "../../../../shared/layout"; import { FlowDefinitionsContextProvider } from "../../../shared/flow-definitions-context-provider"; import { FlowRunsContextProvider } from "../../../shared/flow-runs-context-provider"; -import { FlowVisualizer } from "../shared/flow-visualizer"; +import { NotFound } from "../../../shared/not-found"; +import { + FlowRunVisualizerSkeleton, + FlowVisualizer, +} from "../shared/flow-visualizer"; +import { useRouteNamespace } from "../shared/use-route-namespace"; import type { NextPageWithLayout } from "../../../../shared/layout"; import type { EntityUuid } from "@blockprotocol/type-system"; @@ -11,12 +18,23 @@ import type { EntityUuid } from "@blockprotocol/type-system"; const FlowDefinitionPage: NextPageWithLayout = () => { const { query } = useRouter(); - /** @todo replace with real uuid once flow definitions are stored in the db */ - const routeFlowDefinitionId = query["flow-def-id"] as EntityUuid; + const routeFlowDefinitionUuid = query["flow-def-id"] as EntityUuid; + + /* The path has the uuid of the definition's entity, and its shortname names the web the definition is in. */ + const { loading, routeNamespace } = useRouteNamespace(); + + if (!routeNamespace) { + return loading ? : ; + } + + const flowDefinitionEntityId = entityIdFromComponents( + routeNamespace.webId, + routeFlowDefinitionUuid, + ); return ( diff --git a/apps/hash-frontend/src/pages/@/[shortname]/shared/flow-visualizer.tsx b/apps/hash-frontend/src/pages/@/[shortname]/shared/flow-visualizer.tsx index 667db745d89..8d6bbbcb90f 100644 --- a/apps/hash-frontend/src/pages/@/[shortname]/shared/flow-visualizer.tsx +++ b/apps/hash-frontend/src/pages/@/[shortname]/shared/flow-visualizer.tsx @@ -11,7 +11,8 @@ import { deserializeSubgraph } from "@local/hash-graph-sdk/subgraph"; import { actionDefinitions } from "@local/hash-isomorphic-utils/flows/action-definitions"; import { flowDefinitionIds, - isGoalFlowDefinitionId, + isGoalFlowDefinitionEntityId, + isHashFlowDefinitionEntityId, } from "@local/hash-isomorphic-utils/flows/flow-definition-ids"; import { generateWorkerRunPath } from "@local/hash-isomorphic-utils/flows/frontend-paths"; import { @@ -63,12 +64,7 @@ import type { ProposedEntityOutput, } from "./flow-visualizer/shared/types"; import type { EntityRootType, Subgraph } from "@blockprotocol/graph"; -import type { - EntityId, - EntityUuid, - Timestamp, - WebId, -} from "@blockprotocol/type-system"; +import type { EntityId, Timestamp, WebId } from "@blockprotocol/type-system"; import type { Filter } from "@local/hash-graph-client"; import type { HashEntity } from "@local/hash-graph-sdk/entity"; import type { @@ -206,7 +202,7 @@ export const FlowVisualizer = () => { const { flowDefinitions, loading: flowDefinitionsLoading, - selectedFlowDefinitionId, + selectedFlowDefinitionEntityId, } = useFlowDefinitionsContext(); const { selectedFlowRun } = useFlowRunsContext(); @@ -218,8 +214,8 @@ export const FlowVisualizer = () => { flowDefinition: flowOfSelectedRun, loading: flowOfSelectedRunLoading, } = useFlowDefinitionAtTime({ - flowDefinitionId: selectedFlowRun - ? (selectedFlowRun.flowDefinitionId as EntityUuid) + flowDefinitionEntityId: selectedFlowRun + ? selectedFlowRun.flowDefinitionEntityId : null, timestamp: selectedFlowRun ? (selectedFlowRun.startedAt as Timestamp) @@ -246,13 +242,13 @@ export const FlowVisualizer = () => { selectedFlowRun ? flowOfSelectedRun : flowDefinitions.find( - ({ flowDefinitionId }) => - flowDefinitionId === selectedFlowDefinitionId, + ({ flowDefinitionEntityId }) => + flowDefinitionEntityId === selectedFlowDefinitionEntityId, ), [ flowDefinitions, flowOfSelectedRun, - selectedFlowDefinitionId, + selectedFlowDefinitionEntityId, selectedFlowRun, ], ); @@ -600,7 +596,7 @@ export const FlowVisualizer = () => { variables = { dataSources: selectedFlowRun.dataSources, - flowDefinitionId: selectedFlow.flowDefinitionId, + flowDefinitionEntityId: selectedFlow.flowDefinitionEntityId, flowInputs: selectedFlowRun.flowInputs, webId: selectedFlowRun.webId, }; @@ -617,7 +613,7 @@ export const FlowVisualizer = () => { enabled: true, }, }, - flowDefinitionId: selectedFlow.flowDefinitionId, + flowDefinitionEntityId: selectedFlow.flowDefinitionEntityId, flowInputs, webId, }; @@ -662,7 +658,7 @@ export const FlowVisualizer = () => { return ; } - if (selectedFlowDefinitionId) { + if (selectedFlowDefinitionEntityId) { /* The definition doesn't exist, or the user can't view it. */ return ; } @@ -674,11 +670,16 @@ export const FlowVisualizer = () => { selectedFlowRun?.flowRunEntityId ?? "definition" }`; - const isRunnableFromHere = !unrunnableDefinitionIds.includes( - selectedFlow.flowDefinitionId, + const isRunnableFromHere = !unrunnableDefinitionIds.some((flowDefinitionId) => + isHashFlowDefinitionEntityId( + selectedFlow.flowDefinitionEntityId, + flowDefinitionId, + ), ); - const isGoal = isGoalFlowDefinitionId(selectedFlow.flowDefinitionId); + const isGoal = isGoalFlowDefinitionEntityId( + selectedFlow.flowDefinitionEntityId, + ); return ( <> @@ -688,7 +689,7 @@ export const FlowVisualizer = () => { open={showDag} onClose={() => setShowDag(false)} selectedFlowDefinition={selectedFlowDefinition} - flowDefinitionId={selectedFlow.flowDefinitionId} + flowDefinitionEntityId={selectedFlow.flowDefinitionEntityId} /> )} @@ -696,7 +697,7 @@ export const FlowVisualizer = () => { setShowRunModal(false)} onScheduleCreated={() => { @@ -731,7 +732,7 @@ export const FlowVisualizer = () => { > {selectedFlowRun ? ( void; selectedFlowDefinition: FlowDefinition; - flowDefinitionId: EntityUuid; + flowDefinitionEntityId: EntityId; }; export const DagSlide = ({ @@ -26,9 +26,9 @@ export const DagSlide = ({ open, onClose, selectedFlowDefinition, - flowDefinitionId, + flowDefinitionEntityId, }: DagSlideProps) => { - const isGoal = isGoalFlowDefinitionId(flowDefinitionId); + const isGoal = isGoalFlowDefinitionEntityId(flowDefinitionEntityId); return ( ( ); type FlowRunSidebarProps = { - flowDefinitionId: EntityUuid; + flowDefinitionEntityId: EntityId; flowRunEntityId: EntityId; flowScheduleId: EntityUuid | null; graph: EdgesAndNodes; @@ -47,7 +47,7 @@ type FlowRunSidebarProps = { }; export const FlowRunSidebar = ({ - flowDefinitionId, + flowDefinitionEntityId, flowRunEntityId, flowScheduleId, graph, @@ -86,7 +86,9 @@ export const FlowRunSidebar = ({ {flowScheduleId && ( diff --git a/apps/hash-frontend/src/pages/@/[shortname]/shared/flow-visualizer/outputs.tsx b/apps/hash-frontend/src/pages/@/[shortname]/shared/flow-visualizer/outputs.tsx index 40c3b409f03..67123158576 100644 --- a/apps/hash-frontend/src/pages/@/[shortname]/shared/flow-visualizer/outputs.tsx +++ b/apps/hash-frontend/src/pages/@/[shortname]/shared/flow-visualizer/outputs.tsx @@ -9,7 +9,7 @@ import { } from "@blockprotocol/type-system"; import { CheckRegularIcon, IconButton } from "@hashintel/design-system"; import { HashEntity } from "@local/hash-graph-sdk/entity"; -import { isGoalFlowDefinitionId } from "@local/hash-isomorphic-utils/flows/flow-definition-ids"; +import { isGoalFlowDefinitionEntityId } from "@local/hash-isomorphic-utils/flows/flow-definition-ids"; import { currentTimeInstantTemporalAxes } from "@local/hash-isomorphic-utils/graph-queries"; import { getClosedMultiEntityTypesQuery } from "../../../../../graphql/queries/ontology/entity-type.queries"; @@ -33,7 +33,6 @@ import type { EntityRootType, Subgraph } from "@blockprotocol/graph"; import type { EntityEditionId, EntityId, - EntityUuid, LeftClosedTemporalInterval, Timestamp, VersionedUrl, @@ -261,7 +260,7 @@ export const Outputs = ({ const hasClaims = !!selectedFlowRun && - isGoalFlowDefinitionId(selectedFlowRun.flowDefinitionId as EntityUuid); + isGoalFlowDefinitionEntityId(selectedFlowRun.flowDefinitionEntityId); const hasEntities = persistedEntitiesMetadata.length > 0 || proposedEntities.length > 0; diff --git a/apps/hash-frontend/src/pages/@/[shortname]/shared/flow-visualizer/run-flow-modal.tsx b/apps/hash-frontend/src/pages/@/[shortname]/shared/flow-visualizer/run-flow-modal.tsx index c1ab7689fa1..c16fd458317 100644 --- a/apps/hash-frontend/src/pages/@/[shortname]/shared/flow-visualizer/run-flow-modal.tsx +++ b/apps/hash-frontend/src/pages/@/[shortname]/shared/flow-visualizer/run-flow-modal.tsx @@ -24,7 +24,7 @@ import type { CreateFlowScheduleMutationVariables, } from "../../../../../graphql/api-types.gen"; import type { FormState, LocalPayload } from "./run-flow-modal/types"; -import type { EntityUuid, WebId } from "@blockprotocol/type-system"; +import type { EntityId, EntityUuid, WebId } from "@blockprotocol/type-system"; import type { CreateFlowScheduleInput } from "@local/hash-isomorphic-utils/flows/schedule-types"; import type { FlowActionDefinitionId, @@ -116,7 +116,7 @@ const intervalUnitToMs: Record = { type RunFlowModalProps = { flowDefinition: FlowDefinition; - flowDefinitionId: EntityUuid; + flowDefinitionEntityId: EntityId; onClose: () => void; open: boolean; runFlow: (flowInputs: FlowInputValues, webId: WebId) => Promise; @@ -125,7 +125,7 @@ type RunFlowModalProps = { export const RunFlowModal = ({ flowDefinition, - flowDefinitionId, + flowDefinitionEntityId, open, onClose, runFlow, @@ -218,7 +218,7 @@ export const RunFlowModal = ({ const scheduleInput: CreateFlowScheduleInput = { name: scheduleName || `${flowDefinition.name} schedule`, - flowDefinitionId, + flowDefinitionEntityId, webId, scheduleSpec: { type: "interval", diff --git a/apps/hash-frontend/src/pages/@/[shortname]/shared/flow-visualizer/topbar.tsx b/apps/hash-frontend/src/pages/@/[shortname]/shared/flow-visualizer/topbar.tsx index 8f2dff42218..2ff90ed1940 100644 --- a/apps/hash-frontend/src/pages/@/[shortname]/shared/flow-visualizer/topbar.tsx +++ b/apps/hash-frontend/src/pages/@/[shortname]/shared/flow-visualizer/topbar.tsx @@ -10,6 +10,7 @@ import { format } from "date-fns"; import { useRouter } from "next/router"; import { useCallback, useMemo, useState } from "react"; +import { extractWebIdFromEntityId } from "@blockprotocol/type-system"; import { AngleRightRegularIcon, PlaySolidIcon, @@ -35,6 +36,7 @@ import type { CancelFlowMutationVariables, FlowRun, } from "../../../../../graphql/api-types.gen"; +import type { EntityId } from "@blockprotocol/type-system"; import type { SvgIconProps, SxProps, Theme } from "@mui/material"; import type { FunctionComponent } from "react"; @@ -166,7 +168,7 @@ export const Topbar = ({ const [cancelling, setCancelling] = useState(false); - const { flowDefinitions, selectedFlowDefinitionId } = + const { flowDefinitions, selectedFlowDefinitionEntityId } = useFlowDefinitionsContext(); const { flowRuns, selectedFlowRunEntityId, selectedFlowRun } = @@ -200,16 +202,25 @@ export const Topbar = ({ const getOwner = useGetOwnerForEntity(); + /** A definition's page is under the shortname of the web the definition is in. */ + const getFlowDefinitionPath = (flowDefinitionEntityId: EntityId) => + generateFlowDefinitionPath({ + shortname: getOwner({ + webId: extractWebIdFromEntityId(flowDefinitionEntityId), + }).shortname, + flowDefinitionEntityId, + }); + const runOptions = useMemo( () => flowRuns.filter( - (run) => run.flowDefinitionId === selectedFlowDefinitionId, + (run) => run.flowDefinitionEntityId === selectedFlowDefinitionEntityId, ), - [flowRuns, selectedFlowDefinitionId], + [flowRuns, selectedFlowDefinitionEntityId], ); const selectedFlowDefinition = flowDefinitions.find( - (flow) => flow.flowDefinitionId === selectedFlowDefinitionId, + (flow) => flow.flowDefinitionEntityId === selectedFlowDefinitionEntityId, ); return ( @@ -256,25 +267,22 @@ export const Topbar = ({ ) : (