Skip to content
Open
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
19 changes: 19 additions & 0 deletions packages/common/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1753,6 +1753,7 @@ export class LMSCourseIntegrationPartial {
lmsSynchronize!: boolean
isExpired!: boolean
selectedResourceTypes?: LMSResourceType[]
moduleLinkedPagesOnly?: boolean
}

export type LMSCourseAPIResponse = {
Expand Down Expand Up @@ -1790,6 +1791,7 @@ export type LMSPage = {
syncEnabled?: boolean
modified?: Date
uploaded?: Date
isModuleLinked?: boolean
}

export type LMSFile = {
Expand All @@ -1803,6 +1805,23 @@ export type LMSFile = {
uploaded?: Date
}

export type LMSModule = {
id: number
name: string
items_url?: string
items?: LMSModuleItem[]
}

export type LMSModuleItem = {
id: number
title: string
type: string
content_id?: number
html_url?: string
url?: string
page_url?: string
}

export enum LMSQuizAccessLevel {
LOGISTICS_ONLY = 'logistics_only',
LOGISTICS_AND_QUESTIONS = 'logistics_and_questions',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,12 +127,17 @@ export default function CourseLMSIntegrationPage(props: {
const [delModalOpen, setDelModalOpen] = useState<boolean>(false)
const [isTesting, setIsTesting] = useState<boolean>(false)
const [selectedResources, setSelectedResources] = useState<string[]>([])
const [moduleLinkedPagesOnly, setModuleLinkedPagesOnly] =
useState<boolean>(false)

useEffect(() => {
if (integration?.selectedResourceTypes) {
setSelectedResources(integration.selectedResourceTypes)
}
}, [integration?.selectedResourceTypes])
if (integration?.moduleLinkedPagesOnly !== undefined) {
setModuleLinkedPagesOnly(integration.moduleLinkedPagesOnly)
}
}, [integration?.selectedResourceTypes, integration?.moduleLinkedPagesOnly])

const onSelectedResourcesChange: GetProp<
typeof Checkbox.Group,
Expand All @@ -145,9 +150,18 @@ export default function CourseLMSIntegrationPage(props: {
if (!integration?.selectedResourceTypes) return false
const currentSorted = [...selectedResources].sort()
const dbSorted = [...integration.selectedResourceTypes].sort()
if (currentSorted.length !== dbSorted.length) return true
return currentSorted.some((item, index) => item !== dbSorted[index])
}, [selectedResources, integration?.selectedResourceTypes])
const resourcesChanged =
currentSorted.length !== dbSorted.length ||
currentSorted.some((item, index) => item !== dbSorted[index])
const moduleLinkedChanged =
moduleLinkedPagesOnly !== integration.moduleLinkedPagesOnly
return resourcesChanged || moduleLinkedChanged
}, [
selectedResources,
integration?.selectedResourceTypes,
moduleLinkedPagesOnly,
integration?.moduleLinkedPagesOnly,
])

const fetchOrgIntegrationsAsync = useCallback(async () => {
await API.lmsIntegration
Expand Down Expand Up @@ -286,16 +300,42 @@ export default function CourseLMSIntegrationPage(props: {
}

const handleSaveAndResync = async () => {
try {
await API.lmsIntegration.updateSelectedResourceTypes(
courseId,
selectedResources as string[],
)
message.success('Resource types updated!')
await forceSync()
setUpdateFlag(!updateFlag)
} catch (err) {
message.error(getErrorMessage(err))
const wasModuleLinkedDisabled = !integration?.moduleLinkedPagesOnly
const isEnablingModuleLinked =
wasModuleLinkedDisabled && moduleLinkedPagesOnly

const performUpdate = async () => {
try {
await API.lmsIntegration.updateSelectedResourceTypes(
courseId,
selectedResources as string[],
)

await API.lmsIntegration.updateModuleLinkedPagesOnly(
courseId,
moduleLinkedPagesOnly,
)

message.success('Settings updated!')
await forceSync()
setUpdateFlag(!updateFlag)
} catch (err) {
message.error(getErrorMessage(err))
}
}

if (isEnablingModuleLinked) {
Modal.confirm({
title: 'Enable Module-linked Pages Only?',
content:
'Enabling "Module-linked pages only" will remove any standalone pages (not linked in course modules) from the chatbot. ' +
'Only pages that are specifically linked within course modules will remain available to the chatbot.',
okText: 'Continue',
cancelText: 'Cancel',
onOk: performUpdate,
})
} else {
await performUpdate()
}
}

Expand Down Expand Up @@ -810,43 +850,78 @@ export default function CourseLMSIntegrationPage(props: {
label: 'Resource Selector',
key: '2',
children: (
<div className={'flex flex-col items-center gap-2'}>
<div
className={
'flex flex-col items-center gap-4 px-4 py-2'
}
>
<Checkbox.Group
style={{ width: '100%' }}
onChange={onSelectedResourcesChange}
value={selectedResources}
>
<Row gutter={[16, 16]}>
<Col xs={24} sm={12} md={8}>
<Checkbox value="assignments">
Assignments
</Checkbox>
<Row gutter={[24, 16]}>
<Col xs={24} sm={12} lg={8}>
<div className="flex min-h-[50px] items-center p-3">
<Checkbox value="assignments">
Assignments
</Checkbox>
</div>
</Col>
<Col xs={24} sm={12} md={8}>
<Checkbox value="announcements">
Announcements
</Checkbox>
<Col xs={24} sm={12} lg={8}>
<div className="flex min-h-[50px] items-center p-3">
<Checkbox value="announcements">
Announcements
</Checkbox>
</div>
</Col>
<Col xs={24} sm={12} md={8}>
<Checkbox value="files">Files</Checkbox>
<Col xs={24} sm={12} lg={8}>
<div className="flex min-h-[50px] items-center p-3">
<Checkbox value="files">Files</Checkbox>
</div>
</Col>
<Col xs={24} sm={12} md={8}>
<Checkbox value="pages">Pages</Checkbox>
<Col xs={24} sm={12} lg={8}>
<div className="flex min-h-[50px] items-center p-3">
<Checkbox value="pages">Pages</Checkbox>
</div>
</Col>
<Col xs={24} sm={12} md={8}>
<Checkbox value="quizzes">Quizzes</Checkbox>
<Col xs={24} sm={12} lg={8}>
<div className="flex min-h-[50px] items-center p-3">
<Checkbox value="quizzes">Quizzes</Checkbox>
</div>
</Col>
<Col xs={24} sm={12} md={8}>
<Checkbox value="Syllabus" disabled={true}>
<Tooltip title="Coming Soon!">
<span className="text-gray-400 line-through">
Syllabus
</span>
</Tooltip>
</Checkbox>
<Col xs={24} sm={12} lg={8}>
<div className="flex min-h-[50px] items-center p-3">
<Checkbox value="Syllabus" disabled={true}>
<Tooltip title="Coming Soon!">
<span className="text-gray-400 line-through">
Syllabus
</span>
</Tooltip>
</Checkbox>
</div>
</Col>
</Row>
</Checkbox.Group>

{/* Module-linked pages toggle */}
{selectedResources.includes('pages') && (
<div className="mt-4 w-full rounded-lg bg-gray-50 p-3">
<Checkbox
checked={moduleLinkedPagesOnly}
onChange={(e) => {
setModuleLinkedPagesOnly(e.target.checked)
}}
>
<Tooltip title="Only sync pages that are linked within course modules. When enabled, standalone pages will be excluded from the chatbot. WARNING: This will remove any previously synced standalone pages from the chatbot when you save changes.">
<span className="text-sm font-medium">
Module-linked pages only
</span>
</Tooltip>
</Checkbox>
</div>
)}

<Button
size={'large'}
shape={'round'}
Expand All @@ -866,7 +941,7 @@ export default function CourseLMSIntegrationPage(props: {
style={{ marginTop: '30px' }}
onClick={handleSaveAndResync}
loading={syncing && integration.lmsSynchronize}
className="w-full sm:w-auto"
className="w-full sm:w-auto md:max-w-xs lg:max-w-sm"
>
<span className="hidden sm:inline">
Save and Re-Sync Documents
Expand Down
10 changes: 10 additions & 0 deletions packages/frontend/app/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1440,6 +1440,16 @@ class APIClient {
this.req('POST', `/api/v1/lms/course/${courseId}/resources`, undefined, {
selectedResourceTypes,
}),
updateModuleLinkedPagesOnly: async (
courseId: number,
moduleLinkedPagesOnly: boolean,
): Promise<string> =>
this.req(
'PUT',
`/api/v1/lms/course/${courseId}/module-pages-only`,
undefined,
{ moduleLinkedPagesOnly },
),
testIntegration: async (
courseId: number,
props: TestLMSIntegrationParams,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,9 @@ export class LMSCourseIntegrationModel extends BaseEntity {
@OneToMany((type) => LMSPageModel, (page) => page.course)
pages: LMSPageModel[];

@Column({ type: 'boolean', default: false })
moduleLinkedPagesOnly: boolean;

@OneToMany((type) => LMSFileModel, (file) => file.course)
files: LMSFileModel[];

Expand Down
Loading