Skip to main content
Glama
FuncTest.vue17.6 kB
<template> <ScrollArea> <template #top> <div class="flex flex-row gap-xs items-center p-xs justify-between border-y border-neutral-200 dark:border-neutral-600" > <div class="font-bold text-xl text-center overflow-hidden text-ellipsis flex-grow break-words" > Test {{ funcStore.selectedFuncSummary?.kind + " " || "" }}Function <span class="italic" >"{{ funcStore.selectedFuncSummary?.displayName || funcStore.selectedFuncSummary?.name }}"</span > </div> <StatusIndicatorIcon v-if="runningTest" :status="testStatus" type="funcTest" /> </div> <div class="flex flex-col border dark:border-neutral-600 p-xs m-xs rounded" > <div class="pb-xs"> Select the <span v-if="assetStore.selectedSchemaVariant" class="italic font-bold" > {{ assetStore.selectedSchemaVariant.schemaName }} </span> component to use as the input for your test: </div> <FuncTestSelector v-if="selectedAsset" ref="funcTestSelectorRef" :isAttributeFunc=" funcStore.selectedFuncSummary?.kind === FuncKind.Attribute " :readyToTest="readyToTest" :schemaVariantId="selectedAsset?.schemaVariantId" :testStatus="testStatus" @loadInput="loadInput" @startTest="startTest" /> </div> <!-- DRY RUN SECTION --> <div v-if="dryRunConfig === 'choose'" class="border dark:border-neutral-600 p-xs m-xs rounded" > <div class="pb-xs"> Do you want the results of this test to be applied to the component? </div> <VormInput v-model="dryRun" class="flex-grow justify-center" disabled inlineLabel label="Dry Run" placeholder="no attribute selected" type="checkbox" /> </div> <div v-else class="py-xs px-sm rounded text-center italic"> <span v-if="dryRunConfig === 'dry'" class="text-neutral-500 dark:text-neutral-400" > The results of this test will not be applied to the component. </span> <span v-else class="font-bold"> WARNING: The results of this test will be applied to the component! </span> </div> <!-- END DRY RUN SECTION --> </template> <TabGroup v-if="enableTestTabGroup" ref="funcTestTabsRef" growTabsToFillWidth marginTop="2xs" startSelectedTabSlug="input" variant="secondary" > <TabGroupItem label="Input" slug="input"> <CodeViewer :code="testInputCode" :title="`Input: ${testComponentDisplayName}`" /> </TabGroupItem> <TabGroupItem label="Execution Logs" slug="logs"> <div class="w-full h-full overflow-hidden flex flex-col absolute"> <template v-if="rawTestLogs.length > 0"> <!-- TODO(WENDY) - a chip here to show output info --> <div class="border dark:border-neutral-600 dark:bg-shade-100 bg-neutral-100 rounded-xl m-xs p-xs flex flex-row items-center gap-xs flex-none" > <StatusIndicatorIcon :status="testLogs.status" size="2xl" type="funcTest" /> <div class="text-xl font-bold capitalize"> Status: {{ testLogs.status }} </div> <div class="flex-grow text-right"> <a class="text-action-400 font-bold text-sm hover:underline cursor-pointer" @click="additionalOutputInfoModalRef.open" > Additional Output Info </a> <Modal ref="additionalOutputInfoModalRef" :title="`Output Information For Test On ${testComponentDisplayName}`" > <div class="w-full max-h-[50vh] relative overflow-auto"> <CodeViewer :code="testLogs.output" :title="`Output Info: ${testComponentDisplayName}`" /> </div> </Modal> </div> </div> <div v-if="testLogs.stdout" class="relative flex-shrink overflow-auto basis-full" > <CodeViewer :code="testLogs.stdout" :title="`stdout: ${testComponentDisplayName}`" showTitle /> </div> <div v-else class="border dark:border-neutral-600 rounded p-xs m-sm text-center text-neutral-500 dark:text-neutral-400 italic" > No stdout logs to show. </div> <div v-if="testLogs.stderr" class="relative flex-shrink overflow-auto basis-full" > <CodeViewer :code="testLogs.stderr" :title="`stderr: ${testComponentDisplayName}`" showTitle /> </div> <div v-else class="border dark:border-neutral-600 rounded p-xs m-sm text-center text-neutral-500 dark:text-neutral-400 italic" > No stderr logs to show. </div> </template> <div v-else-if="runningTest" class="w-full p-md text-center text-neutral-500 dark:text-neutral-400 flex flex-col items-center" > <template v-if="testStatus === 'running'"> <div class="pb-sm"> Awaiting logs for the currently running test... </div> <StatusIndicatorIcon :status="testStatus" size="2xl" tone="neutral" type="funcTest" /> </template> <template v-else>No logs available for this test.</template> </div> <div v-else class="w-full p-md text-center text-neutral-500 dark:text-neutral-400" > Run a test to see the execution logs. </div> </div> </TabGroupItem> <TabGroupItem label="Output" slug="output"> <CodeViewer v-if="testOutputCode" :code="testOutputCode" :title="`Output: ${testComponentDisplayName}`" /> <div v-else-if="runningTest" class="w-full p-md text-center text-neutral-500 dark:text-neutral-400 flex flex-col items-center" > <template v-if="testStatus === 'running'"> <div class="pb-sm"> Awaiting output for the currently running test... </div> <StatusIndicatorIcon :status="testStatus" size="2xl" tone="neutral" type="funcTest" /> </template> <template v-else>No output available for this test.</template> </div> <div v-else class="w-full p-md text-center text-neutral-500 dark:text-neutral-400" > Run a test to see the output. </div> </TabGroupItem> </TabGroup> </ScrollArea> </template> <script lang="ts" setup> import * as _ from "lodash-es"; import { VormInput, ScrollArea, TabGroupItem, TabGroup, Modal, } from "@si/vue-lib/design-system"; import { computed, ref } from "vue"; import { useFuncStore } from "@/store/func/funcs.store"; import { useAssetStore } from "@/store/asset.store"; import { useComponentsStore } from "@/store/components.store"; import { FuncKind, LeafInputLocation } from "@/api/sdf/dal/func"; import { useFuncRunsStore } from "@/store/func_runs.store"; import FuncTestSelector from "./FuncTestSelector.vue"; import CodeViewer from "../CodeViewer.vue"; import StatusIndicatorIcon, { Status } from "../StatusIndicatorIcon.vue"; export type TestStatus = "running" | "success" | "failure"; const componentsStore = useComponentsStore(); const funcStore = useFuncStore(); const assetStore = useAssetStore(); const funcRunsStore = useFuncRunsStore(); const additionalOutputInfoModalRef = ref(); const funcTestSelectorRef = ref<InstanceType<typeof FuncTestSelector>>(); const selectedAsset = computed(() => assetStore.selectedSchemaVariant); const enableTestTabGroup = computed((): boolean => { if (funcStore.selectedFuncSummary?.kind === FuncKind.Attribute) { if ( funcTestSelectorRef.value?.selectedComponentId && funcTestSelectorRef.value?.selectedOutputLocationId ) { return true; } return false; } if (funcTestSelectorRef.value?.selectedComponentId) { return true; } return false; }); const funcTestTabsRef = ref(); const dryRun = ref(true); const testInputCode = ref(""); const testInputProperties = ref<Record<string, unknown> | null>(); const testOutputCode = ref(""); const testOutput = ref<unknown>(null); const readyToTest = ref(false); const runningTest = ref(false); const dryRunConfig = computed(() => { // TODO(Wendy) - which function variants allow for a choice of dry run? which are always dry and which are always wet? // Note(Paulo): We only support dry run when testing functions if (funcStore.selectedFuncSummary?.kind === FuncKind.Attribute) { return "dry"; } else { // return "wet"; return "dry"; } }); const testComponentDisplayName = computed(() => { if (funcTestSelectorRef.value?.selectedComponentId) { return componentsStore.allComponentsById[ funcTestSelectorRef.value.selectedComponentId ]?.def.displayName; } else return "ERROR"; }); const testStatus = computed((): TestStatus => { const status = funcStore.getRequestStatus("TEST_EXECUTE").value; if (status.isPending) return "running"; else if (status.isSuccess) return "success"; else return "failure"; }); const rawTestLogs = ref< { stream: string; level: string; message: string; timestamp: string }[] >([]); const testLogs = computed(() => { const logs = { stdout: "", stderr: "", output: "", status: "running" as Status, }; if (rawTestLogs.value && rawTestLogs.value.length > 0) { rawTestLogs.value.forEach((log) => { if (log.stream === "stdout") { if (logs.stdout !== "") logs.stdout += "\n"; logs.stdout += log.message; } else if (log.stream === "stderr") { if (logs.stderr !== "") logs.stderr += "\n"; logs.stderr += log.message; } else if ( log.stream === "output" && log.message.slice(0, 8) === "Output: " ) { logs.output = log.message.slice(8); const outputJSON = JSON.parse(logs.output); logs.status = (outputJSON.status as Status) ?? "unknown"; } }); if (logs.status === "running" && testOutput.value) { logs.status = (testOutput.value as { result: Status }).result ?? "running"; } } return logs; }); const resetTestData = () => { testInputCode.value = ""; testOutputCode.value = ""; testOutput.value = null; rawTestLogs.value = []; readyToTest.value = false; runningTest.value = false; }; const loadInput = async () => { await prepareTest(); if (funcTestTabsRef.value && funcTestTabsRef.value.tabExists("input")) { funcTestTabsRef.value.selectTab("input"); } }; const prepareTest = async () => { if ( !funcStore.selectedFuncId || !funcTestSelectorRef.value?.selectedComponentId ) return; resetTestData(); if (funcStore.selectedFuncSummary?.kind === FuncKind.Attribute) { if (!funcTestSelectorRef.value.selectedOutputLocationId) { throw new Error( "cannot prepare test for attribute func without a selected output location", ); } const propId = funcTestSelectorRef.value.selectedOutputLocationId.startsWith("p_") ? funcTestSelectorRef.value.selectedOutputLocationId.replace("p_", "") : undefined; const outputSocketId = funcTestSelectorRef.value.selectedOutputLocationId.startsWith("s_") ? funcTestSelectorRef.value.selectedOutputLocationId.replace("s_", "") : undefined; const res = await funcStore.FETCH_PROTOTYPE_ARGUMENTS( propId, outputSocketId, ); if (!res.result.success) { throw new Error( "could not fetch prototype arguments needed for preparing test", ); } const preparedArguments = res.result.data.preparedArguments; let properties: Record<string, unknown> = {}; properties = preparedArguments; testInputCode.value = JSON.stringify(properties, null, 2); testInputProperties.value = properties; } else if ( funcStore.selectedFuncSummary?.kind === FuncKind.CodeGeneration || funcStore.selectedFuncSummary?.kind === FuncKind.Qualification ) { const res = await componentsStore.FETCH_COMPONENT_JSON( funcTestSelectorRef.value.selectedComponentId, ); if (!res.result.success) { throw new Error( "could not fetch component json needed for preparing test", ); } const json = res.result.data.json; const props: Record<string, unknown> | null = json as Record< string, unknown >; const toSnakeCase = (inputString: string) => { return inputString .split("") .map((character) => { if (character === character.toUpperCase()) { return `_${character.toLowerCase()}`; } else { return character; } }) .join(""); }; const properties: Record<string, unknown> = {}; let selectedInputs = [] as LeafInputLocation[]; if (funcStore.selectedFuncSummary?.kind === FuncKind.CodeGeneration) selectedInputs = funcStore.codegenBindings[funcStore.selectedFuncId]?.find((b) => { const schemaVariantId = componentsStore.allComponentsById[ funcTestSelectorRef.value?.selectedComponentId || "" ]?.def.schemaVariantId; if (schemaVariantId) return b.schemaVariantId === schemaVariantId; return false; })?.inputs || []; if (funcStore.selectedFuncSummary?.kind === FuncKind.Qualification) selectedInputs = funcStore.qualificationBindings[funcStore.selectedFuncId]?.find((b) => { const schemaVariantId = componentsStore.allComponentsById[ funcTestSelectorRef.value?.selectedComponentId || "" ]?.def.schemaVariantId; if (schemaVariantId) return b.schemaVariantId === schemaVariantId; return false; })?.inputs || []; if (props) for (const input of selectedInputs) { const key = toSnakeCase(`${input}`); properties[key] = props[key]; } testInputCode.value = JSON.stringify(properties, null, 2); testInputProperties.value = properties; } else if (funcStore.selectedFuncSummary?.kind === FuncKind.Management) { const res = await componentsStore.FETCH_COMPONENT_JSON( funcTestSelectorRef.value.selectedComponentId, ); if (!res.result.success) { throw new Error( "could not fetch component json needed for preparing test", ); } const json = res.result.data.json; const props: Record<string, unknown> | null = json as Record< string, unknown >; const geometry = { x: 0, y: 0, width: 500, height: 500 }; // TODO recursive toSnakeCase all props const thisComponent = { this_component: { properties: props, geometry, }, }; testInputCode.value = JSON.stringify(thisComponent, null, 2); testInputProperties.value = thisComponent; } else { // This should not be possible since we should only prepare tests for valid func kinds. return; } readyToTest.value = true; }; const startTest = async () => { if ( !funcStore.selectedFuncCode || !funcTestSelectorRef.value?.selectedComponentId || !readyToTest.value ) return; prepareTest(); readyToTest.value = false; // Run the test! runningTest.value = true; rawTestLogs.value = []; funcTestTabsRef.value.selectTab("logs"); const args = testInputProperties.value; const response = await funcStore.TEST_EXECUTE({ funcId: funcStore.selectedFuncCode.funcId, args, code: funcStore.selectedFuncCode.code, componentId: funcTestSelectorRef.value?.selectedComponentId, }); readyToTest.value = true; // TODO: @brit - currently test does not create a changeset // so this will work fine // but once we force a change set, this will need to change if (response.result.success) { const funcRunId = response.result.data.funcRunId; await funcRunsStore.GET_FUNC_RUN(funcRunId); const funcRun = funcRunsStore.funcRuns[funcRunId]; if (funcRun) { testOutput.value = funcRun.resultValue; testOutputCode.value = JSON.stringify(funcRun.resultValue, null, 2); if (funcRun.logs) { rawTestLogs.value = funcRun.logs.logs; } } else { testOutput.value = null; testOutputCode.value = "ERROR: Test Execution Request Succeeded, But Func Run Not Found"; } } else { testOutput.value = null; testOutputCode.value = "ERROR: Test Failed To Run"; } }; </script>

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/systeminit/si'

If you have feedback or need assistance with the MCP directory API, please join our Discord server