From 44f3d514dee260f4227b8f8b6baad8d200be62ac Mon Sep 17 00:00:00 2001 From: Chris Rowland Date: Tue, 20 Jul 2021 15:20:54 -0400 Subject: [PATCH 01/22] Wire up get course sections w/ create sections FE --- ccm_web/client/src/App.tsx | 2 +- ccm_web/client/src/api.ts | 25 ++++++++----------- ccm_web/client/src/models/FeatureUIData.tsx | 6 +++-- ccm_web/client/src/models/canvas.ts | 6 +++++ .../client/src/pages/BulkSectionCreate.tsx | 7 +++--- 5 files changed, 26 insertions(+), 20 deletions(-) diff --git a/ccm_web/client/src/App.tsx b/ccm_web/client/src/App.tsx index 6d4dc211d..cae1e4178 100644 --- a/ccm_web/client/src/App.tsx +++ b/ccm_web/client/src/App.tsx @@ -103,7 +103,7 @@ function App (props: AppProps): JSX.Element { ()} /> {features.map(feature => { - return + return }/> })} (
Under Construction
)} />
diff --git a/ccm_web/client/src/api.ts b/ccm_web/client/src/api.ts index 4a3d970ad..151a81fd7 100644 --- a/ccm_web/client/src/api.ts +++ b/ccm_web/client/src/api.ts @@ -1,4 +1,4 @@ -import { CanvasCourseBase } from './models/canvas' +import { CanvasCourseBase, CanvasCourseSection } from './models/canvas' import { Globals } from './models/models' import handleErrors from './utils/handleErrors' @@ -55,18 +55,15 @@ export const getGlobals = async (key: string | undefined): Promise => { return await resp.json() } -const delay = async (ms: number): Promise => { - await new Promise(resolve => setTimeout(() => resolve(), ms)) -} +// usage: +// const data = await delay(nnnn).then(() => {return something}) +// const delay = async (ms: number): Promise => { +// await new Promise(resolve => setTimeout(() => resolve(), ms)) +// } -// This is a placeholder for a real implementation (I mean, obviously :D) -export const getCourseSections = async (key: string | undefined, courseId: string): Promise => { - const sections = await delay(2000).then(() => { - if (Math.random() * 3 > 1) { - return (['AAAA', 'BBBB']) - } else { - return new Promise((resolve, reject) => { reject(new Error('Error retrieving course section information.')) }) - } - }) - return sections +export const getCourseSections = async (key: string | undefined, courseId: number): Promise => { + const request = getGet(key) + const resp = await fetch('/api/course/' + courseId.toString() + '/sections', request) + await handleErrors(resp) + return await resp.json() } diff --git a/ccm_web/client/src/models/FeatureUIData.tsx b/ccm_web/client/src/models/FeatureUIData.tsx index b7178964c..a28d4d11c 100644 --- a/ccm_web/client/src/models/FeatureUIData.tsx +++ b/ccm_web/client/src/models/FeatureUIData.tsx @@ -11,9 +11,11 @@ import ConvertCanvasGradebook from '../pages/GradebookCanvas' import MergeSections from '../pages/MergeSections' import BulkSectionCreate from '../pages/BulkSectionCreate' import { LtiProps } from '../api' -import { RoleEnum } from './models' +import { Globals, RoleEnum } from './models' -export interface CCMComponentProps extends LtiProps {} +export interface CCMComponentProps extends LtiProps { + globals: Globals +} interface FeatureUIGroup { id: string diff --git a/ccm_web/client/src/models/canvas.ts b/ccm_web/client/src/models/canvas.ts index c7f857cc5..cc904a126 100644 --- a/ccm_web/client/src/models/canvas.ts +++ b/ccm_web/client/src/models/canvas.ts @@ -2,3 +2,9 @@ export interface CanvasCourseBase { id: number name: string } + +export interface CanvasCourseSection { + id: number + name: string + // total_students?: number +} diff --git a/ccm_web/client/src/pages/BulkSectionCreate.tsx b/ccm_web/client/src/pages/BulkSectionCreate.tsx index 022a3b2c6..af765fef4 100644 --- a/ccm_web/client/src/pages/BulkSectionCreate.tsx +++ b/ccm_web/client/src/pages/BulkSectionCreate.tsx @@ -11,6 +11,7 @@ import { CCMComponentProps } from '../models/FeatureUIData' import usePromise from '../hooks/usePromise' import { DuplicateSectionInFileSectionRowsValidator, hasHeader, InvalidationType, SectionNameHeaderValidator, SectionRowsValidator, SectionsRowInvalidation, SectionsSchemaInvalidation, SectionsSchemaValidator } from '../components/BulkSectionCreateValidators' import ExampleFileDownloadHeader, { ExampleFileDownloadHeaderProps } from '../components/ExampleFileDownloadHeader' +import { CanvasCourseSection } from '../models/canvas' const useStyles = makeStyles((theme) => ({ root: { @@ -154,8 +155,8 @@ function BulkSectionCreate (props: BulkSectionCreateProps): JSX.Element { const [existingSectionNames, setExistingSectionNames] = useState(undefined) const [doLoadCanvasSectionData, isExistingSectionsLoading, getCanvasSectionDataError] = usePromise( - async () => await getCourseSections(props.ltiKey, 'TODO-CourseNumberFromProps?'), - (value: string[]) => setExistingSectionNames(value.map(s => { return s.toUpperCase() })) + async () => await getCourseSections(props.ltiKey, props.globals.course.id), + (value: CanvasCourseSection[]) => setExistingSectionNames(value.map(s => { return s.name.toUpperCase() })) ) useEffect(() => { @@ -415,7 +416,7 @@ Section 001` Review your CSV file Your file is valid! If this looks correct proceed with download - + From 04bcb1aa02497ac92f669a76ea6dfe7dc91e2c50 Mon Sep 17 00:00:00 2001 From: Chris Rowland Date: Thu, 22 Jul 2021 15:43:57 -0400 Subject: [PATCH 02/22] Wire up adding sections --- ccm_web/client/src/api.ts | 19 ++++ .../client/src/pages/BulkSectionCreate.tsx | 89 +++++++++++++++++-- ccm_web/client/tsconfig.json | 2 +- 3 files changed, 100 insertions(+), 10 deletions(-) diff --git a/ccm_web/client/src/api.ts b/ccm_web/client/src/api.ts index 151a81fd7..ae7d108f0 100644 --- a/ccm_web/client/src/api.ts +++ b/ccm_web/client/src/api.ts @@ -23,6 +23,16 @@ const getGet = (key: string | undefined): RequestInit => { return request } +const getPost = (key: string | undefined, body: string): RequestInit => { + const headers: string[][] = [] + headers.push(['Content-Type', 'application/json']) + headers.push(['Accept', 'application/json']) + const request = initRequest(key, headers) + request.method = 'POST' + request.body = body + return request +} + // This currently assumes all put requests have a JSON payload and receive a JSON response. const getPut = (key: string | undefined, body: string): RequestInit => { const headers: string[][] = [] @@ -67,3 +77,12 @@ export const getCourseSections = async (key: string | undefined, courseId: numbe await handleErrors(resp) return await resp.json() } + +export const addCourseSections = async (key: string | undefined, courseId: number, sectionNames: string[]): Promise => { + console.log('addCourseSections') + const body = JSON.stringify({ sections: sectionNames }) + const request = getPost(key, body) + const resp = await fetch('/api/course/' + courseId.toString() + '/sections', request) + await handleErrors(resp) + return await resp.json() +} diff --git a/ccm_web/client/src/pages/BulkSectionCreate.tsx b/ccm_web/client/src/pages/BulkSectionCreate.tsx index af765fef4..d89a5cc5c 100644 --- a/ccm_web/client/src/pages/BulkSectionCreate.tsx +++ b/ccm_web/client/src/pages/BulkSectionCreate.tsx @@ -1,8 +1,8 @@ -import { Backdrop, Box, Button, CircularProgress, Grid, makeStyles, Paper, Typography } from '@material-ui/core' -import CloudDoneIcon from '@material-ui/icons/CloudDone' +import { Backdrop, Box, Button, Card, CardActions, CardContent, CircularProgress, Grid, makeStyles, Paper, Typography } from '@material-ui/core' +import { CloudDone as CloudDoneIcon, CheckCircle } from '@material-ui/icons' import ErrorIcon from '@material-ui/icons/Error' import React, { useEffect, useState } from 'react' -import { getCourseSections } from '../api' +import { addCourseSections, getCourseSections } from '../api' import BulkSectionCreateUploadConfirmationTable, { Section } from '../components/BulkSectionCreateUploadConfirmationTable' import FileUpload from '../components/FileUpload' import ValidationErrorTable from '../components/ValidationErrorTable' @@ -21,6 +21,11 @@ const useStyles = makeStyles((theme) => ({ margin: 5 } }, + confirmContainer: { + position: 'relative', + zIndex: 0, + textAlign: 'center' + }, uploadContainer: { position: 'relative', zIndex: 0, @@ -125,13 +130,34 @@ const useAPIErrorStyles = makeStyles((theme) => ({ } })) +const useSuccessStyles = makeStyles((theme) => ({ + card: { + textAlign: 'center' + }, + cardContent: { + }, + cardFooter: { + display: 'block', + backgroundColor: '#F7F7F7', + textAlign: 'center' + }, + cardFooterText: { + textAlign: 'center' + }, + icon: { + color: '#306430', + width: 100, + height: 100 + } +})) + enum BulkSectionCreatePageState { LoadingExistingSectionNames, LoadingExistingSectionNamesFailed, Upload, InvalidUpload, Confirm, - Done + Success } interface BulkSectionCreatePageStateData { @@ -148,6 +174,7 @@ function BulkSectionCreate (props: BulkSectionCreateProps): JSX.Element { const rowLevelErrorClasses = useRowLevelErrorStyles() const topLevelClasses = useTopLevelErrorStyles() const apiErrorClasses = useAPIErrorStyles() + const successClasses = useSuccessStyles() const [pageState, setPageState] = useState({ state: BulkSectionCreatePageState.LoadingExistingSectionNames, schemaInvalidation: [], rowInvalidations: [] }) const [file, setFile] = useState(undefined) @@ -158,14 +185,26 @@ function BulkSectionCreate (props: BulkSectionCreateProps): JSX.Element { async () => await getCourseSections(props.ltiKey, props.globals.course.id), (value: CanvasCourseSection[]) => setExistingSectionNames(value.map(s => { return s.name.toUpperCase() })) ) + useEffect(() => { + setPageState({ state: BulkSectionCreatePageState.LoadingExistingSectionNamesFailed, schemaInvalidation: [], rowInvalidations: [] }) + }, [getCanvasSectionDataError]) useEffect(() => { void doLoadCanvasSectionData() }, []) + const [doSaveCanvasSectionData, isSaveCanvasSectionDataLoading, getSaveCanvasSectionDataError] = usePromise( + async () => await addCourseSections(props.ltiKey, props.globals.course.id, sectionNames), + (newSections: CanvasCourseSection[]) => { + const originalSectionNames: string[] = (existingSectionNames != null) ? existingSectionNames : [] + setExistingSectionNames([...new Set([...originalSectionNames, ...newSections.map(newSection => { return newSection.name.toUpperCase() })])]) + setPageState({ state: BulkSectionCreatePageState.Success, schemaInvalidation: [], rowInvalidations: [] }) + } + ) + useEffect(() => { setPageState({ state: BulkSectionCreatePageState.LoadingExistingSectionNamesFailed, schemaInvalidation: [], rowInvalidations: [] }) - }, [getCanvasSectionDataError]) + }, [getSaveCanvasSectionDataError]) class DuplicateExistingSectionRowsValidator implements SectionRowsValidator { validate = (sectionNames: string[]): SectionsRowInvalidation[] => { @@ -275,6 +314,14 @@ Section 001` return () } + const renderLoadingText = (): JSX.Element => { + if (isExistingSectionsLoading) { + return (Loading Section Information) + } else { + return (Saving Section Information) + } + } + const renderFileUpload = (): JSX.Element => { return
@@ -288,7 +335,7 @@ Section 001` - Loading Section Information + {renderLoadingText()} @@ -402,7 +449,7 @@ Section 001` const renderConfirm = (sectionNames: Section[]): JSX.Element => { return ( -
+
{renderCSVFileName()} @@ -417,10 +464,21 @@ Section 001` Your file is valid! If this looks correct proceed with download + + + + + + + + {renderLoadingText()} + + +
) } @@ -428,6 +486,19 @@ Section 001` return sectionNames.map((name, i) => { return { rowNumber: i + 1, sectionName: name } }) } + const renderSuccess = (): JSX.Element => { + return ( + + + + New sections have been added! + + + See your sections in the Canvas Setting + + + ) + } const renderComponent = (): JSX.Element => { switch (pageState.state) { case BulkSectionCreatePageState.LoadingExistingSectionNames: @@ -440,8 +511,8 @@ Section 001` return renderInvalidUpload() case BulkSectionCreatePageState.Confirm: return renderConfirm(sectionNamesToSection(sectionNames)) - case BulkSectionCreatePageState.Done: - return (
DONE
) + case BulkSectionCreatePageState.Success: + return renderSuccess() default: return
?
} diff --git a/ccm_web/client/tsconfig.json b/ccm_web/client/tsconfig.json index cf2097413..8b023497d 100644 --- a/ccm_web/client/tsconfig.json +++ b/ccm_web/client/tsconfig.json @@ -1,7 +1,7 @@ { "extends": "../tsconfig.json", "compilerOptions": { - "target": "es5", + "target": "es6", "lib": [ "dom", "dom.iterable", From 64071b7e939439cd3b4f3dd898495d4eab40d67e Mon Sep 17 00:00:00 2001 From: Chris Rowland Date: Tue, 27 Jul 2021 11:08:35 -0400 Subject: [PATCH 03/22] Handle error display --- ccm_web/client/src/api.ts | 1 - ccm_web/client/src/models/models.ts | 4 + .../client/src/pages/BulkSectionCreate.tsx | 73 +++++++++++++++---- ccm_web/client/src/utils/handleErrors.ts | 6 +- 4 files changed, 68 insertions(+), 16 deletions(-) diff --git a/ccm_web/client/src/api.ts b/ccm_web/client/src/api.ts index ae7d108f0..0d3e8a127 100644 --- a/ccm_web/client/src/api.ts +++ b/ccm_web/client/src/api.ts @@ -79,7 +79,6 @@ export const getCourseSections = async (key: string | undefined, courseId: numbe } export const addCourseSections = async (key: string | undefined, courseId: number, sectionNames: string[]): Promise => { - console.log('addCourseSections') const body = JSON.stringify({ sections: sectionNames }) const request = getPost(key, body) const resp = await fetch('/api/course/' + courseId.toString() + '/sections', request) diff --git a/ccm_web/client/src/models/models.ts b/ccm_web/client/src/models/models.ts index 835273e0d..c4ddbb2e9 100644 --- a/ccm_web/client/src/models/models.ts +++ b/ccm_web/client/src/models/models.ts @@ -42,3 +42,7 @@ export interface APIErrorData { statusCode: number errors: APIErrorPayload[] } + +export interface IDefaultError { + errors: APIErrorPayload[] +} diff --git a/ccm_web/client/src/pages/BulkSectionCreate.tsx b/ccm_web/client/src/pages/BulkSectionCreate.tsx index d89a5cc5c..10908f9bd 100644 --- a/ccm_web/client/src/pages/BulkSectionCreate.tsx +++ b/ccm_web/client/src/pages/BulkSectionCreate.tsx @@ -1,6 +1,5 @@ import { Backdrop, Box, Button, Card, CardActions, CardContent, CircularProgress, Grid, makeStyles, Paper, Typography } from '@material-ui/core' -import { CloudDone as CloudDoneIcon, CheckCircle } from '@material-ui/icons' -import ErrorIcon from '@material-ui/icons/Error' +import { CloudDone as CloudDoneIcon, CheckCircle, Error as ErrorIcon, Warning } from '@material-ui/icons' import React, { useEffect, useState } from 'react' import { addCourseSections, getCourseSections } from '../api' import BulkSectionCreateUploadConfirmationTable, { Section } from '../components/BulkSectionCreateUploadConfirmationTable' @@ -12,6 +11,7 @@ import usePromise from '../hooks/usePromise' import { DuplicateSectionInFileSectionRowsValidator, hasHeader, InvalidationType, SectionNameHeaderValidator, SectionRowsValidator, SectionsRowInvalidation, SectionsSchemaInvalidation, SectionsSchemaValidator } from '../components/BulkSectionCreateValidators' import ExampleFileDownloadHeader, { ExampleFileDownloadHeaderProps } from '../components/ExampleFileDownloadHeader' import { CanvasCourseSection } from '../models/canvas' +import { APIErrorPayload, IDefaultError } from '../models/models' const useStyles = makeStyles((theme) => ({ root: { @@ -80,8 +80,11 @@ const useRowLevelErrorStyles = makeStyles((theme) => ({ paddingLeft: 10, paddingRight: 10 }, - dialogIcon: { - color: 'red' + errorIcon: { + color: '#3F648E' + }, + warningIcon: { + color: '#e2cf2a' } })) @@ -103,7 +106,7 @@ const useTopLevelErrorStyles = makeStyles((theme) => ({ } }, dialogIcon: { - color: 'red' + color: '#3F648E' } })) @@ -126,7 +129,7 @@ const useAPIErrorStyles = makeStyles((theme) => ({ } }, dialogIcon: { - color: 'red' + color: '#3F648E' } })) @@ -157,7 +160,13 @@ enum BulkSectionCreatePageState { Upload, InvalidUpload, Confirm, - Success + CreateSectionsSuccess, + CreateSectionsError +} + +enum ErrorType { + Error, + Warning } interface BulkSectionCreatePageStateData { @@ -198,12 +207,26 @@ function BulkSectionCreate (props: BulkSectionCreateProps): JSX.Element { (newSections: CanvasCourseSection[]) => { const originalSectionNames: string[] = (existingSectionNames != null) ? existingSectionNames : [] setExistingSectionNames([...new Set([...originalSectionNames, ...newSections.map(newSection => { return newSection.name.toUpperCase() })])]) - setPageState({ state: BulkSectionCreatePageState.Success, schemaInvalidation: [], rowInvalidations: [] }) + setPageState({ state: BulkSectionCreatePageState.CreateSectionsSuccess, schemaInvalidation: [], rowInvalidations: [] }) } ) + const convertErrorsToRowInvalidations = (apiError: APIErrorPayload[]): SectionsRowInvalidation[] => { + const invalidations: SectionsRowInvalidation[] = [] + if (apiError !== undefined) { + apiError.forEach(e => { + invalidations.push({ message: '"' + (e.failedInput !== null ? e.failedInput : 'UNKNOWN') + '" ' + e.message, rowNumber: e.failedInput !== null ? sectionNames.indexOf(e.failedInput) + 1 : -1, type: InvalidationType.Error }) + }) + } + return invalidations + } + useEffect(() => { - setPageState({ state: BulkSectionCreatePageState.LoadingExistingSectionNamesFailed, schemaInvalidation: [], rowInvalidations: [] }) + if (getSaveCanvasSectionDataError !== undefined) { + const errors = (getSaveCanvasSectionDataError as unknown as IDefaultError).errors + const rowInvalidations = convertErrorsToRowInvalidations(errors) + setPageState({ state: BulkSectionCreatePageState.CreateSectionsError, schemaInvalidation: [], rowInvalidations: rowInvalidations }) + } }, [getSaveCanvasSectionDataError]) class DuplicateExistingSectionRowsValidator implements SectionRowsValidator { @@ -371,7 +394,15 @@ Section 001` return } - const renderRowLevelErrors = (invalidations: SectionsRowInvalidation[]): JSX.Element => { + const renderErrorIcon = (errorType: ErrorType): JSX.Element => { + if (errorType === ErrorType.Error) { + return () + } else { + return () + } + } + + const renderRowLevelErrors = (invalidations: SectionsRowInvalidation[], title: string, errorType: ErrorType): JSX.Element => { return (
{renderCSVFileName()} @@ -384,8 +415,8 @@ Section 001` - Review your CSV file - + {title} + {renderErrorIcon(errorType)} Correct the file and{renderUploadAgainButton()} @@ -419,7 +450,7 @@ Section 001` let rowLevelErrors: JSX.Element | undefined let schemaLevelErrors: JSX.Element | undefined if (pageState.rowInvalidations.length > 0) { - rowLevelErrors = renderRowLevelErrors(pageState.rowInvalidations) + rowLevelErrors = renderRowLevelErrors(pageState.rowInvalidations, 'Review your CSV file', ErrorType.Error) } if (pageState.schemaInvalidation.length > 0) { const schemaErrors: JSX.Element[] = pageState.schemaInvalidation.map((invalidation, i) => { @@ -435,6 +466,18 @@ Section 001` ) } + const renderPartialSuccess = (): JSX.Element => { + let rowLevelErrors: JSX.Element | undefined + if (pageState.rowInvalidations.length > 0) { + rowLevelErrors = renderRowLevelErrors(pageState.rowInvalidations, 'Some errors occurred', ErrorType.Warning) + } + return ( +
+ {rowLevelErrors !== undefined && rowLevelErrors} +
+ ) + } + const renderAPIError = (): JSX.Element => { return ( @@ -511,8 +554,10 @@ Section 001` return renderInvalidUpload() case BulkSectionCreatePageState.Confirm: return renderConfirm(sectionNamesToSection(sectionNames)) - case BulkSectionCreatePageState.Success: + case BulkSectionCreatePageState.CreateSectionsSuccess: return renderSuccess() + case BulkSectionCreatePageState.CreateSectionsError: + return renderPartialSuccess() default: return
?
} diff --git a/ccm_web/client/src/utils/handleErrors.ts b/ccm_web/client/src/utils/handleErrors.ts index de2493f6b..e11cd2aa5 100644 --- a/ccm_web/client/src/utils/handleErrors.ts +++ b/ccm_web/client/src/utils/handleErrors.ts @@ -33,7 +33,11 @@ class NotFoundError extends Error { } } -class DefaultError extends Error { +interface IDefaultError { + errors: APIErrorPayload[] +} + +class DefaultError extends Error implements IDefaultError { public name = 'DefaultError' errors: APIErrorPayload[] From 76a141941589bb016a171686e074176c528e692b Mon Sep 17 00:00:00 2001 From: Chris Rowland Date: Wed, 28 Jul 2021 11:06:33 -0400 Subject: [PATCH 04/22] File name alignment fix --- ccm_web/client/src/pages/BulkSectionCreate.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ccm_web/client/src/pages/BulkSectionCreate.tsx b/ccm_web/client/src/pages/BulkSectionCreate.tsx index 10908f9bd..0e2055ae4 100644 --- a/ccm_web/client/src/pages/BulkSectionCreate.tsx +++ b/ccm_web/client/src/pages/BulkSectionCreate.tsx @@ -492,9 +492,9 @@ Section 001` const renderConfirm = (sectionNames: Section[]): JSX.Element => { return ( -
+
{renderCSVFileName()} - + From 0af6590f3f3e551748d6426c026619b10a95e79f Mon Sep 17 00:00:00 2001 From: Chris Rowland Date: Wed, 28 Jul 2021 11:10:46 -0400 Subject: [PATCH 05/22] File name alignment fix --- ccm_web/client/src/pages/BulkSectionCreate.tsx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/ccm_web/client/src/pages/BulkSectionCreate.tsx b/ccm_web/client/src/pages/BulkSectionCreate.tsx index 0e2055ae4..fd00c05e3 100644 --- a/ccm_web/client/src/pages/BulkSectionCreate.tsx +++ b/ccm_web/client/src/pages/BulkSectionCreate.tsx @@ -39,7 +39,8 @@ const useStyles = makeStyles((theme) => ({ fileNameContainer: { marginBottom: 15, paddingLeft: 10, - paddingRight: 10 + paddingRight: 10, + textAlign: 'left' }, fileName: { color: '#3F648E', @@ -492,9 +493,9 @@ Section 001` const renderConfirm = (sectionNames: Section[]): JSX.Element => { return ( -
+
{renderCSVFileName()} - + From 69c4ecdd1e72d9ba5ab7482bbb1ec045748eb6b9 Mon Sep 17 00:00:00 2001 From: Chris Rowland Date: Mon, 2 Aug 2021 14:22:57 -0400 Subject: [PATCH 06/22] Refactor flow. Apply context free validation on file upload. Apply validation requiring awareness of server data on submit. --- .../client/src/pages/BulkSectionCreate.tsx | 100 +++++++++++------- 1 file changed, 64 insertions(+), 36 deletions(-) diff --git a/ccm_web/client/src/pages/BulkSectionCreate.tsx b/ccm_web/client/src/pages/BulkSectionCreate.tsx index fd00c05e3..06aacf68f 100644 --- a/ccm_web/client/src/pages/BulkSectionCreate.tsx +++ b/ccm_web/client/src/pages/BulkSectionCreate.tsx @@ -156,13 +156,13 @@ const useSuccessStyles = makeStyles((theme) => ({ })) enum BulkSectionCreatePageState { - LoadingExistingSectionNames, + UploadPending, LoadingExistingSectionNamesFailed, - Upload, InvalidUpload, - Confirm, + Submit, CreateSectionsSuccess, - CreateSectionsError + CreateSectionsError, + Saving } enum ErrorType { @@ -186,32 +186,47 @@ function BulkSectionCreate (props: BulkSectionCreateProps): JSX.Element { const apiErrorClasses = useAPIErrorStyles() const successClasses = useSuccessStyles() - const [pageState, setPageState] = useState({ state: BulkSectionCreatePageState.LoadingExistingSectionNames, schemaInvalidation: [], rowInvalidations: [] }) + const [pageState, setPageState] = useState({ state: BulkSectionCreatePageState.UploadPending, schemaInvalidation: [], rowInvalidations: [] }) const [file, setFile] = useState(undefined) const [sectionNames, setSectionNames] = useState([]) const [existingSectionNames, setExistingSectionNames] = useState(undefined) const [doLoadCanvasSectionData, isExistingSectionsLoading, getCanvasSectionDataError] = usePromise( async () => await getCourseSections(props.ltiKey, props.globals.course.id), - (value: CanvasCourseSection[]) => setExistingSectionNames(value.map(s => { return s.name.toUpperCase() })) + (value: CanvasCourseSection[]) => { + const es = value.map(s => { return s.name.toUpperCase() }) + setExistingSectionNames(es) + } ) useEffect(() => { setPageState({ state: BulkSectionCreatePageState.LoadingExistingSectionNamesFailed, schemaInvalidation: [], rowInvalidations: [] }) }, [getCanvasSectionDataError]) useEffect(() => { - void doLoadCanvasSectionData() - }, []) + if (pageState.state === BulkSectionCreatePageState.Saving) { + const serverInvalidations = doServerValidation() + if (serverInvalidations.length !== 0) { + handleRowLevelInvalidationError(serverInvalidations) + } else { + void doSaveCanvasSectionData() + } + } + }, [existingSectionNames]) const [doSaveCanvasSectionData, isSaveCanvasSectionDataLoading, getSaveCanvasSectionDataError] = usePromise( async () => await addCourseSections(props.ltiKey, props.globals.course.id, sectionNames), (newSections: CanvasCourseSection[]) => { const originalSectionNames: string[] = (existingSectionNames != null) ? existingSectionNames : [] - setExistingSectionNames([...new Set([...originalSectionNames, ...newSections.map(newSection => { return newSection.name.toUpperCase() })])]) setPageState({ state: BulkSectionCreatePageState.CreateSectionsSuccess, schemaInvalidation: [], rowInvalidations: [] }) + setExistingSectionNames([...new Set([...originalSectionNames, ...newSections.map(newSection => { return newSection.name.toUpperCase() })])]) } ) + const submit = async (): Promise => { + setPageState({ state: BulkSectionCreatePageState.Saving, schemaInvalidation: [], rowInvalidations: [] }) + void doLoadCanvasSectionData() + } + const convertErrorsToRowInvalidations = (apiError: APIErrorPayload[]): SectionsRowInvalidation[] => { const invalidations: SectionsRowInvalidation[] = [] if (apiError !== undefined) { @@ -258,11 +273,26 @@ function BulkSectionCreate (props: BulkSectionCreateProps): JSX.Element { } useEffect(() => { - parseUpload(file) + if (file === undefined) { + resetPageState() + } else { + parseFile(file) + } }, [file]) + useEffect(() => { + if (sectionNames.length > 0) { + const clientInvalidations = doClientValidation() + if (clientInvalidations.length !== 0) { + handleRowLevelInvalidationError(clientInvalidations) + } else { + setPageState({ state: BulkSectionCreatePageState.Submit, schemaInvalidation: [], rowInvalidations: [] }) + } + } + }, [sectionNames]) + const resetPageState = (): void => { - setPageState({ state: BulkSectionCreatePageState.Upload, schemaInvalidation: [], rowInvalidations: [] }) + setPageState({ state: BulkSectionCreatePageState.UploadPending, schemaInvalidation: [], rowInvalidations: [] }) } const handleSchemaError = (schemaInvalidations: SectionsSchemaInvalidation[]): void => { @@ -275,14 +305,10 @@ function BulkSectionCreate (props: BulkSectionCreateProps): JSX.Element { const handleParseSuccess = (sectionNames: string[]): void => { setSectionNames(sectionNames) - setPageState({ state: BulkSectionCreatePageState.Confirm, schemaInvalidation: [], rowInvalidations: [] }) + setPageState({ state: BulkSectionCreatePageState.Submit, schemaInvalidation: [], rowInvalidations: [] }) } - const parseUpload = (file: File|undefined): void => { - if (file === undefined) { - resetPageState() - return - } + const parseFile = (file: File): void => { file.text().then(t => { let lines = t.split(/[\r\n]+/).map(line => { return line.trim() }) // An empty file will resultin 1 line @@ -303,20 +329,6 @@ function BulkSectionCreate (props: BulkSectionCreateProps): JSX.Element { if (hasHeader(lines)) { lines = lines.slice(1) } - - const rowInvalidations: SectionsRowInvalidation[] = [] - - const duplicateNamesInFileValidator: SectionRowsValidator = new DuplicateSectionInFileSectionRowsValidator() - rowInvalidations.push(...duplicateNamesInFileValidator.validate(lines)) - - const duplicatesNamesInCanvasValidator: DuplicateExistingSectionRowsValidator = new DuplicateExistingSectionRowsValidator() - rowInvalidations.push(...duplicatesNamesInCanvasValidator.validate(lines)) - - if (rowInvalidations.length !== 0) { - handleRowLevelInvalidationError(rowInvalidations) - return - } - handleParseSuccess(lines) }).catch(e => { // TODO Not sure how to produce this error in real life @@ -324,6 +336,23 @@ function BulkSectionCreate (props: BulkSectionCreateProps): JSX.Element { }) } + const doClientValidation = (): SectionsRowInvalidation[] => { + const rowInvalidations: SectionsRowInvalidation[] = [] + + const duplicateNamesInFileValidator: SectionRowsValidator = new DuplicateSectionInFileSectionRowsValidator() + rowInvalidations.push(...duplicateNamesInFileValidator.validate(sectionNames)) + + return rowInvalidations + } + + const doServerValidation = (): SectionsRowInvalidation[] => { + const rowInvalidations: SectionsRowInvalidation[] = [] + const duplicatesNamesInCanvasValidator: DuplicateExistingSectionRowsValidator = new DuplicateExistingSectionRowsValidator() + rowInvalidations.push(...duplicatesNamesInCanvasValidator.validate(sectionNames)) + + return rowInvalidations + } + const renderUploadHeader = (): JSX.Element => { const fileData = `SECTION_NAME @@ -508,7 +537,7 @@ Section 001` Your file is valid! If this looks correct proceed with download - + @@ -545,15 +574,14 @@ Section 001` } const renderComponent = (): JSX.Element => { switch (pageState.state) { - case BulkSectionCreatePageState.LoadingExistingSectionNames: + case BulkSectionCreatePageState.UploadPending: return renderUpload() case BulkSectionCreatePageState.LoadingExistingSectionNamesFailed: return renderAPIError() - case BulkSectionCreatePageState.Upload: - return renderUpload() case BulkSectionCreatePageState.InvalidUpload: return renderInvalidUpload() - case BulkSectionCreatePageState.Confirm: + case BulkSectionCreatePageState.Submit: + case BulkSectionCreatePageState.Saving: return renderConfirm(sectionNamesToSection(sectionNames)) case BulkSectionCreatePageState.CreateSectionsSuccess: return renderSuccess() From 4dd1d9ef5c51c08f440ff8581e793b1b6091e583 Mon Sep 17 00:00:00 2001 From: Chris Rowland Date: Wed, 4 Aug 2021 09:54:46 -0400 Subject: [PATCH 07/22] Uncoment a thus far unused property --- ccm_web/client/src/models/canvas.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ccm_web/client/src/models/canvas.ts b/ccm_web/client/src/models/canvas.ts index cc904a126..59bd832ed 100644 --- a/ccm_web/client/src/models/canvas.ts +++ b/ccm_web/client/src/models/canvas.ts @@ -6,5 +6,5 @@ export interface CanvasCourseBase { export interface CanvasCourseSection { id: number name: string - // total_students?: number + total_students?: number } From 586ffdb96f03425b9cb57dabdbbe0026a6744fe8 Mon Sep 17 00:00:00 2001 From: chrisrrowland Date: Wed, 4 Aug 2021 10:03:44 -0400 Subject: [PATCH 08/22] Update ccm_web/client/src/api.ts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Mr. Lance E Sloan «UMich» --- ccm_web/client/src/api.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/ccm_web/client/src/api.ts b/ccm_web/client/src/api.ts index 0d3e8a127..acf176811 100644 --- a/ccm_web/client/src/api.ts +++ b/ccm_web/client/src/api.ts @@ -24,9 +24,8 @@ const getGet = (key: string | undefined): RequestInit => { } const getPost = (key: string | undefined, body: string): RequestInit => { - const headers: string[][] = [] - headers.push(['Content-Type', 'application/json']) - headers.push(['Accept', 'application/json']) + const jsonType: string = 'application/json' + const headers: string[][] = [['Content-Type', jsonType], ['Accept', jsonType]] const request = initRequest(key, headers) request.method = 'POST' request.body = body From dfdbbf0aa9ad04fef46157ecd4113fed0fbc8866 Mon Sep 17 00:00:00 2001 From: Chris Rowland Date: Wed, 4 Aug 2021 10:28:03 -0400 Subject: [PATCH 09/22] Non functional tweaking of getPost getPut --- ccm_web/client/src/api.ts | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/ccm_web/client/src/api.ts b/ccm_web/client/src/api.ts index acf176811..36c3e9ace 100644 --- a/ccm_web/client/src/api.ts +++ b/ccm_web/client/src/api.ts @@ -6,6 +6,8 @@ export interface LtiProps { ltiKey: string | undefined } +const jsonMimeType = 'application/json' + const initRequest = (key: string | undefined, headers: string[][] = []): RequestInit => { if (key !== undefined) { headers.push(['Authorization', 'Bearer ' + key]) @@ -24,8 +26,7 @@ const getGet = (key: string | undefined): RequestInit => { } const getPost = (key: string | undefined, body: string): RequestInit => { - const jsonType: string = 'application/json' - const headers: string[][] = [['Content-Type', jsonType], ['Accept', jsonType]] + const headers: string[][] = [['Content-Type', jsonMimeType], ['Accept', jsonMimeType]] const request = initRequest(key, headers) request.method = 'POST' request.body = body @@ -34,9 +35,7 @@ const getPost = (key: string | undefined, body: string): RequestInit => { // This currently assumes all put requests have a JSON payload and receive a JSON response. const getPut = (key: string | undefined, body: string): RequestInit => { - const headers: string[][] = [] - headers.push(['Content-Type', 'application/json']) - headers.push(['Accept', 'application/json']) + const headers: string[][] = [['Content-Type', jsonMimeType], ['Accept', jsonMimeType]] const request = initRequest(key, headers) request.method = 'PUT' request.body = body From c551ff225e1a50d14ecc718b23c55cefd895dcdd Mon Sep 17 00:00:00 2001 From: Chris Rowland Date: Wed, 4 Aug 2021 10:28:33 -0400 Subject: [PATCH 10/22] Delete the forbidden comment --- ccm_web/client/src/api.ts | 6 ------ 1 file changed, 6 deletions(-) diff --git a/ccm_web/client/src/api.ts b/ccm_web/client/src/api.ts index 36c3e9ace..48a465aee 100644 --- a/ccm_web/client/src/api.ts +++ b/ccm_web/client/src/api.ts @@ -63,12 +63,6 @@ export const getGlobals = async (key: string | undefined): Promise => { return await resp.json() } -// usage: -// const data = await delay(nnnn).then(() => {return something}) -// const delay = async (ms: number): Promise => { -// await new Promise(resolve => setTimeout(() => resolve(), ms)) -// } - export const getCourseSections = async (key: string | undefined, courseId: number): Promise => { const request = getGet(key) const resp = await fetch('/api/course/' + courseId.toString() + '/sections', request) From 75c9da5a0af5c48b68abc58232d15b32bb4bc79a Mon Sep 17 00:00:00 2001 From: Chris Rowland Date: Wed, 4 Aug 2021 10:38:42 -0400 Subject: [PATCH 11/22] Reorder some imports ~asthetics~ --- ccm_web/client/src/pages/BulkSectionCreate.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ccm_web/client/src/pages/BulkSectionCreate.tsx b/ccm_web/client/src/pages/BulkSectionCreate.tsx index 06aacf68f..a370ee78e 100644 --- a/ccm_web/client/src/pages/BulkSectionCreate.tsx +++ b/ccm_web/client/src/pages/BulkSectionCreate.tsx @@ -5,12 +5,12 @@ import { addCourseSections, getCourseSections } from '../api' import BulkSectionCreateUploadConfirmationTable, { Section } from '../components/BulkSectionCreateUploadConfirmationTable' import FileUpload from '../components/FileUpload' import ValidationErrorTable from '../components/ValidationErrorTable' -import { createSectionsProps } from '../models/feature' -import { CCMComponentProps } from '../models/FeatureUIData' import usePromise from '../hooks/usePromise' import { DuplicateSectionInFileSectionRowsValidator, hasHeader, InvalidationType, SectionNameHeaderValidator, SectionRowsValidator, SectionsRowInvalidation, SectionsSchemaInvalidation, SectionsSchemaValidator } from '../components/BulkSectionCreateValidators' import ExampleFileDownloadHeader, { ExampleFileDownloadHeaderProps } from '../components/ExampleFileDownloadHeader' import { CanvasCourseSection } from '../models/canvas' +import { createSectionsProps } from '../models/feature' +import { CCMComponentProps } from '../models/FeatureUIData' import { APIErrorPayload, IDefaultError } from '../models/models' const useStyles = makeStyles((theme) => ({ From 2ab72f62ce0daa5a69b4baf10a7d0cfbed679daa Mon Sep 17 00:00:00 2001 From: Chris Rowland Date: Wed, 4 Aug 2021 11:36:41 -0400 Subject: [PATCH 12/22] Remove an unused css style --- ccm_web/client/src/pages/BulkSectionCreate.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/ccm_web/client/src/pages/BulkSectionCreate.tsx b/ccm_web/client/src/pages/BulkSectionCreate.tsx index a370ee78e..380fa009d 100644 --- a/ccm_web/client/src/pages/BulkSectionCreate.tsx +++ b/ccm_web/client/src/pages/BulkSectionCreate.tsx @@ -138,8 +138,6 @@ const useSuccessStyles = makeStyles((theme) => ({ card: { textAlign: 'center' }, - cardContent: { - }, cardFooter: { display: 'block', backgroundColor: '#F7F7F7', @@ -562,7 +560,7 @@ Section 001` const renderSuccess = (): JSX.Element => { return ( - + New sections have been added! From 6df77d6ddbfaa149fe2995da62bbe7743b58763a Mon Sep 17 00:00:00 2001 From: Chris Rowland Date: Wed, 4 Aug 2021 11:41:57 -0400 Subject: [PATCH 13/22] rename a variable --- ccm_web/client/src/pages/BulkSectionCreate.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ccm_web/client/src/pages/BulkSectionCreate.tsx b/ccm_web/client/src/pages/BulkSectionCreate.tsx index 380fa009d..21287da5b 100644 --- a/ccm_web/client/src/pages/BulkSectionCreate.tsx +++ b/ccm_web/client/src/pages/BulkSectionCreate.tsx @@ -192,8 +192,8 @@ function BulkSectionCreate (props: BulkSectionCreateProps): JSX.Element { const [doLoadCanvasSectionData, isExistingSectionsLoading, getCanvasSectionDataError] = usePromise( async () => await getCourseSections(props.ltiKey, props.globals.course.id), (value: CanvasCourseSection[]) => { - const es = value.map(s => { return s.name.toUpperCase() }) - setExistingSectionNames(es) + const existingSuggestions = value.map(s => { return s.name.toUpperCase() }) + setExistingSectionNames(existingSuggestions) } ) useEffect(() => { From f03449bfeea4ee91b67805e7256035d14c25f6db Mon Sep 17 00:00:00 2001 From: Chris Rowland Date: Thu, 5 Aug 2021 10:07:15 -0400 Subject: [PATCH 14/22] Only change to LoadingExistingSectionNamesFailed if getCanvasSectionDataError is defined --- ccm_web/client/src/pages/BulkSectionCreate.tsx | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/ccm_web/client/src/pages/BulkSectionCreate.tsx b/ccm_web/client/src/pages/BulkSectionCreate.tsx index 21287da5b..8770a3131 100644 --- a/ccm_web/client/src/pages/BulkSectionCreate.tsx +++ b/ccm_web/client/src/pages/BulkSectionCreate.tsx @@ -197,7 +197,9 @@ function BulkSectionCreate (props: BulkSectionCreateProps): JSX.Element { } ) useEffect(() => { - setPageState({ state: BulkSectionCreatePageState.LoadingExistingSectionNamesFailed, schemaInvalidation: [], rowInvalidations: [] }) + if (getCanvasSectionDataError !== undefined) { + setPageState({ state: BulkSectionCreatePageState.LoadingExistingSectionNamesFailed, schemaInvalidation: [], rowInvalidations: [] }) + } }, [getCanvasSectionDataError]) useEffect(() => { @@ -365,10 +367,10 @@ Section 001` return () } - const renderLoadingText = (): JSX.Element => { + const renderLoadingText = (): JSX.Element | undefined => { if (isExistingSectionsLoading) { return (Loading Section Information) - } else { + } else if (isSaveCanvasSectionDataLoading) { return (Saving Section Information) } } @@ -419,7 +421,7 @@ Section 001` const renderTryAgainButton = (): JSX.Element => { // eslint-disable-next-line no-void - return + return } const renderErrorIcon = (errorType: ErrorType): JSX.Element => { From d951d165bf2ed29c54a9a5f2400a17c061d1208d Mon Sep 17 00:00:00 2001 From: Chris Rowland Date: Thu, 5 Aug 2021 10:10:53 -0400 Subject: [PATCH 15/22] Prevent multiple submits --- ccm_web/client/src/pages/BulkSectionCreate.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/ccm_web/client/src/pages/BulkSectionCreate.tsx b/ccm_web/client/src/pages/BulkSectionCreate.tsx index 8770a3131..53a2cb0b9 100644 --- a/ccm_web/client/src/pages/BulkSectionCreate.tsx +++ b/ccm_web/client/src/pages/BulkSectionCreate.tsx @@ -222,6 +222,10 @@ function BulkSectionCreate (props: BulkSectionCreateProps): JSX.Element { } ) + const isSubmitting = (): boolean => { + return (isExistingSectionsLoading || isSaveCanvasSectionDataLoading) + } + const submit = async (): Promise => { setPageState({ state: BulkSectionCreatePageState.Saving, schemaInvalidation: [], rowInvalidations: [] }) void doLoadCanvasSectionData() @@ -537,7 +541,7 @@ Section 001` Your file is valid! If this looks correct proceed with download - + From 6a60c85716890db950df5b7138eaf1834eea3319 Mon Sep 17 00:00:00 2001 From: chrisrrowland Date: Thu, 5 Aug 2021 10:12:25 -0400 Subject: [PATCH 16/22] Update ccm_web/client/src/pages/BulkSectionCreate.tsx Suggested formatting change Co-authored-by: Sam Sciolla <35741256+ssciolla@users.noreply.github.com> --- ccm_web/client/src/pages/BulkSectionCreate.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ccm_web/client/src/pages/BulkSectionCreate.tsx b/ccm_web/client/src/pages/BulkSectionCreate.tsx index 53a2cb0b9..0e97739f0 100644 --- a/ccm_web/client/src/pages/BulkSectionCreate.tsx +++ b/ccm_web/client/src/pages/BulkSectionCreate.tsx @@ -235,7 +235,7 @@ function BulkSectionCreate (props: BulkSectionCreateProps): JSX.Element { const invalidations: SectionsRowInvalidation[] = [] if (apiError !== undefined) { apiError.forEach(e => { - invalidations.push({ message: '"' + (e.failedInput !== null ? e.failedInput : 'UNKNOWN') + '" ' + e.message, rowNumber: e.failedInput !== null ? sectionNames.indexOf(e.failedInput) + 1 : -1, type: InvalidationType.Error }) + invalidations.push({ message: `"${e.failedInput !== null ? e.failedInput : 'UNKNOWN'}" ${e.message}`, rowNumber: e.failedInput !== null ? sectionNames.indexOf(e.failedInput) + 1 : -1, type: InvalidationType.Error }) }) } return invalidations From 5ef8b47093c4948c9595c0c21a423ec1b03479e5 Mon Sep 17 00:00:00 2001 From: chrisrrowland Date: Thu, 5 Aug 2021 10:13:00 -0400 Subject: [PATCH 17/22] Update ccm_web/client/src/pages/BulkSectionCreate.tsx Suggested formatting change Co-authored-by: Sam Sciolla <35741256+ssciolla@users.noreply.github.com> --- ccm_web/client/src/pages/BulkSectionCreate.tsx | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/ccm_web/client/src/pages/BulkSectionCreate.tsx b/ccm_web/client/src/pages/BulkSectionCreate.tsx index 0e97739f0..3dc1a90cb 100644 --- a/ccm_web/client/src/pages/BulkSectionCreate.tsx +++ b/ccm_web/client/src/pages/BulkSectionCreate.tsx @@ -505,11 +505,7 @@ Section 001` if (pageState.rowInvalidations.length > 0) { rowLevelErrors = renderRowLevelErrors(pageState.rowInvalidations, 'Some errors occurred', ErrorType.Warning) } - return ( -
- {rowLevelErrors !== undefined && rowLevelErrors} -
- ) + return
{rowLevelErrors !== undefined && rowLevelErrors}
} const renderAPIError = (): JSX.Element => { From 4503bbbb183c85b95bee342074a5e10c2d72e350 Mon Sep 17 00:00:00 2001 From: chrisrrowland Date: Thu, 5 Aug 2021 10:15:34 -0400 Subject: [PATCH 18/22] Update ccm_web/client/src/pages/BulkSectionCreate.tsx Fix alert text Co-authored-by: Sam Sciolla <35741256+ssciolla@users.noreply.github.com> --- ccm_web/client/src/pages/BulkSectionCreate.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ccm_web/client/src/pages/BulkSectionCreate.tsx b/ccm_web/client/src/pages/BulkSectionCreate.tsx index 3dc1a90cb..439188db5 100644 --- a/ccm_web/client/src/pages/BulkSectionCreate.tsx +++ b/ccm_web/client/src/pages/BulkSectionCreate.tsx @@ -535,7 +535,7 @@ Section 001` Review your CSV file - Your file is valid! If this looks correct proceed with download + Your file is valid! If this looks correct, click "Submit" to proceed. From 4bd27964aa13ef9db0efb0efb819d3a853cdb835 Mon Sep 17 00:00:00 2001 From: Chris Rowland Date: Thu, 5 Aug 2021 10:24:23 -0400 Subject: [PATCH 19/22] Escape quotes in text --- ccm_web/client/src/pages/BulkSectionCreate.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ccm_web/client/src/pages/BulkSectionCreate.tsx b/ccm_web/client/src/pages/BulkSectionCreate.tsx index 439188db5..99c275173 100644 --- a/ccm_web/client/src/pages/BulkSectionCreate.tsx +++ b/ccm_web/client/src/pages/BulkSectionCreate.tsx @@ -535,7 +535,7 @@ Section 001` Review your CSV file - Your file is valid! If this looks correct, click "Submit" to proceed. + Your file is valid! If this looks correct, click "Submit" to proceed. From e70ddfbcbd2b107db1251589909425800f9f440e Mon Sep 17 00:00:00 2001 From: Chris Rowland Date: Thu, 5 Aug 2021 10:31:25 -0400 Subject: [PATCH 20/22] Learning to spell basic words --- ccm_web/client/src/pages/BulkSectionCreate.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ccm_web/client/src/pages/BulkSectionCreate.tsx b/ccm_web/client/src/pages/BulkSectionCreate.tsx index 99c275173..ba3e4e4f1 100644 --- a/ccm_web/client/src/pages/BulkSectionCreate.tsx +++ b/ccm_web/client/src/pages/BulkSectionCreate.tsx @@ -537,7 +537,7 @@ Section 001` Your file is valid! If this looks correct, click "Submit" to proceed. - +
From 27b081db093a4f552d9f6dfdbccd775fae377bda Mon Sep 17 00:00:00 2001 From: Chris Rowland Date: Thu, 5 Aug 2021 10:33:58 -0400 Subject: [PATCH 21/22] Get rid of redundant code. Need to get better at javascript --- ccm_web/client/src/pages/BulkSectionCreate.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ccm_web/client/src/pages/BulkSectionCreate.tsx b/ccm_web/client/src/pages/BulkSectionCreate.tsx index ba3e4e4f1..0b946970e 100644 --- a/ccm_web/client/src/pages/BulkSectionCreate.tsx +++ b/ccm_web/client/src/pages/BulkSectionCreate.tsx @@ -537,7 +537,7 @@ Section 001` Your file is valid! If this looks correct, click "Submit" to proceed. - + From 06dfdfdbe25bafbbe7b2b5427ec5f797405093c3 Mon Sep 17 00:00:00 2001 From: Sam Sciolla <35741256+ssciolla@users.noreply.github.com> Date: Thu, 5 Aug 2021 11:31:25 -0400 Subject: [PATCH 22/22] Add Canvas settings URL (#3) * Send Canvas URL in /api/globals; wire up BulkSectionCreate to use it for settings link * Use Link component; add target _parent --- ccm_web/client/src/models/models.ts | 1 + ccm_web/client/src/pages/BulkSectionCreate.tsx | 6 ++++-- ccm_web/server/src/api/api.interfaces.ts | 1 + ccm_web/server/src/api/api.module.ts | 3 ++- ccm_web/server/src/api/api.service.ts | 4 +++- 5 files changed, 11 insertions(+), 4 deletions(-) diff --git a/ccm_web/client/src/models/models.ts b/ccm_web/client/src/models/models.ts index c4ddbb2e9..59c81306d 100644 --- a/ccm_web/client/src/models/models.ts +++ b/ccm_web/client/src/models/models.ts @@ -28,6 +28,7 @@ export interface Course { export interface Globals { environment: 'production' | 'development' + canvasURL: string userLoginId: string course: Course } diff --git a/ccm_web/client/src/pages/BulkSectionCreate.tsx b/ccm_web/client/src/pages/BulkSectionCreate.tsx index 0b946970e..7cf599717 100644 --- a/ccm_web/client/src/pages/BulkSectionCreate.tsx +++ b/ccm_web/client/src/pages/BulkSectionCreate.tsx @@ -1,4 +1,4 @@ -import { Backdrop, Box, Button, Card, CardActions, CardContent, CircularProgress, Grid, makeStyles, Paper, Typography } from '@material-ui/core' +import {Backdrop, Box, Button, Card, CardActions, CardContent, CircularProgress, Grid, Link, makeStyles, Paper, Typography } from '@material-ui/core' import { CloudDone as CloudDoneIcon, CheckCircle, Error as ErrorIcon, Warning } from '@material-ui/icons' import React, { useEffect, useState } from 'react' import { addCourseSections, getCourseSections } from '../api' @@ -560,6 +560,8 @@ Section 001` } const renderSuccess = (): JSX.Element => { + const { canvasURL, course } = props.globals + const settingsURL = `${canvasURL}/courses/${course.id}/settings` return ( @@ -567,7 +569,7 @@ Section 001` New sections have been added! - See your sections in the Canvas Setting + See your sections on the Canvas Settings page for your course. ) diff --git a/ccm_web/server/src/api/api.interfaces.ts b/ccm_web/server/src/api/api.interfaces.ts index d80ebbe4c..52dc27956 100644 --- a/ccm_web/server/src/api/api.interfaces.ts +++ b/ccm_web/server/src/api/api.interfaces.ts @@ -2,6 +2,7 @@ import { hasKeys } from '../typeUtils' export interface Globals { environment: 'production' | 'development' + canvasURL: string userLoginId: string course: { id: number diff --git a/ccm_web/server/src/api/api.module.ts b/ccm_web/server/src/api/api.module.ts index 01ed39106..0d5b9b227 100644 --- a/ccm_web/server/src/api/api.module.ts +++ b/ccm_web/server/src/api/api.module.ts @@ -1,4 +1,5 @@ import { Module } from '@nestjs/common' +import { ConfigModule } from '@nestjs/config' import { APIController } from './api.controller' import { APIService } from './api.service' @@ -6,7 +7,7 @@ import { CanvasModule } from '../canvas/canvas.module' import { CanvasService } from '../canvas/canvas.service' @Module({ - imports: [CanvasModule], + imports: [CanvasModule, ConfigModule], providers: [APIService, CanvasService], controllers: [APIController] }) diff --git a/ccm_web/server/src/api/api.service.ts b/ccm_web/server/src/api/api.service.ts index 8d86d26bf..984dcbf09 100644 --- a/ccm_web/server/src/api/api.service.ts +++ b/ccm_web/server/src/api/api.service.ts @@ -1,5 +1,6 @@ import { SessionData } from 'express-session' import { Injectable } from '@nestjs/common' +import { ConfigService } from '@nestjs/config' import { handleAPIError } from './api.utils' import { CanvasCourse, CanvasCourseBase, CanvasCourseSection } from '../canvas/canvas.interfaces' @@ -13,11 +14,12 @@ const logger = baseLogger.child({ filePath: __filename }) @Injectable() export class APIService { - constructor (private readonly canvasService: CanvasService) {} + constructor (private readonly canvasService: CanvasService, private readonly configService: ConfigService) {} getGlobals (sessionData: SessionData): Globals { return { environment: process.env.NODE_ENV === 'production' ? 'production' : 'development', + canvasURL: this.configService.get('canvas.instanceURL') as string, userLoginId: sessionData.data.userLoginId, course: { id: sessionData.data.course.id,