From 8a82d0a3841c6f477fc5bbb2acbd29df72666925 Mon Sep 17 00:00:00 2001 From: KarlaCarvajal Date: Wed, 18 Dec 2024 14:54:44 -0600 Subject: [PATCH 1/2] build(sdk-dotnet): add dotnet job to release pipeline (#1214) * Add release job to publish sdk-dotnet package in nuget.org * Add test dotnet-sdk job Co-authored-by: Saul --- .github/workflows/release.yml | 26 +++++++++++++++++++ .github/workflows/tests.yml | 15 +++++++++++ .../LittleHorse.Sdk/LittleHorse.Sdk.csproj | 4 +-- 3 files changed, 42 insertions(+), 3 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 5edd5ac1c..ccc88ff82 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -146,6 +146,31 @@ jobs: NODE_AUTH_TOKEN: ${{ secrets.NPM_PUBLISH_TOKEN }} run: npm publish --access public + sdk-dotnet: + runs-on: ubuntu-latest + needs: + - publish-docker + - prepare + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Setup Dotnet + uses: actions/setup-dotnet@v4 + with: + dotnet-version: '6' + - name: Bump version + env: + TAG: ${{ needs.prepare.outputs.tag }} + run: sed -i "s/.*<\/PackageVersion>/${TAG}<\/PackageVersion>/g" sdk-dotnet/LittleHorse.Sdk/LittleHorse.Sdk.csproj + - name: Build Project + run: dotnet build sdk-dotnet/LittleHorse.Sdk/LittleHorse.Sdk.csproj + - name: Create Package + run: dotnet pack --configuration Release sdk-dotnet/LittleHorse.Sdk/LittleHorse.Sdk.csproj + - name: Publish Package to nuget.org + run: dotnet nuget push sdk-dotnet/LittleHorse.Sdk/bin/Release/*.nupkg -k $NUGET_AUTH_TOKEN -s https://api.nuget.org/v3/index.json + env: + NUGET_AUTH_TOKEN: ${{ secrets.NUGET_TOKEN }} + lhctl: runs-on: ubuntu-latest steps: @@ -174,6 +199,7 @@ jobs: - sdk-java - sdk-python - sdk-js + - sdk-dotnet runs-on: ubuntu-latest steps: - name: Checkout diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index e523db727..7e834f9dd 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -133,3 +133,18 @@ jobs: run: | npm ci npm run test + + tests-sdk-dotnet: + if: ${{ !contains(github.event.head_commit.message, '[skip main]') }} + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Setup Dotnet + uses: actions/setup-dotnet@v4 + with: + dotnet-version: '6' + - name: Build Project + run: dotnet build sdk-dotnet/LittleHorse.Sdk/LittleHorse.Sdk.csproj + - name: Test Dotnet + run: dotnet test sdk-dotnet/LittleHorse.Sdk.Tests \ No newline at end of file diff --git a/sdk-dotnet/LittleHorse.Sdk/LittleHorse.Sdk.csproj b/sdk-dotnet/LittleHorse.Sdk/LittleHorse.Sdk.csproj index 7b89de2a0..1f3e09655 100644 --- a/sdk-dotnet/LittleHorse.Sdk/LittleHorse.Sdk.csproj +++ b/sdk-dotnet/LittleHorse.Sdk/LittleHorse.Sdk.csproj @@ -4,9 +4,7 @@ net6.0 enable enable - 0.5.8 - 0.5.8 - 0.5.8-alpha + 0.0.0 LittleHorse Enterprises LLC LittleHorse Enterprises LLC LittleHorseSDK From 3807fad6e0e491d02e8b77c75a304daebee9f0b4 Mon Sep 17 00:00:00 2001 From: "Bryson G." <114206517+bryson-g@users.noreply.github.com> Date: Wed, 18 Dec 2024 20:09:34 -0800 Subject: [PATCH 2/2] feat(dashboard): display scheduledWfRuns (#1202) --- dashboard/package-lock.json | 53 ++++++++ dashboard/package.json | 2 + .../[...props]/actions/ScheduleWfRun.ts | 19 --- .../[...props]/actions/getScheduleWfSpec.ts | 24 +++- .../[...props]/components/ScheduledWfRuns.tsx | 124 ++++++++++++++++++ .../wfSpec/[...props]/components/WfRuns.tsx | 15 +-- .../[...props]/components/WfRunsHeader.tsx | 2 - .../wfSpec/[...props]/components/WfSpec.tsx | 20 ++- .../(diagram)/wfSpec/[...props]/page.tsx | 3 +- .../[tenantId]/components/LinkWithTenant.tsx | 9 +- .../[tenantId]/components/SelectionLink.tsx | 32 +++++ .../components/tables/WfSpecTable.tsx | 11 +- dashboard/src/app/constants.ts | 14 ++ dashboard/src/app/utils/getCronTimeWindow.ts | 18 +++ dashboard/src/components/ui/tabs.tsx | 55 ++++++++ 15 files changed, 348 insertions(+), 53 deletions(-) delete mode 100644 dashboard/src/app/[tenantId]/(diagram)/wfSpec/[...props]/actions/ScheduleWfRun.ts create mode 100644 dashboard/src/app/[tenantId]/(diagram)/wfSpec/[...props]/components/ScheduledWfRuns.tsx create mode 100644 dashboard/src/app/[tenantId]/components/SelectionLink.tsx create mode 100644 dashboard/src/app/utils/getCronTimeWindow.ts create mode 100644 dashboard/src/components/ui/tabs.tsx diff --git a/dashboard/package-lock.json b/dashboard/package-lock.json index 9ed39d2d5..e760647f4 100644 --- a/dashboard/package-lock.json +++ b/dashboard/package-lock.json @@ -18,10 +18,12 @@ "@radix-ui/react-separator": "^1.1.0", "@radix-ui/react-slot": "^1.1.0", "@radix-ui/react-switch": "^1.1.1", + "@radix-ui/react-tabs": "^1.1.1", "@tanstack/react-query": "^5.37.1", "class-variance-authority": "^0.7.0", "clsx": "^2.1.1", "cmdk": "^1.0.0", + "cron-parser": "^4.9.0", "dagre": "^0.8.5", "littlehorse-client": "file://../sdk-js", "lucide-react": "^0.379.0", @@ -3544,6 +3546,36 @@ } } }, + "node_modules/@radix-ui/react-tabs": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-tabs/-/react-tabs-1.1.1.tgz", + "integrity": "sha512-3GBUDmP2DvzmtYLMsHmpA1GtR46ZDZ+OreXM/N+kkQJOPIgytFWWTfDQmBQKBvaFS0Vno0FktdbVzN28KGrMdw==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.0", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-direction": "1.1.0", + "@radix-ui/react-id": "1.1.0", + "@radix-ui/react-presence": "1.1.1", + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-roving-focus": "1.1.0", + "@radix-ui/react-use-controllable-state": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-use-callback-ref": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.0.tgz", @@ -9485,6 +9517,18 @@ "devOptional": true, "license": "MIT" }, + "node_modules/cron-parser": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/cron-parser/-/cron-parser-4.9.0.tgz", + "integrity": "sha512-p0SaNjrHOnQeR8/VnfGbmg9te2kfyYSQ7Sc/j/6DtPL3JQvKxmjO9TSjNFpujqV3vEYYBvNNvXSxzyksBWAx1Q==", + "license": "MIT", + "dependencies": { + "luxon": "^3.2.1" + }, + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -17323,6 +17367,15 @@ "react": "^16.5.1 || ^17.0.0 || ^18.0.0" } }, + "node_modules/luxon": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.5.0.tgz", + "integrity": "sha512-rh+Zjr6DNfUYR3bPwJEnuwDdqMbxZW7LOQfUN4B54+Cl+0o5zaU9RJ6bcidfDtC1cWCZXQ+nvX8bf6bAji37QQ==", + "license": "MIT", + "engines": { + "node": ">=12" + } + }, "node_modules/make-dir": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", diff --git a/dashboard/package.json b/dashboard/package.json index 8ccf848b5..0a989b96b 100644 --- a/dashboard/package.json +++ b/dashboard/package.json @@ -21,10 +21,12 @@ "@radix-ui/react-separator": "^1.1.0", "@radix-ui/react-slot": "^1.1.0", "@radix-ui/react-switch": "^1.1.1", + "@radix-ui/react-tabs": "^1.1.1", "@tanstack/react-query": "^5.37.1", "class-variance-authority": "^0.7.0", "clsx": "^2.1.1", "cmdk": "^1.0.0", + "cron-parser": "^4.9.0", "dagre": "^0.8.5", "littlehorse-client": "file://../sdk-js", "lucide-react": "^0.379.0", diff --git a/dashboard/src/app/[tenantId]/(diagram)/wfSpec/[...props]/actions/ScheduleWfRun.ts b/dashboard/src/app/[tenantId]/(diagram)/wfSpec/[...props]/actions/ScheduleWfRun.ts deleted file mode 100644 index cd2def909..000000000 --- a/dashboard/src/app/[tenantId]/(diagram)/wfSpec/[...props]/actions/ScheduleWfRun.ts +++ /dev/null @@ -1,19 +0,0 @@ -'use server' -import { lhClient } from '@/app/lhClient' -import { WithTenant } from '@/types' -import { ScheduleWfRequest } from 'littlehorse-client/proto' -import { ScheduledWfRun } from '../../../../../../../../sdk-js/dist/proto/scheduled_wf_run' - -export const ScheduleWfRun = async ({ - wfSpecName, - tenantId, - majorVersion, - revision, - parentWfRunId, - id, - variables, - cronExpression, -}: ScheduleWfRequest & WithTenant): Promise => { - const client = await lhClient({ tenantId }) - return client.scheduleWf({ wfSpecName, majorVersion, revision, parentWfRunId, id, variables, cronExpression }) -} diff --git a/dashboard/src/app/[tenantId]/(diagram)/wfSpec/[...props]/actions/getScheduleWfSpec.ts b/dashboard/src/app/[tenantId]/(diagram)/wfSpec/[...props]/actions/getScheduleWfSpec.ts index 347204681..aaceb50f3 100644 --- a/dashboard/src/app/[tenantId]/(diagram)/wfSpec/[...props]/actions/getScheduleWfSpec.ts +++ b/dashboard/src/app/[tenantId]/(diagram)/wfSpec/[...props]/actions/getScheduleWfSpec.ts @@ -1,20 +1,30 @@ 'use server' + import { lhClient } from '@/app/lhClient' import { WithTenant } from '@/types' -import { ScheduledWfRunIdList } from 'littlehorse-client/proto' +import { ScheduledWfRun } from 'littlehorse-client/proto' type GetWfSpecProps = { name: string version: string } & WithTenant -export const getScheduleWfSpec = async ({ name, version, tenantId }: GetWfSpecProps): Promise => { +export const getScheduleWfSpec = async ({ name, version, tenantId }: GetWfSpecProps): Promise => { const client = await lhClient({ tenantId }) const [majorVersion, revision] = version.split('.') - return client.searchScheduledWfRun({ - wfSpecName: name, - majorVersion: parseInt(majorVersion) || 0, - revision: parseInt(revision) | 0, - }) + + return Promise.all( + ( + await client.searchScheduledWfRun({ + wfSpecName: name, + majorVersion: parseInt(majorVersion) || 0, + revision: parseInt(revision) || 0, + }) + ).results.map(async scheduledWfRun => { + return await client.getScheduledWfRun({ + id: scheduledWfRun.id, + }) + }) + ) } diff --git a/dashboard/src/app/[tenantId]/(diagram)/wfSpec/[...props]/components/ScheduledWfRuns.tsx b/dashboard/src/app/[tenantId]/(diagram)/wfSpec/[...props]/components/ScheduledWfRuns.tsx new file mode 100644 index 000000000..17c3355d4 --- /dev/null +++ b/dashboard/src/app/[tenantId]/(diagram)/wfSpec/[...props]/components/ScheduledWfRuns.tsx @@ -0,0 +1,124 @@ +'use client' + +import { ScheduledWfRunIdList, WfSpec } from 'littlehorse-client/proto' +import { getScheduleWfSpec } from '../actions/getScheduleWfSpec' +import { SelectionLink } from '@/app/[tenantId]/components/SelectionLink' +import { ScheduledWfRun } from 'littlehorse-client/proto' +import { FUTURE_TIME_RANGES, SEARCH_DEFAULT_LIMIT, TimeRange } from '@/app/constants' +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select' +import { ClockIcon } from 'lucide-react' +import { useEffect, useState, useMemo } from 'react' +import { getCronTimeWindow } from '@/app/utils/getCronTimeWindow' +import { parseExpression } from 'cron-parser' +import { utcToLocalDateTime } from '@/app/utils' +import { SearchVariableDialog } from './SearchVariableDialog' +import { SearchFooter } from '@/app/[tenantId]/components/SearchFooter' +import { useParams, useSearchParams } from 'next/navigation' +import { RefreshCwIcon } from 'lucide-react' + +export const ScheduledWfRuns = (spec: WfSpec) => { + const [currentWindow, setWindow] = useState(-1) + const [isLoading, setIsLoading] = useState(true) + const [error, setError] = useState(null) + const [scheduledWfRuns, setScheduledWfRuns] = useState([]) + const tenantId = useParams().tenantId as string + + useEffect(() => { + let isMounted = true + + const fetchScheduledWfRuns = async () => { + try { + setIsLoading(true) + setError(null) + const runs = await getScheduleWfSpec({ + name: spec.id!.name, + version: spec.id!.majorVersion + '.' + spec.id!.revision, + tenantId: tenantId, + }) + if (isMounted) { + setScheduledWfRuns(runs) + } + } catch (err) { + if (isMounted) { + setError(err instanceof Error ? err : new Error('Failed to fetch scheduled runs')) + } + } finally { + if (isMounted) { + setIsLoading(false) + } + } + } + + fetchScheduledWfRuns() + + return () => { + isMounted = false + } + }, [spec.id, tenantId]) + + const filteredScheduledWfRuns = useMemo( + () => + scheduledWfRuns + .filter(scheduledWfRun => { + if (currentWindow === -1) return true + const timeWindow = getCronTimeWindow(scheduledWfRun.cronExpression) + return timeWindow && timeWindow <= currentWindow + }) + .sort((a, b) => { + const timeA = parseExpression(a.cronExpression).next().toDate().getTime() + const timeB = parseExpression(b.cronExpression).next().toDate().getTime() + return timeA - timeB + }), + [currentWindow, scheduledWfRuns] + ) + + if (isLoading) { + return ( +
+ +
+ ) + } + + if (error) { + return ( +
+

Error loading scheduled runs

+

{error.message}

+
+ ) + } + + return ( +
+
+ +
+
+ {filteredScheduledWfRuns.map(scheduledWfRun => ( + +

{scheduledWfRun.id?.id}

+
+ +

{utcToLocalDateTime(parseExpression(scheduledWfRun.cronExpression).next().toDate().toISOString())}

+
+
+ ))} +
+
+ ) +} diff --git a/dashboard/src/app/[tenantId]/(diagram)/wfSpec/[...props]/components/WfRuns.tsx b/dashboard/src/app/[tenantId]/(diagram)/wfSpec/[...props]/components/WfRuns.tsx index 2099bd7b0..b20605f55 100644 --- a/dashboard/src/app/[tenantId]/(diagram)/wfSpec/[...props]/components/WfRuns.tsx +++ b/dashboard/src/app/[tenantId]/(diagram)/wfSpec/[...props]/components/WfRuns.tsx @@ -1,5 +1,4 @@ 'use client' -import LinkWithTenant from '@/app/[tenantId]/components/LinkWithTenant' import { SearchFooter } from '@/app/[tenantId]/components/SearchFooter' import { SEARCH_DEFAULT_LIMIT, TIME_RANGES, TimeRange } from '@/app/constants' import { concatWfRunIds } from '@/app/utils' @@ -10,6 +9,7 @@ import { useParams, useSearchParams } from 'next/navigation' import { FC, Fragment, useMemo, useState } from 'react' import { PaginatedWfRunIdList, searchWfRun } from '../actions/searchWfRun' import { WfRunsHeader } from './WfRunsHeader' +import { SelectionLink } from '@/app/[tenantId]/components/SelectionLink' export const WfRuns: FC = spec => { const searchParams = useSearchParams() @@ -57,18 +57,13 @@ export const WfRuns: FC = spec => { ) : ( -
+
{data?.pages.map((page, i) => ( {page.results.map(wfRunId => ( -
- - {wfRunId.id} - -
+ +

{wfRunId.id}

+
))}
))} diff --git a/dashboard/src/app/[tenantId]/(diagram)/wfSpec/[...props]/components/WfRunsHeader.tsx b/dashboard/src/app/[tenantId]/(diagram)/wfSpec/[...props]/components/WfRunsHeader.tsx index 465f2e752..51f791acc 100644 --- a/dashboard/src/app/[tenantId]/(diagram)/wfSpec/[...props]/components/WfRunsHeader.tsx +++ b/dashboard/src/app/[tenantId]/(diagram)/wfSpec/[...props]/components/WfRunsHeader.tsx @@ -20,8 +20,6 @@ export const WfRunsHeader: FC = ({ spec, currentStatus, currentWindow, se return (
-

WfRun Search

-