-
Notifications
You must be signed in to change notification settings - Fork 0
Added orchestration workflow support #7
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| "use client"; | ||
|
|
||
| import { OrchestrationLayout } from "@/app/components/OrchestrationLayout"; | ||
| import { OrchestrationHome } from "@/app/components/OrchestrationHome"; | ||
|
|
||
| export default function OrchestrationsPage() { | ||
| return ( | ||
| <OrchestrationLayout | ||
| title="Orchestrations" | ||
| description="Manage operational workflows and automation" | ||
| > | ||
| <OrchestrationHome /> | ||
| </OrchestrationLayout> | ||
| ); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| 'use client'; | ||
|
|
||
| import React from 'react'; | ||
| import { useParams } from 'next/navigation'; | ||
| import { OrchestrationLayout } from '@/app/components/OrchestrationLayout'; | ||
| import { PlanDetail } from '@/app/components/PlanDetail'; | ||
|
|
||
| export default function PlanDetailPage() { | ||
| const params = useParams(); | ||
| const planId = params.planId as string; | ||
|
|
||
| return ( | ||
| <OrchestrationLayout title="Plan Details"> | ||
| <PlanDetail planId={planId} /> | ||
| </OrchestrationLayout> | ||
| ); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| 'use client'; | ||
|
|
||
| import React from 'react'; | ||
| import { OrchestrationLayout } from '@/app/components/OrchestrationLayout'; | ||
| import { PlanBrowser } from '@/app/components/PlanBrowser'; | ||
|
|
||
| export default function PlansPage() { | ||
| return ( | ||
| <OrchestrationLayout | ||
| title="Workflow Plans" | ||
| description="Browse and manage orchestration workflow plans" | ||
| > | ||
| <PlanBrowser /> | ||
| </OrchestrationLayout> | ||
| ); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| 'use client'; | ||
|
|
||
| import React from 'react'; | ||
| import { useParams } from 'next/navigation'; | ||
| import { OrchestrationLayout } from '@/app/components/OrchestrationLayout'; | ||
| import { RunDetail } from '@/app/components/RunDetail'; | ||
|
|
||
| export default function RunDetailPage() { | ||
| const params = useParams(); | ||
| const runId = params.runId as string; | ||
|
|
||
| return ( | ||
| <OrchestrationLayout title="Run Details"> | ||
| <RunDetail runId={runId} /> | ||
| </OrchestrationLayout> | ||
| ); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,65 @@ | ||
| 'use client'; | ||
|
|
||
| import React, { Suspense } from 'react'; | ||
| import { useSearchParams } from 'next/navigation'; | ||
| import { OrchestrationLayout } from '@/app/components/OrchestrationLayout'; | ||
| import { RunBrowser } from '@/app/components/RunBrowser'; | ||
| import { RunQuery, RunStatus } from '@/app/lib/types'; | ||
| import { parseScope } from '@/app/lib/scope'; | ||
|
|
||
| function RunsPageContent() { | ||
| const searchParams = useSearchParams(); | ||
|
|
||
| // Parse initial query from URL parameters | ||
| const initialQuery: Partial<RunQuery> = {}; | ||
|
|
||
| const statusParam = searchParams.get('status') || searchParams.get('statuses'); | ||
| if (statusParam) { | ||
| const statuses = statusParam.split(',') as RunStatus[]; | ||
| initialQuery.statuses = statuses; | ||
| } | ||
|
|
||
| const planIdParam = searchParams.get('planId') || searchParams.get('planIds'); | ||
| if (planIdParam) { | ||
| initialQuery.planIds = planIdParam.split(',').filter(Boolean); | ||
| } | ||
|
|
||
| const limitParam = searchParams.get('limit'); | ||
| if (limitParam) { | ||
| const limit = Number(limitParam); | ||
| if (!Number.isNaN(limit) && limit > 0) { | ||
| initialQuery.limit = limit; | ||
| } | ||
| } | ||
|
|
||
| const scopeParam = searchParams.get('scope'); | ||
| const parsedScope = parseScope(scopeParam); | ||
| const service = searchParams.get('service'); | ||
| const environment = searchParams.get('environment'); | ||
| const team = searchParams.get('team'); | ||
|
|
||
| if (parsedScope) { | ||
| initialQuery.scope = parsedScope; | ||
| } else if (service || environment || team) { | ||
| initialQuery.scope = { | ||
| service: service || undefined, | ||
| environment: environment || undefined, | ||
| team: team || undefined, | ||
| }; | ||
| } | ||
|
|
||
| return <RunBrowser initialQuery={initialQuery} />; | ||
| } | ||
|
|
||
| export default function RunsPage() { | ||
| return ( | ||
| <OrchestrationLayout | ||
| title="Workflow Runs" | ||
| description="Browse and monitor orchestration workflow runs" | ||
| > | ||
| <Suspense fallback={<div className="p-8 text-center text-gray-500">Loading...</div>}> | ||
| <RunsPageContent /> | ||
| </Suspense> | ||
| </OrchestrationLayout> | ||
| ); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,47 @@ | ||
| 'use client'; | ||
|
|
||
| import React from 'react'; | ||
|
|
||
| type MarkdownTextProps = { | ||
| text?: string; | ||
| className?: string; | ||
| }; | ||
|
|
||
| function renderPlainText(segment: string, keyPrefix: string) { | ||
| const lines = segment.split('\n'); | ||
| return lines.map((line, index) => ( | ||
| <React.Fragment key={`${keyPrefix}-line-${index}`}> | ||
| {line} | ||
| {index < lines.length - 1 && <br />} | ||
| </React.Fragment> | ||
| )); | ||
| } | ||
|
|
||
| export function MarkdownText({ text, className }: MarkdownTextProps) { | ||
| if (!text) return null; | ||
|
|
||
| const parts = text.split('`'); | ||
|
|
||
| return ( | ||
| <span className={className}> | ||
| {parts.map((part, index) => { | ||
| if (index % 2 === 1) { | ||
| return ( | ||
| <code | ||
| key={`code-${index}`} | ||
| className="rounded bg-gray-100 px-1 py-0.5 font-mono text-[12px] text-gray-700" | ||
| > | ||
| {part} | ||
| </code> | ||
| ); | ||
|
Comment on lines
+28
to
+36
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. logic: Empty code blocks will be rendered when consecutive backticks appear in the input (e.g., 'test``code' creates an empty code element). Consider filtering out empty segments. Prompt To Fix With AIThis is a comment left during a code review.
Path: app/components/MarkdownText.tsx
Line: 28:36
Comment:
**logic:** Empty code blocks will be rendered when consecutive backticks appear in the input (e.g., 'test``code' creates an empty code element). Consider filtering out empty segments.
How can I resolve this? If you propose a fix, please make it concise. |
||
| } | ||
|
|
||
| return ( | ||
| <React.Fragment key={`text-${index}`}> | ||
| {renderPlainText(part, `text-${index}`)} | ||
| </React.Fragment> | ||
| ); | ||
| })} | ||
| </span> | ||
| ); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| "use client"; | ||
|
|
||
| import { RunStatusSummary } from "@/app/components/RunStatusSummary"; | ||
| import { RecentPlans } from "@/app/components/RecentPlans"; | ||
| import { QuickActions } from "@/app/components/QuickActions"; | ||
|
|
||
| export function OrchestrationHome() { | ||
| return ( | ||
| <div className="grid grid-cols-1 gap-6"> | ||
| {/* Welcome Section */} | ||
| <div className="rounded-xl border border-slate-200 bg-gradient-to-br from-slate-50 to-white p-6"> | ||
| <h2 className="text-lg font-semibold text-slate-900 mb-3">Workflow Orchestration</h2> | ||
| <p className="text-sm text-slate-600 mb-4"> | ||
| Automate your operations with runbooks, playbooks, and checklists. Streamline complex workflows and ensure consistency across your team. | ||
| </p> | ||
| </div> | ||
|
|
||
| {/* Quick Actions */} | ||
| <QuickActions /> | ||
|
|
||
| {/* Status Summary and Recent Plans */} | ||
| <div className="grid grid-cols-1 lg:grid-cols-2 gap-6"> | ||
| <RunStatusSummary /> | ||
| <RecentPlans /> | ||
| </div> | ||
| </div> | ||
| ); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| "use client"; | ||
|
|
||
| import { ReactNode } from "react"; | ||
| import { AppShell } from "@/app/components/AppShell"; | ||
|
|
||
| interface OrchestrationLayoutProps { | ||
| title: string; | ||
| description?: string; | ||
| children: ReactNode; | ||
| } | ||
|
|
||
| export function OrchestrationLayout({ | ||
| title, | ||
| description, | ||
| children, | ||
| }: OrchestrationLayoutProps) { | ||
| return ( | ||
| <AppShell | ||
| title={title} | ||
| description={description} | ||
| > | ||
| {children} | ||
| </AppShell> | ||
| ); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,100 @@ | ||
| "use client"; | ||
|
|
||
| import { useCallback, useEffect, useState } from "react"; | ||
| import { useAsyncState } from "@/app/lib/hooks"; | ||
| import { queryPlans } from "@/app/lib/orchestration"; | ||
| import { OrchestrationPlan, PlanQuery } from "@/app/lib/types"; | ||
| import { PlanGrid } from "@/app/components/PlanGrid"; | ||
| import { PlanFilters } from "@/app/components/PlanFilters"; | ||
|
|
||
| export function PlanBrowser() { | ||
| const [plans, setPlans] = useState<OrchestrationPlan[]>([]); | ||
| const [currentFilters, setCurrentFilters] = useState<Partial<PlanQuery>>({}); | ||
| const asyncState = useAsyncState(); | ||
| const { start, succeed, fail } = asyncState; | ||
|
|
||
| const loadPlans = useCallback( | ||
| async (filters: Partial<PlanQuery> = {}) => { | ||
| start(); | ||
| try { | ||
| const result = await queryPlans({ | ||
| ...filters, | ||
| limit: 50, // Load up to 50 plans | ||
| }); | ||
| setPlans(result || []); | ||
| succeed(); | ||
| } catch (err) { | ||
| fail(err); | ||
| } | ||
| }, | ||
| [start, succeed, fail], | ||
| ); | ||
|
|
||
| const handleFilterChange = (filters: Partial<PlanQuery>) => { | ||
| setCurrentFilters(filters); | ||
| loadPlans(filters); | ||
| }; | ||
|
|
||
| // Load initial plans | ||
| useEffect(() => { | ||
| const timeoutId = setTimeout(() => { | ||
| void loadPlans(); | ||
| }, 0); | ||
| return () => clearTimeout(timeoutId); | ||
| }, [loadPlans]); | ||
|
|
||
| return ( | ||
| <div className="space-y-6"> | ||
| {/* Filters */} | ||
| <PlanFilters | ||
| onFilterChange={handleFilterChange} | ||
| loading={asyncState.loading} | ||
| /> | ||
|
|
||
| {/* Results Header */} | ||
| <div className="flex items-center justify-between"> | ||
| <div> | ||
| <h2 className="text-xl font-semibold text-slate-900"> | ||
| Available Plans | ||
| </h2> | ||
| <p className="text-sm text-slate-600 mt-1"> | ||
| {asyncState.loading ? ( | ||
| "Loading plans..." | ||
| ) : ( | ||
| `${plans.length} plan${plans.length !== 1 ? 's' : ''} found` | ||
| )} | ||
| </p> | ||
| </div> | ||
|
|
||
| {/* Sort Options */} | ||
| <div className="flex items-center gap-2"> | ||
| <label className="text-sm font-medium text-slate-700">Sort by:</label> | ||
| <select className="rounded-lg border border-slate-300 px-3 py-1 text-sm focus:border-[#55cfd0] focus:outline-none focus:ring-1 focus:ring-[#55cfd0]"> | ||
| <option value="title">Title</option> | ||
| <option value="type">Type</option> | ||
| <option value="steps">Step Count</option> | ||
| </select> | ||
|
Comment on lines
+72
to
+76
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. logic: Sort dropdown is not functional - no onChange handler or state management. Should this sorting functionality be implemented now or is it intended for a future iteration? Prompt To Fix With AIThis is a comment left during a code review.
Path: app/components/PlanBrowser.tsx
Line: 66:70
Comment:
**logic:** Sort dropdown is not functional - no onChange handler or state management. Should this sorting functionality be implemented now or is it intended for a future iteration?
How can I resolve this? If you propose a fix, please make it concise. |
||
| </div> | ||
| </div> | ||
|
|
||
| {/* Plan Grid */} | ||
| <PlanGrid | ||
| plans={plans} | ||
| loading={asyncState.loading} | ||
| error={asyncState.error} | ||
| /> | ||
|
|
||
| {/* Load More Button (if needed) */} | ||
| {plans.length >= 50 && !asyncState.loading && ( | ||
| <div className="text-center pt-6"> | ||
| <button | ||
| onClick={() => loadPlans({ ...currentFilters, limit: plans.length + 50 })} | ||
|
Comment on lines
+88
to
+91
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. logic: Load more logic may not work as intended - it passes limit as total count rather than maintaining offset Prompt To Fix With AIThis is a comment left during a code review.
Path: app/components/PlanBrowser.tsx
Line: 82:85
Comment:
**logic:** Load more logic may not work as intended - it passes limit as total count rather than maintaining offset
How can I resolve this? If you propose a fix, please make it concise. |
||
| className="inline-flex items-center px-6 py-3 border border-slate-300 text-sm font-medium rounded-lg text-slate-700 bg-white hover:bg-slate-50 transition" | ||
| > | ||
| Load More Plans | ||
| </button> | ||
| </div> | ||
| )} | ||
| </div> | ||
| ); | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
logic: Type assertion assumes URL params contain valid RunStatus values without validation. Should this validate that the status values are actually valid RunStatus enum values before casting?
Prompt To Fix With AI