Skip to content
Merged
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
2 changes: 2 additions & 0 deletions packages/admin/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { SetsList } from './components/sets/SetsList.component';
import { StudyMappingList } from './components/studymapping/StudyMappingList.component';
import { BrowserRouter } from 'react-router-dom';
import { authProvider, JWT_TOKEN_KEY } from './auth';
import { DownloadsList } from './components/downloads/DownloadsLists.component';

function App() {
const httpClient = (url: string, options: fetchUtils.Options = {}) => {
Expand All @@ -29,6 +30,7 @@ function App() {
<Resource name="tasks" list={TasksLists} />
<Resource name="taskCompletions" list={TaskCompletionsList} />
<Resource name="studymapping" list={StudyMappingList} />
<Resource name="downloads" list={DownloadsList} />
</Admin>
</ClientProvider>
</BrowserRouter>
Expand Down
93 changes: 92 additions & 1 deletion packages/admin/src/client/sdk.gen.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// This file is auto-generated by @hey-api/openapi-ts

import { type Options as ClientOptions, type TDataShape, type Client, formDataBodySerializer } from '@hey-api/client-fetch';
import type { AppControllerGetHelloData, CasdoorControllerHandleRedirectData, CasdoorControllerHandleSigninData, TasksControllerFindAllData, TasksControllerFindAllResponse, TasksControllerCreateData, TasksControllerCreateResponse, TasksControllerRemoveData, TasksControllerFindOneData, TasksControllerFindOneResponse, TasksControllerUpdateData, TasksControllerUpdateResponse, TasksControllerGetActiveTasksData, TasksControllerGetActiveTasksResponse, TaskSetControllerFindAllData, TaskSetControllerFindAllResponse, TaskSetControllerCreateData, TaskSetControllerCreateResponse, TaskSetControllerRemoveData, TaskSetControllerFindOneData, TaskSetControllerFindOneResponse, TaskSetControllerUpdateData, TaskSetControllerUpdateResponse, TaskSetControllerSetActiveData, TaskSetControllerSetActiveResponse, TaskCompletionsControllerRemoveData, TaskCompletionsControllerFindAllData, TaskCompletionsControllerFindAllResponse, TaskCompletionsControllerUpdateData, TaskCompletionsControllerUpdateResponse, TaskCompletionsControllerCreateData, TaskCompletionsControllerCreateResponse, TaskCompletionsControllerFindOneData, TaskCompletionsControllerFindOneResponse, TaskCompletionsControllerFindOrCreateByUserTaskData, TaskCompletionsControllerFindOrCreateByUserTaskResponse, TaskCompletionsControllerFindOrCreateByTaskData, TaskCompletionsControllerFindOrCreateByTaskResponse, TaskCompletionsControllerGetNextIncompleteData, TaskCompletionsControllerGetNextIncompleteResponse, TaskCompletionsControllerGetVideoUploadUrlData, TaskCompletionsControllerGetVideoUploadUrlResponse, TaskCompletionsControllerGetVideoDownloadUrlData, TaskCompletionsControllerGetVideoDownloadUrlResponse, TaskCompletionsControllerDeleteVideoData, UsersControllerFindAllData, UsersControllerFindOneData, StudymappingControllerFindAllData, StudymappingControllerFindAllResponse, StudymappingControllerWebhookData, StudymappingControllerUploadCsvData } from './types.gen';
import type { AppControllerGetHelloData, CasdoorControllerHandleRedirectData, CasdoorControllerHandleSigninData, TasksControllerFindAllData, TasksControllerFindAllResponse, TasksControllerCreateData, TasksControllerCreateResponse, TasksControllerRemoveData, TasksControllerFindOneData, TasksControllerFindOneResponse, TasksControllerUpdateData, TasksControllerUpdateResponse, TasksControllerGetActiveTasksData, TasksControllerGetActiveTasksResponse, TaskSetControllerFindAllData, TaskSetControllerFindAllResponse, TaskSetControllerCreateData, TaskSetControllerCreateResponse, TaskSetControllerRemoveData, TaskSetControllerFindOneData, TaskSetControllerFindOneResponse, TaskSetControllerUpdateData, TaskSetControllerUpdateResponse, TaskSetControllerSetActiveData, TaskSetControllerSetActiveResponse, TaskCompletionsControllerRemoveData, TaskCompletionsControllerFindAllData, TaskCompletionsControllerFindAllResponse, TaskCompletionsControllerUpdateData, TaskCompletionsControllerUpdateResponse, TaskCompletionsControllerCreateData, TaskCompletionsControllerCreateResponse, TaskCompletionsControllerFindOneData, TaskCompletionsControllerFindOneResponse, TaskCompletionsControllerFindOrCreateByUserTaskData, TaskCompletionsControllerFindOrCreateByUserTaskResponse, TaskCompletionsControllerFindOrCreateByTaskData, TaskCompletionsControllerFindOrCreateByTaskResponse, TaskCompletionsControllerGetNextIncompleteData, TaskCompletionsControllerGetNextIncompleteResponse, TaskCompletionsControllerGetVideoUploadUrlData, TaskCompletionsControllerGetVideoUploadUrlResponse, TaskCompletionsControllerGetVideoDownloadUrlData, TaskCompletionsControllerGetVideoDownloadUrlResponse, TaskCompletionsControllerDeleteVideoData, UsersControllerFindAllData, UsersControllerFindOneData, UsersControllerIsTrainingCompleteData, UsersControllerIsTrainingCompleteResponse, UsersControllerMarkTrainingCompleteData, StudymappingControllerFindAllData, StudymappingControllerFindAllResponse, StudymappingControllerWebhookData, StudymappingControllerUploadCsvData, DownloadsControllerFindAllData, DownloadsControllerFindAllResponse, DownloadsControllerCreateData, DownloadsControllerCreateResponse, DownloadsControllerRemoveData, DownloadsControllerFindOneData, DownloadsControllerFindOneResponse, DownloadsControllerGetDownloadUrlData, DownloadsControllerGetDownloadUrlResponse } from './types.gen';
import { client as _heyApiClient } from './client.gen';

export type Options<TData extends TDataShape = TDataShape, ThrowOnError extends boolean = boolean> = ClientOptions<TData, ThrowOnError> & {
Expand Down Expand Up @@ -406,6 +406,32 @@ export const usersControllerFindOne = <ThrowOnError extends boolean = false>(opt
});
};

export const usersControllerIsTrainingComplete = <ThrowOnError extends boolean = false>(options: Options<UsersControllerIsTrainingCompleteData, ThrowOnError>) => {
return (options.client ?? _heyApiClient).get<UsersControllerIsTrainingCompleteResponse, unknown, ThrowOnError>({
security: [
{
scheme: 'bearer',
type: 'http'
}
],
url: '/users/training-complete/{id}',
...options
});
};

export const usersControllerMarkTrainingComplete = <ThrowOnError extends boolean = false>(options: Options<UsersControllerMarkTrainingCompleteData, ThrowOnError>) => {
return (options.client ?? _heyApiClient).put<unknown, unknown, ThrowOnError>({
security: [
{
scheme: 'bearer',
type: 'http'
}
],
url: '/users/training-complete/{id}',
...options
});
};

export const studymappingControllerFindAll = <ThrowOnError extends boolean = false>(options?: Options<StudymappingControllerFindAllData, ThrowOnError>) => {
return (options?.client ?? _heyApiClient).get<StudymappingControllerFindAllResponse, unknown, ThrowOnError>({
security: [
Expand Down Expand Up @@ -452,4 +478,69 @@ export const studymappingControllerUploadCsv = <ThrowOnError extends boolean = f
...options?.headers
}
});
};

export const downloadsControllerFindAll = <ThrowOnError extends boolean = false>(options?: Options<DownloadsControllerFindAllData, ThrowOnError>) => {
return (options?.client ?? _heyApiClient).get<DownloadsControllerFindAllResponse, unknown, ThrowOnError>({
security: [
{
scheme: 'bearer',
type: 'http'
}
],
url: '/downloads',
...options
});
};

export const downloadsControllerCreate = <ThrowOnError extends boolean = false>(options?: Options<DownloadsControllerCreateData, ThrowOnError>) => {
return (options?.client ?? _heyApiClient).post<DownloadsControllerCreateResponse, unknown, ThrowOnError>({
security: [
{
scheme: 'bearer',
type: 'http'
}
],
url: '/downloads',
...options
});
};

export const downloadsControllerRemove = <ThrowOnError extends boolean = false>(options: Options<DownloadsControllerRemoveData, ThrowOnError>) => {
return (options.client ?? _heyApiClient).delete<unknown, unknown, ThrowOnError>({
security: [
{
scheme: 'bearer',
type: 'http'
}
],
url: '/downloads/{id}',
...options
});
};

export const downloadsControllerFindOne = <ThrowOnError extends boolean = false>(options: Options<DownloadsControllerFindOneData, ThrowOnError>) => {
return (options.client ?? _heyApiClient).get<DownloadsControllerFindOneResponse, unknown, ThrowOnError>({
security: [
{
scheme: 'bearer',
type: 'http'
}
],
url: '/downloads/{id}',
...options
});
};

export const downloadsControllerGetDownloadUrl = <ThrowOnError extends boolean = false>(options: Options<DownloadsControllerGetDownloadUrlData, ThrowOnError>) => {
return (options.client ?? _heyApiClient).get<DownloadsControllerGetDownloadUrlResponse, unknown, ThrowOnError>({
security: [
{
scheme: 'bearer',
type: 'http'
}
],
url: '/downloads/download/url',
...options
});
};
130 changes: 130 additions & 0 deletions packages/admin/src/client/types.gen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,12 @@ export type CreateTaskDto = {
* User provided ID for the task
*/
descriptor: string;
/**
* Optional content image
*/
contentImage: {
[key: string]: unknown;
};
};

export type TaskEntity = {
Expand Down Expand Up @@ -106,6 +112,10 @@ export type TaskEntity = {
* User provided ID for the task
*/
descriptor: string;
/**
* Optional content image
*/
contentImage: string | null;
};

export type UpdateTaskDto = {
Expand Down Expand Up @@ -214,6 +224,10 @@ export type UpdateTaskCompletionDto = {
userId?: string;
};

export type HasComplete = {
complete: boolean;
};

export type WebhookPayload = {
[key: string]: unknown;
};
Expand All @@ -237,6 +251,25 @@ export type StudyMappingEntity = {
region: string;
};

export type DownloadEntity = {
/**
* Unique ID of the download
*/
id: string;
/**
* The status of the download
*/
status: 'STARTING' | 'IN_PROGRESS' | 'COMPLETE' | 'FAILED';
/**
* Where the download is located in the download bucket
*/
location: string;
/**
* When the download request was made
*/
createdAt: string;
};

export type AppControllerGetHelloData = {
body?: never;
path?: never;
Expand Down Expand Up @@ -650,6 +683,34 @@ export type UsersControllerFindOneResponses = {
200: unknown;
};

export type UsersControllerIsTrainingCompleteData = {
body?: never;
path: {
id: string;
};
query?: never;
url: '/users/training-complete/{id}';
};

export type UsersControllerIsTrainingCompleteResponses = {
default: HasComplete;
};

export type UsersControllerIsTrainingCompleteResponse = UsersControllerIsTrainingCompleteResponses[keyof UsersControllerIsTrainingCompleteResponses];

export type UsersControllerMarkTrainingCompleteData = {
body?: never;
path: {
id: string;
};
query?: never;
url: '/users/training-complete/{id}';
};

export type UsersControllerMarkTrainingCompleteResponses = {
200: unknown;
};

export type StudymappingControllerFindAllData = {
body?: never;
path?: never;
Expand Down Expand Up @@ -687,6 +748,75 @@ export type StudymappingControllerUploadCsvResponses = {
201: unknown;
};

export type DownloadsControllerFindAllData = {
body?: never;
path?: never;
query?: never;
url: '/downloads';
};

export type DownloadsControllerFindAllResponses = {
default: Array<DownloadEntity>;
};

export type DownloadsControllerFindAllResponse = DownloadsControllerFindAllResponses[keyof DownloadsControllerFindAllResponses];

export type DownloadsControllerCreateData = {
body?: never;
path?: never;
query?: never;
url: '/downloads';
};

export type DownloadsControllerCreateResponses = {
default: DownloadEntity;
};

export type DownloadsControllerCreateResponse = DownloadsControllerCreateResponses[keyof DownloadsControllerCreateResponses];

export type DownloadsControllerRemoveData = {
body?: never;
path: {
id: string;
};
query?: never;
url: '/downloads/{id}';
};

export type DownloadsControllerRemoveResponses = {
200: unknown;
};

export type DownloadsControllerFindOneData = {
body?: never;
path: {
id: string;
};
query?: never;
url: '/downloads/{id}';
};

export type DownloadsControllerFindOneResponses = {
default: DownloadEntity;
};

export type DownloadsControllerFindOneResponse = DownloadsControllerFindOneResponses[keyof DownloadsControllerFindOneResponses];

export type DownloadsControllerGetDownloadUrlData = {
body?: never;
path?: never;
query: {
downloadLocation: string;
};
url: '/downloads/download/url';
};

export type DownloadsControllerGetDownloadUrlResponses = {
default: string;
};

export type DownloadsControllerGetDownloadUrlResponse = DownloadsControllerGetDownloadUrlResponses[keyof DownloadsControllerGetDownloadUrlResponses];

export type ClientOptions = {
baseUrl: string;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { Button } from 'react-admin';
import { FC, useEffect, useState } from 'react';
import { useRecordContext } from 'react-admin';
import { downloadsControllerGetDownloadUrl } from '../../client';

export const DownloadButton: FC<{ source: string }> = ({ source }) => {
const record = useRecordContext();
if (!record) {
return null;
}

const [url, setUrl] = useState<string | null>(null);

const getDownloadURL = async () => {
const urlResponse = await downloadsControllerGetDownloadUrl({
query: { downloadLocation: record[source] }
});

if (urlResponse.error || !urlResponse.data) {
// TODO: Handle inability to get video URL
console.error(urlResponse.error);
return;
}

setUrl(urlResponse.data);
};

useEffect(() => {
getDownloadURL();
}, []);

const onDownload = (url: string) => {
const link = document.createElement('a');
link.download = record[source].split('/')[1];
link.href = url;
link.click();
};

return (
url && (
<Button variant="contained" disabled={record['status'] != 'COMPLETE'} onClick={() => onDownload(url)}>
Download
</Button>
)
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { Button, Datagrid, DateField, List, TextField, useRefresh } from 'react-admin';
import { FC } from 'react';
import { Stack } from '@mui/material';
import { downloadsControllerCreate } from '../../client';
import { DownloadButton } from './DownloadButton.component';

export const DownloadsList: FC = () => {
const refresh = useRefresh();

const handleDownloadRequest = async () => {
const downloadResponse = await downloadsControllerCreate();

if (downloadResponse.error || !downloadResponse.data) {
console.error(downloadResponse.error);
alert('Failed to make a download request');
return;
}

alert('Download in progress');
refresh();
};

return (
<Stack direction="column" sx={{ alignContent: 'center', alignItems: 'center' }}>
<Button onClick={handleDownloadRequest} variant="contained" sx={{ maxWidth: 300 }}>
Request Download
</Button>
<List sort={{ field: 'createdAt', order: 'DESC' }}>
<Datagrid>
<TextField source="status" />
<DateField source="createdAt" showTime={true} />
<DownloadButton source="location" />
</Datagrid>
</List>
</Stack>
);
};
Loading
Loading