diff --git a/apps/hash-api/src/ai/infer-entities-websocket.ts b/apps/hash-api/src/ai/infer-entities-websocket.ts index ca503872017..1cd21d0a21d 100644 --- a/apps/hash-api/src/ai/infer-entities-websocket.ts +++ b/apps/hash-api/src/ai/infer-entities-websocket.ts @@ -14,7 +14,6 @@ import { handleInferEntitiesRequest } from "./infer-entities-websocket/handle-in import type { GraphApi, ImpureGraphContext } from "../graph/context-types"; import type { User } from "../graph/knowledge/system-types/user"; -import type { EntityUuid } from "@blockprotocol/type-system"; import type { FileStorageProvider } from "@local/hash-backend-utils/file-storage"; import type { Logger } from "@local/hash-backend-utils/logger"; import type { @@ -89,7 +88,7 @@ const inferEntitiesMessageHandler = async ({ for (const inputRequest of flowRun.inputRequests) { if (!inputRequest.resolvedAt) { const requestMessage: ExternalInputWebsocketRequestMessage = { - workflowId: flowRun.flowRunId, + flowRunEntityId: flowRun.flowRunEntityId, payload: inputRequest, type: "external-input-request", }; @@ -100,11 +99,11 @@ const inferEntitiesMessageHandler = async ({ return; } case "external-input-response": { - const { workflowId, payload } = message; + const { flowRunEntityId, payload } = message; const flow = await getFlowRunEntityById({ userAuthentication: { actorId: user.accountId }, - flowRunId: workflowId as EntityUuid, + flowRunEntityId, graphApiClient, }); @@ -112,7 +111,9 @@ const inferEntitiesMessageHandler = async ({ return; } - const handle = temporalClient.workflow.getHandle(workflowId); + const handle = temporalClient.workflow.getHandle( + flow.properties["https://hash.ai/@h/types/property-type/workflow-id/"], + ); await handle.signal<[ExternalInputResponseSignal]>( externalInputResponseSignal, { ...payload, resolvedBy: user.accountId }, @@ -152,7 +153,7 @@ const isInferenceWebsocketClientMessage = ( ); case "cancel-inference-request": return ( - typeof message.flowRunId === "string" && + typeof message.flowRunEntityId === "string" && typeof message.requestUuid === "string" && hasValidOptionalCookie(message) ); @@ -160,7 +161,7 @@ const isInferenceWebsocketClientMessage = ( return hasValidOptionalCookie(message); case "external-input-response": return ( - typeof message.workflowId === "string" && + typeof message.flowRunEntityId === "string" && typeof message.payload === "object" && message.payload !== null && hasValidOptionalCookie(message) diff --git a/apps/hash-api/src/ai/infer-entities-websocket/handle-cancel-infer-entities-request.ts b/apps/hash-api/src/ai/infer-entities-websocket/handle-cancel-infer-entities-request.ts index 45737e15632..6b389e84a64 100644 --- a/apps/hash-api/src/ai/infer-entities-websocket/handle-cancel-infer-entities-request.ts +++ b/apps/hash-api/src/ai/infer-entities-websocket/handle-cancel-infer-entities-request.ts @@ -2,7 +2,6 @@ import { getFlowRunEntityById } from "@local/hash-backend-utils/flows"; import type { GraphApi } from "../../graph/context-types"; import type { User } from "../../graph/knowledge/system-types/user"; -import type { EntityUuid } from "@blockprotocol/type-system"; import type { CancelInferEntitiesWebsocketRequestMessage } from "@local/hash-isomorphic-utils/ai-inference-types"; import type { Client } from "@temporalio/client"; @@ -17,11 +16,11 @@ export const handleCancelInferEntitiesRequest = async ({ message: Omit; user: User; }) => { - const { flowRunId } = message; + const { flowRunEntityId } = message; const flow = await getFlowRunEntityById({ userAuthentication: { actorId: user.accountId }, - flowRunId: flowRunId as EntityUuid, + flowRunEntityId, graphApiClient, }); @@ -29,7 +28,9 @@ export const handleCancelInferEntitiesRequest = async ({ return; } - const workflowHandle = temporalClient.workflow.getHandle(flowRunId); + const workflowHandle = temporalClient.workflow.getHandle( + flow.properties["https://hash.ai/@h/types/property-type/workflow-id/"], + ); void workflowHandle.cancel(); }; 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 26fc0bbd5bc..3f8b6c2e0b5 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 @@ -7,6 +7,7 @@ import { import { FlowRunStatus } from "../../graphql/api-types.gen"; import type { User } from "../../graph/knowledge/system-types/user"; +import type { EntityUuid } from "@blockprotocol/type-system"; import type { DistributiveOmit } from "@local/advanced-types/distribute"; import type { FileStorageProvider } from "@local/hash-backend-utils/file-storage"; import type { GraphApi } from "@local/hash-graph-client"; @@ -95,6 +96,8 @@ export const handleInferEntitiesRequest = async ({ }, flowDefinitionId, flowInputs, + /* So that the run's entity id can be worked out from the request, as the browser plugin does. */ + flowRunEntityUuid: requestUuid as EntityUuid, userAuthentication: { actorId: user.accountId }, webId, }, diff --git a/apps/hash-api/src/graphql/resolvers/flows/cancel-flow.ts b/apps/hash-api/src/graphql/resolvers/flows/cancel-flow.ts index c1792600628..e76e02d96e7 100644 --- a/apps/hash-api/src/graphql/resolvers/flows/cancel-flow.ts +++ b/apps/hash-api/src/graphql/resolvers/flows/cancel-flow.ts @@ -7,7 +7,6 @@ import * as Error from "../../error"; import type { MutationResetFlowArgs, ResolverFn } from "../../api-types.gen"; import type { LoggedInGraphQLContext } from "../../context"; -import type { EntityUuid } from "@blockprotocol/type-system"; export const cancelFlow: ResolverFn< Promise, @@ -16,19 +15,22 @@ export const cancelFlow: ResolverFn< MutationResetFlowArgs > = async ( _, - { flowUuid }, + { flowRunEntityId }, { authentication, dataSources, provenance, temporal, user }, ) => { const flow = await getFlowRunEntityById({ - flowRunId: flowUuid as EntityUuid, + flowRunEntityId, graphApiClient: dataSources.graphApi, userAuthentication: authentication, }); if (!flow) { - throw Error.notFound(`Flow with id ${flowUuid} not found`); + throw Error.notFound(`Flow with id ${flowRunEntityId} not found`); } + const workflowId = + flow.properties["https://hash.ai/@h/types/property-type/workflow-id/"]; + const userCanModify = await checkEntityPermission( { graphApi: dataSources.graphApi, provenance }, authentication, @@ -45,7 +47,7 @@ export const cancelFlow: ResolverFn< await temporal.workflowService.requestCancelWorkflowExecution({ namespace: temporalNamespace, workflowExecution: { - workflowId: flowUuid, + workflowId, }, reason: `Cancelled via GraphQL API by @${user.shortname}`, requestId: generateUuid(), diff --git a/apps/hash-api/src/graphql/resolvers/flows/get-flow-run-by-id.ts b/apps/hash-api/src/graphql/resolvers/flows/get-flow-run-by-id.ts index 0e647e13cbd..ff209e131b0 100644 --- a/apps/hash-api/src/graphql/resolvers/flows/get-flow-run-by-id.ts +++ b/apps/hash-api/src/graphql/resolvers/flows/get-flow-run-by-id.ts @@ -9,7 +9,6 @@ import type { ResolverFn, } from "../../api-types.gen"; import type { GraphQLContext } from "../../context"; -import type { EntityUuid } from "@blockprotocol/type-system"; import type { SparseFlowRun } from "@local/hash-isomorphic-utils/flows/types"; export const getFlowRunByIdResolver: ResolverFn< @@ -18,12 +17,12 @@ export const getFlowRunByIdResolver: ResolverFn< Pick, QueryGetFlowRunByIdArgs > = async (_parent, args, context, info) => { - const { flowRunId } = args; + const { flowRunEntityId } = args; const includeDetails = wereDetailedFieldsRequested(info); const flowRun = await getFlowRunById({ - flowRunId: flowRunId as EntityUuid, + flowRunEntityId, graphApiClient: context.dataSources.graphApi, includeDetails, storageProvider: context.dataSources.uploadProvider, @@ -32,7 +31,7 @@ export const getFlowRunByIdResolver: ResolverFn< }); if (!flowRun) { - throw Error.notFound(`Flow run with id ${flowRunId} not found`); + throw Error.notFound(`Flow run with id ${flowRunEntityId} not found`); } return flowRun; diff --git a/apps/hash-api/src/graphql/resolvers/flows/reset-flow.ts b/apps/hash-api/src/graphql/resolvers/flows/reset-flow.ts index f5811ba8003..02b4495bf19 100644 --- a/apps/hash-api/src/graphql/resolvers/flows/reset-flow.ts +++ b/apps/hash-api/src/graphql/resolvers/flows/reset-flow.ts @@ -10,7 +10,6 @@ import * as Error from "../../error"; import type { MutationResetFlowArgs, ResolverFn } from "../../api-types.gen"; import type { LoggedInGraphQLContext } from "../../context"; -import type { EntityUuid } from "@blockprotocol/type-system"; export const resetFlow: ResolverFn< Promise, @@ -19,19 +18,22 @@ export const resetFlow: ResolverFn< MutationResetFlowArgs > = async ( _, - { flowUuid, checkpointId, eventId }, + { flowRunEntityId, checkpointId, eventId }, { authentication, dataSources, provenance, temporal }, ) => { const flow = await getFlowRunEntityById({ - flowRunId: flowUuid as EntityUuid, + flowRunEntityId, graphApiClient: dataSources.graphApi, userAuthentication: authentication, }); if (!flow) { - throw Error.notFound(`Flow with id ${flowUuid} not found`); + throw Error.notFound(`Flow with id ${flowRunEntityId} not found`); } + const workflowId = + flow.properties["https://hash.ai/@h/types/property-type/workflow-id/"]; + const userCanModify = await checkEntityPermission( { graphApi: dataSources.graphApi, provenance }, authentication, @@ -48,7 +50,7 @@ export const resetFlow: ResolverFn< await temporal.workflowService.resetWorkflowExecution({ namespace: temporalNamespace, workflowExecution: { - workflowId: flowUuid, + workflowId, }, /** * If we don't set this, Temporal will replay signals sent after the cancellation point to the new workflow execution. 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 220226c836b..0e5580ac104 100644 --- a/apps/hash-api/src/graphql/resolvers/flows/start-flow.ts +++ b/apps/hash-api/src/graphql/resolvers/flows/start-flow.ts @@ -1,3 +1,4 @@ +import { entityIdFromComponents } from "@blockprotocol/type-system"; import { generateUuid } from "@local/hash-isomorphic-utils/generate-uuid"; import { @@ -9,14 +10,14 @@ import { assertClientFlowInputs } from "./shared/assert-client-flow-inputs"; import { getRunnableFlowDefinition } from "./shared/get-runnable-flow-definition"; import type { LoggedInGraphQLContext } from "../../context"; -import type { EntityUuid } from "@blockprotocol/type-system"; +import type { EntityId, EntityUuid } from "@blockprotocol/type-system"; import type { RunFlowWorkflowParams, RunFlowWorkflowResponse, } from "@local/hash-isomorphic-utils/flows/temporal-types"; export const startFlow: ResolverFn< - Promise, + Promise, Record, LoggedInGraphQLContext, MutationStartFlowArgs @@ -45,7 +46,7 @@ export const startFlow: ResolverFn< const params: RunFlowWorkflowParams = { ...(flowType === "ai" ? { dataSources } : {}), - flowRunId: workflowId, + flowRunEntityUuid: workflowId, flowInputs, flowDefinitionId, userAuthentication: { actorId: user.accountId }, @@ -68,5 +69,5 @@ export const startFlow: ResolverFn< }, }); - return workflowId; + return entityIdFromComponents(webId, workflowId); }; diff --git a/apps/hash-api/src/graphql/resolvers/flows/submit-external-input-response.ts b/apps/hash-api/src/graphql/resolvers/flows/submit-external-input-response.ts index 91ddb0ed5f6..d2ecbfe5afd 100644 --- a/apps/hash-api/src/graphql/resolvers/flows/submit-external-input-response.ts +++ b/apps/hash-api/src/graphql/resolvers/flows/submit-external-input-response.ts @@ -8,7 +8,6 @@ import type { ResolverFn, } from "../../api-types.gen"; import type { LoggedInGraphQLContext } from "../../context"; -import type { EntityUuid } from "@blockprotocol/type-system"; export const submitExternalInputResponse: ResolverFn< Promise, @@ -17,20 +16,23 @@ export const submitExternalInputResponse: ResolverFn< MutationSubmitExternalInputResponseArgs > = async ( _, - { response, flowUuid }, + { response, flowRunEntityId }, { authentication, dataSources, temporal }, ) => { const flow = await getFlowRunEntityById({ - flowRunId: flowUuid as EntityUuid, + flowRunEntityId, graphApiClient: dataSources.graphApi, userAuthentication: authentication, }); if (!flow) { - throw Error.notFound(`Flow with id ${flowUuid} not found`); + throw Error.notFound(`Flow with id ${flowRunEntityId} not found`); } - const handle = temporal.workflow.getHandle(flowUuid); + const workflowId = + flow.properties["https://hash.ai/@h/types/property-type/workflow-id/"]; + + const handle = temporal.workflow.getHandle(workflowId); await handle.signal(externalInputResponseSignal, response); 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 772aa942acb..ed1c80643b9 100644 --- a/apps/hash-frontend/src/graphql/queries/knowledge/flow.queries.ts +++ b/apps/hash-frontend/src/graphql/queries/knowledge/flow.queries.ts @@ -17,18 +17,22 @@ export const startFlowMutation = gql` `; export const resetFlowMutation = gql` - mutation resetFlow($flowUuid: ID!, $checkpointId: ID!, $eventId: Int!) { + mutation resetFlow( + $flowRunEntityId: EntityId! + $checkpointId: ID! + $eventId: Int! + ) { resetFlow( checkpointId: $checkpointId eventId: $eventId - flowUuid: $flowUuid + flowRunEntityId: $flowRunEntityId ) } `; export const cancelFlowMutation = gql` - mutation cancelFlow($flowUuid: ID!) { - cancelFlow(flowUuid: $flowUuid) + mutation cancelFlow($flowRunEntityId: EntityId!) { + cancelFlow(flowRunEntityId: $flowRunEntityId) } `; 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 51cd401c862..ecafb83d3f8 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 @@ -18,7 +18,7 @@ const FlowDefinitionPage: NextPageWithLayout = () => { - + 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 e7558c3831b..7e313a1d427 100644 --- a/apps/hash-frontend/src/pages/@/[shortname]/shared/flow-visualizer.tsx +++ b/apps/hash-frontend/src/pages/@/[shortname]/shared/flow-visualizer.tsx @@ -603,8 +603,8 @@ export const FlowVisualizer = () => { try { const { data } = await startFlow({ variables }); - const flowRunId = data?.startFlow; - if (!flowRunId) { + const flowRunEntityId = data?.startFlow; + if (!flowRunEntityId) { throw new Error("Failed to start flow"); } @@ -616,7 +616,7 @@ export const FlowVisualizer = () => { const { shortname } = getOwner({ webId: variables.webId }); - void push(generateWorkerRunPath({ shortname, flowRunId })); + void push(generateWorkerRunPath({ shortname, flowRunEntityId })); } finally { setStartFlowPending(false); } @@ -646,7 +646,7 @@ export const FlowVisualizer = () => { const flowDefinitionStateKey = `${selectedFlowDefinition.name} ?? "loading`; const flowRunStateKey = `${flowDefinitionStateKey}-${ - selectedFlowRun?.flowRunId ?? "definition" + selectedFlowRun?.flowRunEntityId ?? "definition" }`; const isRunnableFromHere = !unrunnableDefinitionIds.includes( @@ -707,7 +707,7 @@ export const FlowVisualizer = () => { {selectedFlowRun ? ( { void resetFlow({ variables: { - flowUuid: selectedFlowRun.flowRunId, + flowRunEntityId: selectedFlowRun.flowRunEntityId, checkpointId: log.checkpointId, eventId: log.eventId, }, diff --git a/apps/hash-frontend/src/pages/@/[shortname]/shared/flow-visualizer/flow-run-sidebar.tsx b/apps/hash-frontend/src/pages/@/[shortname]/shared/flow-visualizer/flow-run-sidebar.tsx index 45c8d74aefc..ef523fbe993 100644 --- a/apps/hash-frontend/src/pages/@/[shortname]/shared/flow-visualizer/flow-run-sidebar.tsx +++ b/apps/hash-frontend/src/pages/@/[shortname]/shared/flow-visualizer/flow-run-sidebar.tsx @@ -22,7 +22,7 @@ import { flowSectionBorderRadius } from "./shared/styles"; import type { FlowRun } from "../../../../../graphql/api-types.gen"; import type { EdgesAndNodes } from "./shared/types"; -import type { EntityUuid } from "@blockprotocol/type-system"; +import type { EntityId, EntityUuid } from "@blockprotocol/type-system"; import type { PropsWithChildren } from "react"; const SidebarSection = ({ children }: PropsWithChildren) => ( @@ -41,7 +41,7 @@ const SidebarSection = ({ children }: PropsWithChildren) => ( type FlowRunSidebarProps = { flowDefinitionId: EntityUuid; - flowRunId: EntityUuid; + flowRunEntityId: EntityId; flowScheduleId: EntityUuid | null; graph: EdgesAndNodes; name: FlowRun["name"]; @@ -50,7 +50,7 @@ type FlowRunSidebarProps = { export const FlowRunSidebar = ({ flowDefinitionId, - flowRunId, + flowRunEntityId, flowScheduleId, graph, name, @@ -59,14 +59,14 @@ export const FlowRunSidebar = ({ const { selectedFlowRun } = useFlowRunsContext(); const { isUsageAvailable, usageByFlowRun } = useFlowRunsUsage({ - flowRunIds: [flowRunId], + flowRunEntityIds: [flowRunEntityId], pollInterval: selectedFlowRun?.closedAt ? 0 : 5_000, }); const [showUsageBreakdown, setShowUsageBreakdown] = useState(false); const [showResearchPrompt, setShowResearchPrompt] = useState(false); - const usage = usageByFlowRun[flowRunId]; + const usage = usageByFlowRun[flowRunEntityId]; const nameParts = useMemo<{ text: string; url?: boolean }[]>(() => { const parts = name.split(/( )/g); diff --git a/apps/hash-frontend/src/pages/@/[shortname]/shared/flow-visualizer/outputs/claims-output.tsx b/apps/hash-frontend/src/pages/@/[shortname]/shared/flow-visualizer/outputs/claims-output.tsx index 11faa671a0a..5d087c1d419 100644 --- a/apps/hash-frontend/src/pages/@/[shortname]/shared/flow-visualizer/outputs/claims-output.tsx +++ b/apps/hash-frontend/src/pages/@/[shortname]/shared/flow-visualizer/outputs/claims-output.tsx @@ -2,7 +2,6 @@ import { useQuery } from "@apollo/client"; import { memo, useLayoutEffect, useMemo, useRef, useState } from "react"; import { getRoots } from "@blockprotocol/graph/stdlib"; -import { entityIdFromComponents } from "@blockprotocol/type-system"; import { deserializeQueryEntitySubgraphResponse } from "@local/hash-graph-sdk/entity"; import { currentTimeInstantTemporalAxes } from "@local/hash-isomorphic-utils/graph-queries"; import { systemEntityTypes } from "@local/hash-isomorphic-utils/ontology-type-ids"; @@ -51,12 +50,7 @@ export const ClaimsOutput = memo(({ proposedEntities }: ClaimsTableProps) => { path: ["editionProvenance", "origin", "id"], }, { - parameter: selectedFlowRun - ? entityIdFromComponents( - selectedFlowRun.webId, - selectedFlowRun.flowRunId, - ) - : "never", + parameter: selectedFlowRun?.flowRunEntityId ?? "never", }, ], }, diff --git a/apps/hash-frontend/src/pages/@/[shortname]/shared/flow-visualizer/shared/question-modal.tsx b/apps/hash-frontend/src/pages/@/[shortname]/shared/flow-visualizer/shared/question-modal.tsx index bc82f4c67b3..92b77ddfefb 100644 --- a/apps/hash-frontend/src/pages/@/[shortname]/shared/flow-visualizer/shared/question-modal.tsx +++ b/apps/hash-frontend/src/pages/@/[shortname]/shared/flow-visualizer/shared/question-modal.tsx @@ -69,7 +69,7 @@ export const QuestionModal = ({ ); } - const { selectedFlowRunId } = useFlowRunsContext(); + const { selectedFlowRunEntityId } = useFlowRunsContext(); const [submitExternalInputResponse] = useMutation< SubmitExternalInputResponseMutation, @@ -87,13 +87,13 @@ export const QuestionModal = ({ ); const submitAnswers = async () => { - if (!allQuestionsAnswered || !selectedFlowRunId) { + if (!allQuestionsAnswered || !selectedFlowRunEntityId) { return; } await submitExternalInputResponse({ variables: { - flowUuid: selectedFlowRunId, + flowRunEntityId: selectedFlowRunEntityId, response: { requestId: inputRequest.requestId, type: "human-input", 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 bf1ad6bf4da..8f2dff42218 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 @@ -35,7 +35,6 @@ import type { CancelFlowMutationVariables, FlowRun, } from "../../../../../graphql/api-types.gen"; -import type { EntityUuid } from "@blockprotocol/type-system"; import type { SvgIconProps, SxProps, Theme } from "@mui/material"; import type { FunctionComponent } from "react"; @@ -170,7 +169,8 @@ export const Topbar = ({ const { flowDefinitions, selectedFlowDefinitionId } = useFlowDefinitionsContext(); - const { flowRuns, selectedFlowRunId, selectedFlowRun } = useFlowRunsContext(); + const { flowRuns, selectedFlowRunEntityId, selectedFlowRun } = + useFlowRunsContext(); const [cancelFlow] = useMutation< CancelFlowMutation, @@ -178,11 +178,13 @@ export const Topbar = ({ >(cancelFlowMutation); const onCancelFlowClicked = useCallback(async () => { - if (selectedFlowRunId) { + if (selectedFlowRunEntityId) { setCancelling(true); try { - await cancelFlow({ variables: { flowUuid: selectedFlowRunId } }); + await cancelFlow({ + variables: { flowRunEntityId: selectedFlowRunEntityId }, + }); } catch { /** * We don't nee to worry about the success case because the cancel flow button will disappear @@ -190,7 +192,7 @@ export const Topbar = ({ setCancelling(false); } } - }, [cancelFlow, selectedFlowRunId]); + }, [cancelFlow, selectedFlowRunEntityId]); const onRunFlowClicked = useCallback(() => { handleRunFlowClicked(); @@ -290,7 +292,7 @@ export const Topbar = ({ ) : (