Skip to content
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

Pacs progress websockets #1258

Merged
merged 49 commits into from
Oct 18, 2024
Merged
Changes from 1 commit
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
5dc1248
Add LonkClient
jennydaman Sep 16, 2024
288aea3
Add new fp-ts PfdcmClient
jennydaman Sep 16, 2024
86e791c
Begin rewriting Pacs
jennydaman Sep 16, 2024
5991eda
Fix font styling conflict by antd App component
jennydaman Sep 17, 2024
0419bb7
Change LonkClient to accept handlers via init
jennydaman Sep 21, 2024
f71c38c
Merge staging (switch from npm to pnpm)
jennydaman Sep 21, 2024
c0556a5
Migrate .../Pacs/components/input.tsx to antd
jennydaman Sep 21, 2024
d2074e2
Merge remote-tracking branch 'upstream/staging' into pacs-progress-we…
jennydaman Sep 21, 2024
707d10e
LonkClient.unsubscribeAll
jennydaman Sep 21, 2024
0c5b27d
Merge staging #1249
jennydaman Sep 22, 2024
2a72864
Reorganize Pacs to be MVC-ish
jennydaman Sep 22, 2024
2ad8b55
Add helpers for testing components which use redux
jennydaman Sep 22, 2024
75ed5fa
Merge staging #1250
jennydaman Sep 22, 2024
5e3fd36
Implement PACS query
jennydaman Sep 22, 2024
6bc5f3e
Implement PACS study details
jennydaman Sep 23, 2024
8628407
Replace Radio.Group with Segmented
jennydaman Sep 23, 2024
7c530d5
Change Pacs studies to use Collapse instead of List
jennydaman Sep 23, 2024
e2cdc0e
Add AccessionNumber to study title
jennydaman Sep 23, 2024
66a0f0b
SeriesList
jennydaman Sep 23, 2024
a20b1d0
Merge remote-tracking branch 'upstream/staging' into pacs-progress-we…
jennydaman Sep 23, 2024
5b3a2c4
Remove redux from Pacs code
jennydaman Sep 23, 2024
c4a2b39
WIP
jennydaman Sep 24, 2024
5bb10d8
ReceiveState
jennydaman Sep 24, 2024
f3057a3
Fix pfdcm tests since removal of fp-ts monads
jennydaman Sep 24, 2024
02d7e39
useLonk
jennydaman Sep 24, 2024
1a66b2a
Rename things + more documentation
jennydaman Sep 24, 2024
e39a50a
Merge staging
jennydaman Sep 25, 2024
0811d4e
PACS pull is working, progress messages aren't though.
jennydaman Sep 25, 2024
76c5a75
Pull study works, except for the last part.
jennydaman Sep 25, 2024
ea6d7f1
Merge branch 'staging' into pacs-progress-websockets
jennydaman Sep 25, 2024
ad7fba4
Everything working! I think...
jennydaman Sep 26, 2024
a74ae25
Customize progress bar theme
jennydaman Sep 26, 2024
2df2851
Implement series pull
jennydaman Sep 26, 2024
e8ddeef
Fix crash on invalid StudyDate
jennydaman Sep 26, 2024
27db1fc
Add test stub
jennydaman Sep 26, 2024
9e4a713
Merge remote-tracking branch 'upstream/staging' into pacs-progress-we…
jennydaman Oct 9, 2024
37f0839
Hacky workaround for double firing of pull request
jennydaman Oct 9, 2024
08e5280
Revert "Hacky workaround for double firing of pull request"
jennydaman Oct 10, 2024
ef2cc3a
Small changes
jennydaman Oct 10, 2024
e174158
pnpm run fmt
jennydaman Oct 10, 2024
8e169bc
Change type of pullRequests from ReadonlyArray to PullRequestStates
jennydaman Oct 10, 2024
f7cf25a
terribleStrictModeWorkaround
jennydaman Oct 10, 2024
5e8505e
Fix all lints
jennydaman Oct 10, 2024
43d9fd0
Fix some pypx types
jennydaman Oct 10, 2024
d0d2bd0
Automatically expand if only one study found
jennydaman Oct 10, 2024
8a8aedb
Fix infinite useEffects calling lonk.subscribe
jennydaman Oct 11, 2024
c0001cd
Add tests
jennydaman Oct 11, 2024
4ede58b
Delete example.test.ts
jennydaman Oct 11, 2024
2ca5227
Make CUBE_POLL_INTERVAL_MS configurable via environment variable
jennydaman Oct 11, 2024
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
Prev Previous commit
Next Next commit
Remove redux from Pacs code
  • Loading branch information
