-
Notifications
You must be signed in to change notification settings - Fork 91
ENG-3100: Seed Data developer UI, DashboardSnapshot model, and dashboard polish #7737
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
8b03868
4fc8a7d
8fc427c
d1b88ed
4fbea88
9d78644
50f43cb
230d07f
b471019
05f3ac7
2e48576
fdd7654
8561032
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,209 @@ | ||||||||||||||||||||
| import { | ||||||||||||||||||||
| Alert, | ||||||||||||||||||||
| Button, | ||||||||||||||||||||
| Card, | ||||||||||||||||||||
| Checkbox, | ||||||||||||||||||||
| Flex, | ||||||||||||||||||||
| Progress, | ||||||||||||||||||||
| Space, | ||||||||||||||||||||
| Tag, | ||||||||||||||||||||
| Typography, | ||||||||||||||||||||
| } from "fidesui"; | ||||||||||||||||||||
| import { useCallback, useEffect, useState } from "react"; | ||||||||||||||||||||
|
|
||||||||||||||||||||
| import { | ||||||||||||||||||||
| SeedStepStatus, | ||||||||||||||||||||
| SeedTasksConfig, | ||||||||||||||||||||
| useGetSeedStatusQuery, | ||||||||||||||||||||
| useTriggerSeedMutation, | ||||||||||||||||||||
| } from "~/features/seed-data/seed-data.slice"; | ||||||||||||||||||||
|
|
||||||||||||||||||||
| const { Paragraph, Text } = Typography; | ||||||||||||||||||||
|
|
||||||||||||||||||||
| interface SeedScenario { | ||||||||||||||||||||
| key: keyof SeedTasksConfig; | ||||||||||||||||||||
| label: string; | ||||||||||||||||||||
| description: string; | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| const SEED_SCENARIOS: SeedScenario[] = [ | ||||||||||||||||||||
| { | ||||||||||||||||||||
| key: "pbac", | ||||||||||||||||||||
| label: "PBAC (Access Control)", | ||||||||||||||||||||
| description: | ||||||||||||||||||||
| "Seed data purposes, data consumers, datasets, a mock query log integration, and 60 days of access control history.", | ||||||||||||||||||||
| }, | ||||||||||||||||||||
| { | ||||||||||||||||||||
| key: "dashboard", | ||||||||||||||||||||
| label: "Dashboard (Landing Page)", | ||||||||||||||||||||
| description: | ||||||||||||||||||||
| "Populate the landing page dashboard with privacy requests, system coverage metrics, audit activity, staged resources, and 30 days of trend history.", | ||||||||||||||||||||
| }, | ||||||||||||||||||||
| ]; | ||||||||||||||||||||
|
|
||||||||||||||||||||
| const STATUS_COLORS = { | ||||||||||||||||||||
| pending: "default", | ||||||||||||||||||||
| in_progress: "processing", | ||||||||||||||||||||
| complete: "success", | ||||||||||||||||||||
| skipped: "warning", | ||||||||||||||||||||
| error: "error", | ||||||||||||||||||||
| } as const; | ||||||||||||||||||||
|
|
||||||||||||||||||||
| const StepStatusTag = ({ status }: { status: SeedStepStatus }) => { | ||||||||||||||||||||
| const color = | ||||||||||||||||||||
| STATUS_COLORS[status.status as keyof typeof STATUS_COLORS] ?? "default"; | ||||||||||||||||||||
| const style = color === "processing" ? { color: "white" } : undefined; | ||||||||||||||||||||
| return ( | ||||||||||||||||||||
| <Tag color={color} style={style}> | ||||||||||||||||||||
| {status.status.replace("_", " ")} | ||||||||||||||||||||
| </Tag> | ||||||||||||||||||||
| ); | ||||||||||||||||||||
| }; | ||||||||||||||||||||
|
|
||||||||||||||||||||
| const computeProgress = (steps: Record<string, SeedStepStatus>): number => { | ||||||||||||||||||||
| const entries = Object.values(steps); | ||||||||||||||||||||
| if (entries.length === 0) { | ||||||||||||||||||||
| return 0; | ||||||||||||||||||||
| } | ||||||||||||||||||||
| const done = entries.filter( | ||||||||||||||||||||
| (step) => | ||||||||||||||||||||
| step.status === "complete" || | ||||||||||||||||||||
| step.status === "skipped" || | ||||||||||||||||||||
| step.status === "error", | ||||||||||||||||||||
| ).length; | ||||||||||||||||||||
| return Math.round((done / entries.length) * 100); | ||||||||||||||||||||
| }; | ||||||||||||||||||||
|
|
||||||||||||||||||||
| const SeedDataPanel = () => { | ||||||||||||||||||||
| const [selectedTasks, setSelectedTasks] = useState< | ||||||||||||||||||||
| Set<keyof SeedTasksConfig> | ||||||||||||||||||||
| >(new Set()); | ||||||||||||||||||||
| const [executionId, setExecutionId] = useState<string | null>(null); | ||||||||||||||||||||
| const [triggerSeed, { isLoading: isTriggering, isError: isTriggerError }] = | ||||||||||||||||||||
| useTriggerSeedMutation(); | ||||||||||||||||||||
|
|
||||||||||||||||||||
| // Poll for status when an execution is in progress | ||||||||||||||||||||
| const { data: statusData } = useGetSeedStatusQuery(executionId!, { | ||||||||||||||||||||
| skip: !executionId, | ||||||||||||||||||||
| // Seed tasks complete within seconds; 2s polling is intentional for this dev-only tool. | ||||||||||||||||||||
| pollingInterval: 2000, | ||||||||||||||||||||
| }); | ||||||||||||||||||||
|
Comment on lines
+86
to
+90
Contributor
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.
Suggested change
Rule Used: Polling intervals for async operations should be s... (source) Learnt From
Contributor
Author
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. Addressed in b471019. Added a clarifying comment that the 2s interval is intentional for this dev-only tool. |
||||||||||||||||||||
|
|
||||||||||||||||||||
| // Stop polling when execution completes | ||||||||||||||||||||
| useEffect(() => { | ||||||||||||||||||||
| if ( | ||||||||||||||||||||
| statusData && | ||||||||||||||||||||
| (statusData.status === "complete" || statusData.status === "error") | ||||||||||||||||||||
| ) { | ||||||||||||||||||||
| setExecutionId(null); | ||||||||||||||||||||
| } | ||||||||||||||||||||
| }, [statusData]); | ||||||||||||||||||||
|
|
||||||||||||||||||||
| const handleToggle = useCallback((key: keyof SeedTasksConfig) => { | ||||||||||||||||||||
| setSelectedTasks((prev) => { | ||||||||||||||||||||
| const next = new Set(prev); | ||||||||||||||||||||
| if (next.has(key)) { | ||||||||||||||||||||
| next.delete(key); | ||||||||||||||||||||
| } else { | ||||||||||||||||||||
| next.add(key); | ||||||||||||||||||||
| } | ||||||||||||||||||||
| return next; | ||||||||||||||||||||
| }); | ||||||||||||||||||||
| }, []); | ||||||||||||||||||||
|
|
||||||||||||||||||||
| const handleSeed = useCallback(async () => { | ||||||||||||||||||||
| const tasks: SeedTasksConfig = {}; | ||||||||||||||||||||
| selectedTasks.forEach((key) => { | ||||||||||||||||||||
| tasks[key] = true; | ||||||||||||||||||||
| }); | ||||||||||||||||||||
|
|
||||||||||||||||||||
| const result = await triggerSeed({ tasks }) | ||||||||||||||||||||
| .unwrap() | ||||||||||||||||||||
| .catch(() => null); | ||||||||||||||||||||
| if (result) { | ||||||||||||||||||||
| setExecutionId(result.execution_id); | ||||||||||||||||||||
| } | ||||||||||||||||||||
| }, [selectedTasks, triggerSeed]); | ||||||||||||||||||||
|
|
||||||||||||||||||||
| const isRunning = !!executionId || isTriggering; | ||||||||||||||||||||
| const showStatus = statusData && statusData.status !== "pending"; | ||||||||||||||||||||
|
|
||||||||||||||||||||
| return ( | ||||||||||||||||||||
| <Card title="Seed scenarios" style={{ maxWidth: 720 }}> | ||||||||||||||||||||
| <Paragraph type="secondary"> | ||||||||||||||||||||
| Select which data scenarios to seed into this environment. Seeding is | ||||||||||||||||||||
| idempotent — it is safe to run multiple times. | ||||||||||||||||||||
| </Paragraph> | ||||||||||||||||||||
|
|
||||||||||||||||||||
| <Space direction="vertical" size="middle" className="mb-6 w-full"> | ||||||||||||||||||||
| {SEED_SCENARIOS.map((scenario) => ( | ||||||||||||||||||||
| <Checkbox | ||||||||||||||||||||
| key={scenario.key} | ||||||||||||||||||||
| checked={selectedTasks.has(scenario.key)} | ||||||||||||||||||||
| onChange={() => handleToggle(scenario.key)} | ||||||||||||||||||||
| disabled={isRunning} | ||||||||||||||||||||
| > | ||||||||||||||||||||
| <Text strong>{scenario.label}</Text> | ||||||||||||||||||||
| <br /> | ||||||||||||||||||||
| <Text type="secondary">{scenario.description}</Text> | ||||||||||||||||||||
| </Checkbox> | ||||||||||||||||||||
| ))} | ||||||||||||||||||||
| </Space> | ||||||||||||||||||||
|
|
||||||||||||||||||||
| <Flex gap="small" align="center"> | ||||||||||||||||||||
| <Button | ||||||||||||||||||||
| type="primary" | ||||||||||||||||||||
| onClick={handleSeed} | ||||||||||||||||||||
| disabled={selectedTasks.size === 0 || isRunning} | ||||||||||||||||||||
| loading={isRunning} | ||||||||||||||||||||
| > | ||||||||||||||||||||
| {isRunning ? "Seeding..." : "Seed data"} | ||||||||||||||||||||
| </Button> | ||||||||||||||||||||
| </Flex> | ||||||||||||||||||||
|
|
||||||||||||||||||||
| {isTriggerError ? ( | ||||||||||||||||||||
| <Alert | ||||||||||||||||||||
| type="error" | ||||||||||||||||||||
| message="Failed to start seed. Please try again." | ||||||||||||||||||||
| className="mt-4" | ||||||||||||||||||||
| showIcon | ||||||||||||||||||||
| /> | ||||||||||||||||||||
| ) : null} | ||||||||||||||||||||
|
|
||||||||||||||||||||
| {showStatus ? ( | ||||||||||||||||||||
| <Flex vertical gap="small" className="mt-4"> | ||||||||||||||||||||
| <Progress | ||||||||||||||||||||
| percent={computeProgress(statusData.steps)} | ||||||||||||||||||||
| status={statusData.status === "error" ? "exception" : undefined} | ||||||||||||||||||||
| /> | ||||||||||||||||||||
| <Space direction="vertical" size="small" className="mt-2 w-full"> | ||||||||||||||||||||
| {Object.entries(statusData.steps).map(([stepName, stepStatus]) => ( | ||||||||||||||||||||
| <Flex key={stepName} justify="space-between" align="center"> | ||||||||||||||||||||
| <Text>{stepName.replace(/_/g, " ")}</Text> | ||||||||||||||||||||
| <StepStatusTag status={stepStatus} /> | ||||||||||||||||||||
| </Flex> | ||||||||||||||||||||
| ))} | ||||||||||||||||||||
| </Space> | ||||||||||||||||||||
| {statusData.error ? ( | ||||||||||||||||||||
| <Alert | ||||||||||||||||||||
| type="error" | ||||||||||||||||||||
| message={statusData.error} | ||||||||||||||||||||
| className="mt-2" | ||||||||||||||||||||
| showIcon | ||||||||||||||||||||
| /> | ||||||||||||||||||||
| ) : null} | ||||||||||||||||||||
| {statusData.status === "complete" ? ( | ||||||||||||||||||||
| <Alert | ||||||||||||||||||||
| type="success" | ||||||||||||||||||||
| message="Seed completed successfully" | ||||||||||||||||||||
| className="mt-2" | ||||||||||||||||||||
| showIcon | ||||||||||||||||||||
| /> | ||||||||||||||||||||
| ) : null} | ||||||||||||||||||||
| </Flex> | ||||||||||||||||||||
| ) : null} | ||||||||||||||||||||
| </Card> | ||||||||||||||||||||
| ); | ||||||||||||||||||||
| }; | ||||||||||||||||||||
|
|
||||||||||||||||||||
| export default SeedDataPanel; | ||||||||||||||||||||
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.
The parameter
sshould use a full descriptive name per the project's variable naming convention.Rule Used: Use full names for variables, not 1 to 2 character... (source)
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
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.
Addressed in b471019. Renamed
stostep.