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

enhance: separate scheduling and publishing logic for microlearnings with cronjob #4289

Merged
merged 7 commits into from
Oct 4, 2024
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
6 changes: 4 additions & 2 deletions apps/docs/docs/tutorials/microlearning.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,9 @@ There are multiple options to add a question to a stack:
1. After you create a Microlearning session, you can find it under Courses. Then select the course to which you added the session.
2. In the course you have multiple options. You can...
- ...copy the access link and provide it to your participants.
- ...copy the access link to integrate the microlearning into your learning management system through LTI.
- ...edit your session before publishing it.
- ...publish it. Not that publishing a Practice Quiz or Microlearning session makes the item visible to all participants. This process cannot be undone. Changes to the content of an item cannot be made after publishing.
- ...delete your session.
- ...extend the duration of your microlearning, while it is still running.
- ...publish it. Note that the publishing process makes the microlearning available to all participants after the scheduled start date. Until then, the microlearning will have a "Scheduled" state and can be unpublished and edited again. The change from the scheduled state to the "Published" state is performed automatically at the scheduled start date and is irreversible.
- ...delete your microlearning with certain restrictions.
3. After publishing your session, your participants can see the session in their account by joining the course. In the app, the Microlearning session is displayed to the participants as shown in the screenshots below.
Original file line number Diff line number Diff line change
Expand Up @@ -102,11 +102,19 @@ function MicroLearningElement({
[PublicationStatus.Published]: (
<StatusTag
color="bg-green-300"
status={t('shared.generic.published')}
icon={isFuture ? faClock : isPast ? faCheck : faPlay}
status={
isPast ? t('shared.generic.completed') : t('shared.generic.published')
}
icon={isPast ? faCheck : faPlay}
/>
),
[PublicationStatus.Scheduled]: (
<StatusTag
color="bg-orange-200"
status={t('shared.generic.scheduled')}
icon={faClock}
/>
),
[PublicationStatus.Scheduled]: <div />,
}

return (
Expand Down Expand Up @@ -226,6 +234,64 @@ function MicroLearningElement({
</>
)}

{microLearning.status === PublicationStatus.Scheduled && (
<>
<MicroLearningAccessLink
microLearning={microLearning}
href={href}
/>
<Dropdown
data={{ cy: `microlearning-actions-${microLearning.name}` }}
className={{
item: 'p-1 hover:bg-gray-200',
viewport: 'bg-white',
}}
trigger={t('manage.course.otherActions')}
items={[
dataUser?.userProfile?.catalyst
? getLTIAccessLink({
href,
setCopyToast,
t,
name: microLearning.name,
})
: [],
// {
// label: (
// <MicroLearningPreviewLink
// microLearning={microLearning}
// href={href}
// />
// ),
// onClick: () => null,
// },
getActivityDuplicationAction({
id: microLearning.id,
text: t('manage.course.duplicateMicroLearning'),
wizardMode: WizardMode.Microlearning,
router: router,
data: {
cy: `duplicate-microlearning-${microLearning.name}`,
},
}),
{
label: (
<div className="flex cursor-pointer flex-row items-center gap-1 text-red-600">
<FontAwesomeIcon icon={faLock} className="w-4" />
<div>{t('manage.course.unpublishMicrolearning')}</div>
</div>
),
onClick: async () => await unpublishMicroLearning(),
data: {
cy: `unpublish-microlearning-${microLearning.name}`,
},
},
].flat()}
triggerIcon={faHandPointer}
/>
</>
)}

{microLearning.status === PublicationStatus.Published && (
<>
<MicroLearningAccessLink
Expand Down Expand Up @@ -296,24 +362,6 @@ function MicroLearningElement({
},
]
: []),
...(isFuture
? [
{
label: (
<div className="flex cursor-pointer flex-row items-center gap-1 text-red-600">
<FontAwesomeIcon icon={faLock} className="w-4" />
<div>
{t('manage.course.unpublishMicrolearning')}
</div>
</div>
),
onClick: async () => await unpublishMicroLearning(),
data: {
cy: `unpublish-microlearning-${microLearning.name}`,
},
},
]
: []),
...(isPast
? [
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ function PracticeQuizElement({
),
[PublicationStatus.Scheduled]: (
<StatusTag
color="bg-green-300"
color="bg-orange-200"
status={t('shared.generic.scheduled')}
icon={faClock}
/>
Expand Down
4 changes: 2 additions & 2 deletions apps/frontend-manage/src/components/courses/StatusTag.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,12 @@ function StatusTag({ color, status, icon }: StatusTagProps) {
return (
<div
className={twMerge(
'flex flex-row items-center gap-1 rounded bg-red-300 px-1 py-0.5 text-sm',
'flex flex-row items-center gap-2.5 rounded bg-red-300 px-1.5 py-0.5 text-sm',
color
)}
>
<div>{status}</div>
<FontAwesomeIcon icon={icon} />
<div>{status}</div>
</div>
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,27 +29,37 @@ function PublishConfirmationModal({
setOpen,
}: PublishConfirmationModalProps) {
const t = useTranslations()
const [publishPracticeQuiz] = useMutation(PublishPracticeQuizDocument, {
variables: {
id: elementId,
},
})
const [publishMicroLearning] = useMutation(PublishMicroLearningDocument, {
variables: {
id: elementId,
},
})
const [publishGroupActivity] = useMutation(PublishGroupActivityDocument, {
variables: {
id: elementId,
},
})
const [publishPracticeQuiz, { loading: pqPublishLoading }] = useMutation(
PublishPracticeQuizDocument,
{
variables: {
id: elementId,
},
}
)
const [publishMicroLearning, { loading: mlPublishLoading }] = useMutation(
PublishMicroLearningDocument,
{
variables: {
id: elementId,
},
}
)
const [publishGroupActivity, { loading: gaPublishLoading }] = useMutation(
PublishGroupActivityDocument,
{
variables: {
id: elementId,
},
}
)

return (
<Modal
title={t(`manage.course.publishItem${elementType}`)}
onPrimaryAction={
<Button
loading={pqPublishLoading || mlPublishLoading || gaPublishLoading}
onClick={async () => {
if (elementType === ElementInstanceType.Microlearning) {
await publishMicroLearning()
Expand Down
8 changes: 4 additions & 4 deletions cypress/cypress/e2e/G-microlearning-workflow.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -405,7 +405,7 @@ describe('Different microlearning workflows', () => {
.click()
cy.get('[data-cy="confirm-publish-action"]').click()
cy.get(`[data-cy="microlearning-${microLearningName}"]`).contains(
messages.shared.generic.published
messages.shared.generic.scheduled
)

// sign in as student
Expand Down Expand Up @@ -516,7 +516,7 @@ describe('Different microlearning workflows', () => {
.click()
cy.get('[data-cy="confirm-publish-action"]').click()
cy.get(`[data-cy="microlearning-${microLearningName}"]`).contains(
messages.shared.generic.published
messages.shared.generic.completed
)

// sign in as student
Expand Down Expand Up @@ -621,7 +621,7 @@ describe('Different microlearning workflows', () => {
.click()
cy.get('[data-cy="confirm-publish-action"]').click()
cy.get(`[data-cy="microlearning-${microLearningName}"]`).contains(
messages.shared.generic.published
messages.shared.generic.scheduled
)

// sign in as student
Expand Down Expand Up @@ -1248,7 +1248,7 @@ describe('Different microlearning workflows', () => {
.click()
cy.get('[data-cy="confirm-publish-action"]').click()
cy.get(`[data-cy="microlearning-${microLearningName}"]`).contains(
messages.shared.generic.published
messages.shared.generic.scheduled
)

// switch back to the lecturer and duplicate the microlearning
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
apiVersion: batch/v1
kind: CronJob
metadata:
name: {{ include "chart.fullname" . }}-cron-micro-pubs
labels:
{{- include "chart.labels" . | nindent 4 }}
spec:
schedule: {{ .Values.cron.microLearningPublications | quote }}
jobTemplate:
spec:
template:
spec:
containers:
- name: curl
image: curlimages/curl:7.85.0
imagePullPolicy: IfNotPresent
args:
- -X
- POST
- -H
- "Content-Type: application/json"
- -H
- "x-token: {{ .Values.cron.token }}"
- -H
- "x-graphql-yoga-csrf: PublishScheduledMicroLearnings"
- -d
- >
{
"operationName": "PublishScheduledMicroLearnings",
"variables": {},
"extensions": {
"persistedQuery": {
"version": 1,
"sha256Hash": "ccbed09035b8907dbe5bcb5ff6986215a3d5c45106895465e5e76566761ac22c"
}
}
}
- 'http://{{ include "chart.fullname" . }}-backend-graphql:3000/api/graphql'
restartPolicy: OnFailure
1 change: 1 addition & 0 deletions deploy/charts/klicker-uzh-v2/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ cron:
dailyGroupScores: "* * * * *" # runs every minute - overridden in deployment config
pushNotifications: "*/5 * * * *" # runs every 5 minutes
practiceQuizPublications: "*/5 * * * *" # runs every 5 minutes
microLearningPublications: "*/5 * * * *" # runs every 5 minutes
runningRandomGroups: "5 0 * * *" # running at midnight
finalRandomGroups: "5 0 * * *" # running at midnight
token: ""
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
mutation PublishScheduledMicroLearnings {
publishScheduledMicroLearnings
}
16 changes: 16 additions & 0 deletions packages/graphql/src/ops.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -12512,6 +12512,22 @@
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "publishScheduledMicroLearnings",
"description": null,
"args": [],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "Boolean",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "publishScheduledPracticeQuizzes",
"description": null,
Expand Down
7 changes: 7 additions & 0 deletions packages/graphql/src/ops.ts
Original file line number Diff line number Diff line change
Expand Up @@ -889,6 +889,7 @@ export type Mutation = {
publishGroupActivity?: Maybe<GroupActivity>;
publishMicroLearning?: Maybe<MicroLearning>;
publishPracticeQuiz?: Maybe<PracticeQuiz>;
publishScheduledMicroLearnings: Scalars['Boolean']['output'];
publishScheduledPracticeQuizzes: Scalars['Boolean']['output'];
rateElement?: Maybe<ElementFeedback>;
renameParticipantGroup?: Maybe<ParticipantGroup>;
Expand Down Expand Up @@ -3072,6 +3073,11 @@ export type PublishPracticeQuizMutationVariables = Exact<{

export type PublishPracticeQuizMutation = { __typename?: 'Mutation', publishPracticeQuiz?: { __typename?: 'PracticeQuiz', id: string, name: string, displayName: string, status: PublicationStatus } | null };

export type PublishScheduledMicroLearningsMutationVariables = Exact<{ [key: string]: never; }>;


export type PublishScheduledMicroLearningsMutation = { __typename?: 'Mutation', publishScheduledMicroLearnings: boolean };

export type PublishScheduledPracticeQuizzesMutationVariables = Exact<{ [key: string]: never; }>;


Expand Down Expand Up @@ -3786,6 +3792,7 @@ export const PublishFeedbackDocument = {"kind":"Document","definitions":[{"kind"
export const PublishGroupActivityDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"PublishGroupActivity"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"publishGroupActivity"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"displayName"}},{"kind":"Field","name":{"kind":"Name","value":"status"}}]}}]}}]} as unknown as DocumentNode<PublishGroupActivityMutation, PublishGroupActivityMutationVariables>;
export const PublishMicroLearningDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"PublishMicroLearning"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"publishMicroLearning"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"displayName"}},{"kind":"Field","name":{"kind":"Name","value":"status"}}]}}]}}]} as unknown as DocumentNode<PublishMicroLearningMutation, PublishMicroLearningMutationVariables>;
export const PublishPracticeQuizDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"PublishPracticeQuiz"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"publishPracticeQuiz"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"displayName"}},{"kind":"Field","name":{"kind":"Name","value":"status"}}]}}]}}]} as unknown as DocumentNode<PublishPracticeQuizMutation, PublishPracticeQuizMutationVariables>;
export const PublishScheduledMicroLearningsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"PublishScheduledMicroLearnings"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"publishScheduledMicroLearnings"}}]}}]} as unknown as DocumentNode<PublishScheduledMicroLearningsMutation, PublishScheduledMicroLearningsMutationVariables>;
export const PublishScheduledPracticeQuizzesDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"PublishScheduledPracticeQuizzes"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"publishScheduledPracticeQuizzes"}}]}}]} as unknown as DocumentNode<PublishScheduledPracticeQuizzesMutation, PublishScheduledPracticeQuizzesMutationVariables>;
export const RateElementDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"RateElement"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"elementInstanceId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"elementId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"rating"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"rateElement"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"elementInstanceId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"elementInstanceId"}}},{"kind":"Argument","name":{"kind":"Name","value":"elementId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"elementId"}}},{"kind":"Argument","name":{"kind":"Name","value":"rating"},"value":{"kind":"Variable","name":{"kind":"Name","value":"rating"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"upvote"}},{"kind":"Field","name":{"kind":"Name","value":"downvote"}},{"kind":"Field","name":{"kind":"Name","value":"feedback"}}]}}]}}]} as unknown as DocumentNode<RateElementMutation, RateElementMutationVariables>;
export const RenameParticipantGroupDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"RenameParticipantGroup"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"groupId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"name"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"renameParticipantGroup"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"groupId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"groupId"}}},{"kind":"Argument","name":{"kind":"Name","value":"name"},"value":{"kind":"Variable","name":{"kind":"Name","value":"name"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}}]}}]} as unknown as DocumentNode<RenameParticipantGroupMutation, RenameParticipantGroupMutationVariables>;
Expand Down
Loading
Loading