diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..66f0171 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,79 @@ +name: CI + +on: + push: + branches: [dev, main] + pull_request: + branches: [main] + +permissions: + contents: read + +jobs: + changes: + name: Detect changed folders + runs-on: ubuntu-latest + outputs: + backend: ${{ steps.filter.outputs.backend }} + rag: ${{ steps.filter.outputs.rag }} + model: ${{ steps.filter.outputs.model }} + steps: + - uses: actions/checkout@v4 + + - uses: dorny/paths-filter@v3 + id: filter + with: + filters: | + backend: + - 'server/**' + rag: + - 'scan_report/**' + model: + - 'scan_model/**' + + test-backend: + name: Backend — Jest tests + runs-on: ubuntu-latest + needs: changes + if: needs.changes.outputs.backend == 'true' + defaults: + run: + working-directory: server + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-node@v4 + with: + node-version: 22 + cache: npm + cache-dependency-path: server/package-lock.json + + - name: Install dependencies + run: npm ci + + - name: Run tests + run: npm run test + + lint-rag: + name: RAG — Ruff lint + runs-on: ubuntu-latest + needs: changes + if: needs.changes.outputs.rag == 'true' + steps: + - uses: actions/checkout@v4 + + - uses: astral-sh/ruff-action@v3 + with: + src: ./scan_report + + lint-model: + name: Model — Ruff lint + runs-on: ubuntu-latest + needs: changes + if: needs.changes.outputs.model == 'true' + steps: + - uses: actions/checkout@v4 + + - uses: astral-sh/ruff-action@v3 + with: + src: ./scan_model diff --git a/client/src/features/dashboard/Home/index.tsx b/client/src/features/dashboard/Home/index.tsx index 53bbe07..c7212b3 100644 --- a/client/src/features/dashboard/Home/index.tsx +++ b/client/src/features/dashboard/Home/index.tsx @@ -2,7 +2,7 @@ import { useNavigate } from "@tanstack/react-router"; import { useQueryClient } from "@tanstack/react-query"; import { message } from "antd"; import type { ApiReportDetail } from "../Reports/types"; -import { generateReportPDF } from "../Reports/generateReportPDF"; +import { generateReportPDF } from "../Reports/components/generateReportPDF"; import axiosInstance from "../../../lib/axios"; import { MdFileUpload, diff --git a/client/src/features/dashboard/Notifications/NotificationDetail.tsx b/client/src/features/dashboard/Notifications/components/NotificationDetail.tsx similarity index 97% rename from client/src/features/dashboard/Notifications/NotificationDetail.tsx rename to client/src/features/dashboard/Notifications/components/NotificationDetail.tsx index 7752c16..84df462 100644 --- a/client/src/features/dashboard/Notifications/NotificationDetail.tsx +++ b/client/src/features/dashboard/Notifications/components/NotificationDetail.tsx @@ -1,7 +1,7 @@ import { MdArrowBack } from "react-icons/md"; -import { C, TYPE_STYLES } from "./tokens"; -import type { Notification } from "./types"; -import { formatRelativeTime } from "./utils"; +import { C, TYPE_STYLES } from "../tokens"; +import type { Notification } from "../types"; +import { formatRelativeTime } from "../utils"; import { useNavigate } from "@tanstack/react-router"; type Props = { diff --git a/client/src/features/dashboard/Notifications/NotificationItem.tsx b/client/src/features/dashboard/Notifications/components/NotificationItem.tsx similarity index 95% rename from client/src/features/dashboard/Notifications/NotificationItem.tsx rename to client/src/features/dashboard/Notifications/components/NotificationItem.tsx index f069833..669037a 100644 --- a/client/src/features/dashboard/Notifications/NotificationItem.tsx +++ b/client/src/features/dashboard/Notifications/components/NotificationItem.tsx @@ -1,6 +1,6 @@ -import { C, TYPE_STYLES } from "./tokens"; -import type { Notification } from "./types"; -import { formatRelativeTime } from "./utils"; +import { C, TYPE_STYLES } from "../tokens"; +import type { Notification } from "../types"; +import { formatRelativeTime } from "../utils"; type Props = { n: Notification; diff --git a/client/src/features/dashboard/Notifications/NotificationsPage.tsx b/client/src/features/dashboard/Notifications/components/NotificationsPage.tsx similarity index 98% rename from client/src/features/dashboard/Notifications/NotificationsPage.tsx rename to client/src/features/dashboard/Notifications/components/NotificationsPage.tsx index 2ff7ff8..27c40eb 100644 --- a/client/src/features/dashboard/Notifications/NotificationsPage.tsx +++ b/client/src/features/dashboard/Notifications/components/NotificationsPage.tsx @@ -8,11 +8,11 @@ import { MdArrowForward, } from "react-icons/md"; -import { C, TYPE_STYLES } from "./tokens"; -import type { Notification, NotifType } from "./types"; -import { useFetchNotifications, useReadNotification } from "./hooks"; +import { C, TYPE_STYLES } from "../tokens"; +import type { Notification, NotifType } from "../types"; +import { useFetchNotifications, useReadNotification } from "../hooks"; import { NotificationsSkeleton } from "./NotificationsSkeleton"; -import { formatRelativeTime } from "./utils"; +import { formatRelativeTime } from "../utils"; type ReadFilter = "all" | "unread" | "read"; diff --git a/client/src/features/dashboard/Notifications/NotificationsSkeleton.tsx b/client/src/features/dashboard/Notifications/components/NotificationsSkeleton.tsx similarity index 97% rename from client/src/features/dashboard/Notifications/NotificationsSkeleton.tsx rename to client/src/features/dashboard/Notifications/components/NotificationsSkeleton.tsx index 719b614..28421aa 100644 --- a/client/src/features/dashboard/Notifications/NotificationsSkeleton.tsx +++ b/client/src/features/dashboard/Notifications/components/NotificationsSkeleton.tsx @@ -1,4 +1,4 @@ -import { C } from "./tokens"; +import { C } from "../tokens"; const Bone = ({ w, h, r = 6 }: { w: string | number; h: number; r?: number }) => (
- {code}
-
-
- {children}
-
-);
-
-const InfoRow = ({
- Icon,
- title,
- children,
-}: {
- Icon: React.ComponentType<{ size?: number; color?: string }>;
- title: string;
- children: React.ReactNode;
-}) => (
- - Available events, payload shape, and signature verification. -
-- Subscribe to one or more events when creating a webhook. -
-| Event | -Category | -Description | -
|---|---|---|
-
- {e.name}
-
- |
- - - {e.category} - - | -- {e.description} - | -
- Every event has the same top-level envelope. The{" "}
-
- The
- Receive real-time event notifications by registering HTTPS endpoints. -
-| Endpoint | -Events | -Status | -Last Delivery | -Actions | -
|---|---|---|---|---|
|
-
-
-
- {w.url}
-
- {w.description && (
-
- {w.description}
-
- )}
- |
-
-
- {w.events.slice(0, 3).map((e) => (
-
- {e}
-
- ))}
- {w.events.length > 3 && (
-
- |
-
-
-
- |
-
- |
-
-
-
- |
-
POST requests with a
- JSON body.
- TumorLens-Signature header on each incoming request. You
- won't be able to see it in full again.
-
- {createdHook?.secret}
-
- {createdHook && {createdHook.url}
- What We Detect
+Detects 4 scan outcomes to support faster, more confident clinical decisions.
+{item.name}
+{item.desc}
+Upload Guidelines
+For best results, follow these recommendations
+{g.title}
+{g.desc}
+Analyzing MRI Scan
+This usually takes under 15 seconds
+Recent Uploads
+Your last 3 scans
+No recent uploads
+ ) : scans.map((r, i) => ( + +{r.patient?.full_name || r.name || ""}
+{formatScanDate(r.updatedAt)}
+MRI Image
+Supported formats: JPEG, PNG, DICOM · Max size: 20MB
+Scan limit reached
+
+ You've used all your free scans. {" "}
+ Upgrade Plan{" "}
+ for unlimited scans and advanced features.
+
Drop your MRI image here
++ Drag and drop your file, or{" "} + { e.stopPropagation(); fileInputRef.current?.click(); }} + > + browse from your computer + +
+Maximum file size: 20MB
+ +{previewFile.name}
+{previewFile.size}
+No preview available for DICOM
+MRI Image
-Supported formats: JPEG, PNG, DICOM · Max size: 20MB
-Scan limit reached
-
- You've used all your free scans. {" "}
- Upgrade Plan{" "}
- for unlimited scans and advanced features.
-
Drop your MRI image here
-- Drag and drop your file, or{" "} - { e.stopPropagation(); fileInputRef.current?.click(); }} - > - browse from your computer - -
-Maximum file size: 20MB
- -{previewFile.name}
-{previewFile.size}
-No preview available for DICOM
-Upload Guidelines
-For best results, follow these recommendations
-{g.title}
-{g.desc}
-Recent Uploads
-Your last 3 scans
-No recent uploads
- ) : recentScans?.map((r: any, i) => ( - - -{r?.patient?.full_name || r?.name || ""}
-{formatScanDate(r.updatedAt)}
-What We Detect
-Detects 4 scan outcomes to support faster, more confident clinical decisions.
-{item.name}
-{item.desc}
-Upload Guidelines
-For best results, follow these recommendations
-{g.title}
-{g.desc}
-Analyzing MRI Scan
-This usually takes under 15 seconds
-