jennydaman committed Sep 23, 2024
commit 5b3a2c48d8f33f548d50e9cff53170795d48804d
2 changes: 1 addition & 1 deletion src/api/chrisapiclient.ts
Original file line number Diff line number Diff line change
@@ -7,7 +7,7 @@ import { Cookies } from "react-cookie";
* passed the token, declare process.env variables, etc.
*/

// biome-ignore lint/complexity/noStaticOnlyClass: <explanation>
// biome-ignore lint/complexity/noStaticOnlyClass: Singleton pattern
class ChrisAPIClient {
private static client: Client;
private static isTokenAuthorized: boolean;
113 changes: 47 additions & 66 deletions src/api/pfdcm/client.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
/**
* Wrapper for the OpenAPI-generated client, providing better typing
* and a fp-ts API.
* Wrapper for the OpenAPI-generated client, providing better typing.
*
* Note to developers: we want to use some types from fp-ts such as
* `ReadonlyNonEmptyArray` but we don't use `TaskEither` because
* traditional promises and `throw` are more compatible with TanStack
* Query.
*/

import * as TE from "fp-ts/TaskEither";
import * as E from "fp-ts/Either";
import {
Configuration,
PACSPypxApiV1PACSSyncPypxPostRequest,
@@ -14,18 +16,14 @@ import {
PACSServiceHandlerApiV1PACSThreadPypxPostRequest,
PACSasync,
} from "./generated";
import { flow, pipe } from "fp-ts/function";
import { pipe } from "fp-ts/function";
import { PypxFind, PypxTag, Series, StudyAndSeries } from "./models.ts";
import { parse as parseDate } from "date-fns";
import {
ReadonlyNonEmptyArray,
fromArray as readonlyNonEmptyArrayFromArray,
} from "fp-ts/ReadonlyNonEmptyArray";

const validateNonEmptyStringArray = flow(
readonlyNonEmptyArrayFromArray<string>,
TE.fromOption(() => new Error("PFDCM returned an empty list for services")),
);
import { match as matchOption } from "fp-ts/Option";

/**
* PFDCM client.
@@ -41,23 +39,25 @@ class PfdcmClient {
/**
* Get list of PACS services which this PFDCM is configured to speak with.
*/
public getPacsServices(): TE.TaskEither<
Error,
ReadonlyNonEmptyArray<string>
> {
public async getPacsServices(): Promise<ReadonlyNonEmptyArray<string>> {
const services =
await this.servicesClient.serviceListGetApiV1PACSserviceListGet();
return pipe(
TE.tryCatch(
() => this.servicesClient.serviceListGetApiV1PACSserviceListGet(),
E.toError,
// default service is a useless option added by pfdcm
services.filter((s) => s !== "default"),
readonlyNonEmptyArrayFromArray,
matchOption(
() => {
throw new Error(
`PFDCM is not configured with any services (besides "default")`,
);
},
(some) => some,
),
TE.flatMap(validateNonEmptyStringArray),
);
}

private find(
service: string,
query: PACSqueryCore,
): TE.TaskEither<Error, PypxFind> {
private async find(service: string, query: PACSqueryCore): Promise<PypxFind> {
const params: PACSPypxApiV1PACSSyncPypxPostRequest = {
bodyPACSPypxApiV1PACSSyncPypxPost: {
pACSservice: {
@@ -69,32 +69,31 @@ class PfdcmClient {
pACSdirective: query,
},
};
return pipe(
TE.tryCatch(
() => this.qrClient.pACSPypxApiV1PACSSyncPypxPost(params),
E.toError,
),
TE.flatMap(validateFindResponseData),
TE.flatMap(validateStatusIsTrue),
);
const data = await this.qrClient.pACSPypxApiV1PACSSyncPypxPost(params);
if (!isFindResponseData(data)) {
throw new Error("Unrecognizable response from PFDCM");
}
raiseForBadStatus(data);
return data;
}

/**
* Search for PACS data.
* @param service which PACS service to search for. See {@link PfdcmClient.getPacsServices}
* @param query PACS query
*/
public query(
public async query(
service: string,
query: PACSqueryCore,
): TE.TaskEither<Error, ReadonlyArray<StudyAndSeries>> {
return pipe(this.find(service, query), TE.map(simplifyResponse));
): Promise<ReadonlyArray<StudyAndSeries>> {
const data = await this.find(service, query);
return simplifyResponse(data);
}

public retrieve(
public async retrieve(
service: string,
query: PACSqueryCore,
): TE.TaskEither<Error, PACSasync> {
): Promise<PACSasync> {
const params: PACSServiceHandlerApiV1PACSThreadPypxPostRequest = {
bodyPACSServiceHandlerApiV1PACSThreadPypxPost: {
pACSservice: {
@@ -110,29 +109,14 @@ class PfdcmClient {
},
},
};
return pipe(
TE.tryCatch(
() => this.qrClient.pACSServiceHandlerApiV1PACSThreadPypxPost(params),
E.toError,
),
TE.flatMap((data) => {
// @ts-ignore OpenAPI spec of PFDCM is incomplete
if (data.response.job.status) {
return TE.right(data);
}
const error = new Error("PYPX job status is missing or false");
return TE.left(error);
}),
);
}
}

function validateFindResponseData(data: any): TE.TaskEither<Error, PypxFind> {
if (isFindResponseData(data)) {
return TE.right(data);
const res =
await this.qrClient.pACSServiceHandlerApiV1PACSThreadPypxPost(params);
// @ts-expect-error PFDCM OpenAPI spec is incomplete
if (!res.response?.job?.status) {
throw new Error("PYPX job status is missing or false");
}
return res;
}
const error = new Error("Invalid response from PFDCM");
return TE.left(error);
}

function isFindResponseData(data: any): data is PypxFind {
@@ -142,33 +126,30 @@ function isFindResponseData(data: any): data is PypxFind {
}

/**
* Validate that all the "status" fields are `true`
* Throw an error for any "status" field that is not `true`
* (this is a convention that Rudolph uses for error handling
* instead of HTTP status codes, exceptions, and/or monads).
*/
function validateStatusIsTrue(data: PypxFind): TE.TaskEither<Error, PypxFind> {
function raiseForBadStatus(data: PypxFind): PypxFind {
if (!data.status) {
const error = new Error("PFDCM response status=false");
return TE.left(error);
throw new Error("PFDCM response status=false");
}
if (data.pypx.status !== "success") {
const error = new Error("PFDCM response pypx.status=false");
return TE.left(error);
throw new Error("PFDCM response pypx.status=false");
}
for (const study of data.pypx.data) {
if (!Array.isArray(study.series)) {
continue;
}
for (const series of study.series) {
if (series.status.value !== "success") {
const error = new Error(
throw new Error(
`PFDCM response pypx...status is false for SeriesInstanceUID=${series?.SeriesInstanceUID?.value}`,
);
return TE.left(error);
}
}
}
return TE.right(data);
return data;
}

/**
Loading
Oops, something went wrong.