Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions clients/admin-ui/codegen.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import type { CodegenConfig } from "@graphql-codegen/cli";

/**
* GraphQL code generation for the dashboard PoC.
*
* Reads the local SDL (emitted from fidesplus via `nox -s graphql_emit_schema`)
* and generates a typed client into src/__generated__/graphql/.
*
* Rerun whenever schema.graphql or a *.graphql document changes:
* npm run graphql:generate
*/
const config: CodegenConfig = {
schema: "schema.graphql",
documents: ["src/**/*.graphql"],
generates: {
"src/__generated__/graphql/": {
preset: "client",
presetConfig: {
gqlTagName: "gql",
},
config: {
useTypeImports: true,
scalars: {
DateTime: "string",
JSON: "Record<string, unknown>",
},
},
},
},
};

export default config;
7 changes: 7 additions & 0 deletions clients/admin-ui/next.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,13 @@ const nextConfig = {
source: `/health`,
destination: `${process.env.NEXT_PUBLIC_FIDESCTL_API_SERVER}/health`,
},
// GraphQL PoC: the Strawberry endpoint is mounted at /graphql (not under
// /api/v1), so proxy it to the backend for the real-BE path. In mock
// mode MSW intercepts /graphql before it reaches this rewrite.
{
source: `/graphql`,
destination: `${process.env.NEXT_PUBLIC_FIDESCTL_API_SERVER}/graphql`,
},
];
},
};
Expand Down
8 changes: 8 additions & 0 deletions clients/admin-ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
"cy:start:dev": "NEXT_PUBLIC_FIDESCTL_API_SERVER=http://localhost:8080 npm run cy:start",
"dev": "next dev -p 3000",
"dev:mock": "echo '🚨 Running with mock API'; NEXT_PUBLIC_MOCK_API=true npm run dev",
"dev:mock-graphql": "echo '🧪 Running with mocked GraphQL endpoint'; NEXT_PUBLIC_MOCK_GRAPHQL=true npm run dev",
"graphql:generate": "graphql-codegen && node scripts/sync-graphql-sdl.mjs",
"export": "PROD_EXPORT=true next build",
"prod-export": "npm run export && npm run copy-export",
"format": "prettier --write --log-level warn .",
Expand All @@ -36,12 +38,15 @@
},
"dependencies": {
"@ant-design/cssinjs": "^2.1.2",
"@apollo/client": "^3.14.1",
"@dagrejs/dagre": "^1.1.4",
"@date-fns/tz": "^1.2.0",
"@dnd-kit/core": "^6.3.1",
"@dnd-kit/sortable": "^10.0.0",
"@dnd-kit/utilities": "^3.2.2",
"@fontsource/inter": "^4.5.15",
"@graphql-tools/mock": "^9.1.7",
"@graphql-tools/schema": "^10.0.33",
"@monaco-editor/react": "^4.6.0",
"@reduxjs/toolkit": "^2.6.0",
"@tanstack/react-table": "^8.10.7",
Expand All @@ -59,6 +64,7 @@
"file-saver": "^2.0.5",
"formik": "^2.4.6",
"framer-motion": "^11.2.12",
"graphql": "^16.14.0",
"i18n-iso-countries": "^7.5.0",
"immer": "^9.0.21",
"js-yaml": "^4.1.1",
Expand All @@ -81,6 +87,8 @@
},
"devDependencies": {
"@ant-design/cli": "^6.3.7",
"@graphql-codegen/cli": "^5.0.7",
"@graphql-codegen/client-preset": "^4.8.3",
"@hey-api/openapi-ts": "^0.88.2",
"@jest/globals": "^29.7.0",
"@next/bundle-analyzer": "^16.2.0",
Expand Down
195 changes: 195 additions & 0 deletions clients/admin-ui/schema.graphql
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
directive @defer(
if: Boolean
label: String
) on FRAGMENT_SPREAD | INLINE_FRAGMENT

directive @stream(if: Boolean, label: String, initialCount: Int = 0) on FIELD

enum ActionSeverity {
critical
high
medium
low
}

type ActivityFeedItem {
actorType: ActorType!
message: String!
timestamp: DateTime!
}

type ActivityFeedPage {
items: [ActivityFeedItem!]!
total: Int!
page: Int!
size: Int!
pages: Int!
}

enum ActorType {
user
agent
}

type AgentBriefing {
briefing: String!
quickActions: [QuickAction!]!
}

type Astralis {
activeConversations: Int!
completedAssessments: Int!
awaitingResponse: Int!
risksIdentified: Int!
}

enum DashboardActionStatus {
pending
in_progress
completed
}

enum DashboardActionType {
classification_review
dsr_action
system_review
steward_assignment
consent_anomaly
policy_violation
pia_update
}

"""
Date with time (isoformat)
"""
scalar DateTime

enum DiffDirection {
up
down
unchanged
}

"""
The `JSON` scalar type represents JSON values as specified by [ECMA-404](https://ecma-international.org/wp-content/uploads/ECMA-404_2nd_edition_december_2017.pdf).
"""
scalar JSON
@specifiedBy(
url: "https://ecma-international.org/wp-content/uploads/ECMA-404_2nd_edition_december_2017.pdf"
)

type Posture {
score: Float!
band: PostureBand!
diffPercent: Float!
diffDirection: DiffDirection!
agentAnnotation: String!
dimensions: [PostureDimension!]!
}

enum PostureBand {
critical
at_risk
good
excellent
}

type PostureDimension {
dimension: String!
label: String!
weight: Float!
score: Float!
band: PostureBand!
}

type PriorityAction {
id: ID!
type: DashboardActionType!
severity: ActionSeverity!
title: String!
message: String!
agentSummary: String!
dueDate: DateTime
actionData: JSON!
status: DashboardActionStatus!
}

type PriorityActionsPage {
items: [PriorityAction!]!
total: Int!
page: Int!
size: Int!
pages: Int!
}

type PrivacyRequestStatuses {
inProgress: Int!
pendingAction: Int!
awaitingApproval: Int!
}

type PrivacyRequests {
activeCount: Int!
statuses: PrivacyRequestStatuses!
overdueCount: Int!
slaHealth: [SLAHealthBucket!]!
}

type Query {
agentBriefing: AgentBriefing!
posture: Posture!
trends(period: TrendPeriod! = thirty_days): Trends!
astralis: Astralis!
activityFeed(page: Int! = 1, size: Int! = 20): ActivityFeedPage!
privacyRequests: PrivacyRequests!
systemCoverage: SystemCoverage!
priorityActions(
page: Int! = 1
size: Int! = 8
action: DashboardActionType = null
status: DashboardActionStatus = null
dimension: String = null
): PriorityActionsPage!
}

type QuickAction {
label: String!
actionType: DashboardActionType!
actionData: JSON!
severity: ActionSeverity!
}

type SLAHealthBucket {
label: String!
onTrack: Int!
approaching: Int!
overdue: Int!
}

type SystemCoverage {
totalSystems: Int!
fullyClassified: Int!
partiallyClassified: Int!
unclassified: Int!
withoutSteward: Int!
coveragePercentage: Float!
}

type TrendMetric {
key: String!
value: Float!
history: [Float!]!
metadata: JSON!
diff: Float!
}

enum TrendPeriod {
thirty_days
sixty_days
ninety_days
}

type Trends {
period: TrendPeriod!
metrics: [TrendMetric!]!
}
24 changes: 24 additions & 0 deletions clients/admin-ui/scripts/sync-graphql-sdl.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#!/usr/bin/env node
/**
* Emits the bundled schema.graphql contents into a TypeScript constant so the
* mock layer (which runs in the browser via MSW) can build an executable
* schema without a webpack/Turbopack raw-loader rule.
*
* Re-run by `npm run graphql:generate` after the SDL changes.
*/
import { readFileSync, writeFileSync } from "node:fs";
import { dirname, resolve } from "node:path";
import { fileURLToPath } from "node:url";

const here = dirname(fileURLToPath(import.meta.url));
const root = resolve(here, "..");
const sdlPath = resolve(root, "schema.graphql");
const outPath = resolve(
root,
"src/features/dashboard-graphql/schema-string.ts",
);

const sdl = readFileSync(sdlPath, "utf8");
const body = `/* eslint-disable */\n// AUTOGENERATED by scripts/sync-graphql-sdl.mjs. Do not edit by hand.\n// Run \`npm run graphql:generate\` to refresh.\n\nexport const dashboardSchemaSDL = ${JSON.stringify(sdl)};\n`;
writeFileSync(outPath, body, "utf8");
console.log(`wrote ${outPath} (${sdl.length} bytes)`);
Loading
Loading