FS-Canvas-Overføring (LMS) - Not reliable, under construction.
- 1 Innledning
- 2 Nøkkel info
- 3 Bakgrunn
- 4 Interessenter
- 5 Brukerhistorie / Integrasjonens omfang (arbeidet som skal gjøres)
- 6 Systemer/tjenester
- 7 Tilgangsstyring og logging
- 8 Behandlingstid/responstid og volum
- 9 Diagram som visualizerer emneUEUA flyten
- 10 Brukte spørringer mot FS (Graph)
- 11 Feilhåndtering, konsekvenser av feil og overordnet risikoanalyse
- 12 Nødvendige tilganger
- 13 FS Graph:
- 14 Kommentarer
Innledning
Dette er en integrasjon for overføring av data fra FS til Canvas. Integrasjonen består av 7 hoved deler.
Dette er en typisk synk-integrasjon der en del data fra FS skal kopieres over til Canvas og holdes i synk basert på hendelser.
Nøkkel info
Initiesering av flyt | scheduler og hendelse basert | Kjører daglig også når skjer endring på dataene i FS |
|---|---|---|
Flyt møsnter | Synkron |
|
Bruk av meldingskø | Nei |
|
Open API | Nei |
|
IntArk | Ikke brukt |
|
Bakgrunn
LMS trenger en nye datasoverføringsintegrasjon som studie-strukturen (institusjon, fakultet, institutt, gruppe, emner, studieprogram, kull, klasse, studenter, lærerer og administratorer) er synkronisert slikt at dataene speils FS dataene til Canvas systemet. Denne informasjon har sin kilde i FS.
For kravspek/bestillings-dokument, se her :
Interessenter
Dette er en leveranse til tjenesten utdanningstjenesten.
Brukerhistorie / Integrasjonens omfang (arbeidet som skal gjøres)
Daglig skal det kjøres en synk som vil sørge for alle dataene nevnt ovenfor.
Daglig skal det sjekkes for eventuelle endringer i dataene nevnt ovenfor og utføre dem. F.eks om en studieprogram er flyttet.
Daglig sjekke og oppdatere rolle-tilknyttinger på emne og studieprogram nivå.
Systemer/tjenester
Detaljert liste av alle innvolverte systemer/tjenester Hva utveksler data? Fra hvor / Til hvor?
Tilgangsstyring og logging
Integrasjonen loger til Humio med detaljert logging av prosessen.
Integrasjonen har ikke noe behov for tilgangstyring
Behandlingstid/responstid og volum
Integrasjonen ikke tatt til bruker nå; ingen data!
Diagram som visualizerer emneUEUA flyten
Diagrammet viser kun de visualiserbare delene av emneUEUAflyten (uten flertermin delen som vil legges til) og det er analysering og koding bak nesten alle elementer og transformasjoner som ikke kan vises.
Flertermin er under arbeid og vil sannsynligvis være den mest kompliserte delen av emneflyten.
Vi har 6 slike flyter (1. oppretter konto hierarki 2. kobler emner til kontoer 3. kobler evuKurs til emneKontoer 4. kobler UEUAer til emner 5. kobler studieprogramer/kull/klasse til kontoer 6. melder brukere til emner, UEUA, evuKurs, studieprogram, kull, og klasse).
flowchart TD
classDef flow fill:#ecfdf5,stroke:#047857,stroke-width:2px,color:#065f46
classDef step fill:#f1f5f9,stroke:#475569,stroke-width:1px,color:#0f172a
classDef ext fill:#fef3c7,stroke:#d97706,color:#92400e
classDef decision fill:#fff,stroke:#64748b,stroke-dasharray:3 3
classDef loop fill:#eff6ff,stroke:#3b82f6,color:#1e3a8a
classDef note fill:#f5f3ff,stroke:#6d28d9,color:#4c1d95
classDef red fill:#fee2e2,stroke:#b91c1c,stroke-width:2px,color:#7f1d1d
classDef blue fill:#dbeafe,stroke:#1e40af,stroke-width:2px,color:#1e3a8a
classDef canvasRed fill:#fecaca,stroke:#b91c1c,stroke-width:2px,color:#7f1d1d
%% Note nodes
F1note1["Wait for Konto flow to finish"]:::note
F3note1["Skip emne (no Canvas account)"]:::note
F3note2["Skip parent UE handling"]:::note
F3note3["Child fleretermin not supported"]:::note
F6note1["Skip child UE (no parent course)"]:::note
%% listOfChanges-UEUA2Emne-FLOW-scheduler
subgraph F0["listOfChanges-UEUA2Emne-FLOW-scheduler"]
direction TB
F00s1["Manual trigger"]:::step
F0s1["Scheduler trigger"]:::step
F0s2["Choose Run type:
1- Changed data set
2- Full data set"]:::step
F0s3["Set targetFlow"]:::step
F0s4[["Call linkUEUA2EmneFromFS-CourseSectionType-FLOW"]]:::ext
F0s1 --> F0s2 --> F0s3 --> F0s4
F00s1 --> F0s2
end
%% linkUEUA2EmneFromFS-CourseSectionType-FLOW
subgraph F1["linkUEUA2EmneFromFS-CourseSectionType-FLOW"]
direction TB
F1s1["Set appVersion"]:::step
F1s2[("Retrieve emne2KontoFlowCompleted")]:::ext
F1s3["Force emne2KontoFlowCompleted=true"]:::step
F1d1{emne2KontoFlowCompleted?}:::decision
F1s4[["Call getOrgListWithActiveFsLms-subFlow"]]:::ext
F1s5["Filter org list"]:::step
F1l1(("For Each Org")):::loop
F1s6[["Call forEachOrg-UEUA2Emne-subFLOW"]]:::ext
F1s7[("Store emne2KontoFlowCompleted=false")]:::ext
F1s1 --> F1s2 --> F1s3 --> F1d1
F1d1 -->|"true"| F1s4 --> F1s5 --> F1l1 --> F1s6 --> F1s7
F1d1 -->|"false"| F1note1
end
%% forEachOrg-UEUA2Emne-subFLOW
subgraph F2["forEachOrg-UEUA2Emne-subFLOW"]
direction TB
F2s1[["Call extractOrgInfo.FSAccessInfo.CanvasAccessInfo-subFLOW"]]:::ext
F2s2["Set FsLmsTransferInfo variables"]:::step
F2s3["Compute syncPeriodList"]:::step
F2l1(("For Each SyncPeriod")):::loop
F2s4["Set syncPeriod"]:::step
F2s5["Set endCursorName"]:::step
F2s6["Clear auditInfoMap"]:::flow
F2s7["Init auditInfoMap"]:::flow
F2s8[("Retrieve previousCursorEnd")]:::ext
F2t1["Build FS query variables and expression"]:::step
F2s9[["Call fsQueryExecution-subFLOW"]]:::ext
F2s10["Store filtered fs2JsonOutput"]:::step
F2s11["Set currentCursorEnd"]:::step
F2s12[("Store end cursor")]:::ext
F2s13["Update audit info map"]:::flow
F2s14["Transform to emneList"]:::step
F2s15[["Call logArrayNameSize"]]:::ext
F2l2(("For Each Emne")):::loop
F2s16[["Call forEachEmne-subFLOW"]]:::ext
F2s17[["Call auditLogRegister-subFLOW"]]:::ext
F2s1 --> F2s2 --> F2s3 --> F2l1
F2l1 --> F2s4 --> F2s5 --> F2s6 --> F2s7 --> F2s8 --> F2t1 --> F2s9 --> F2s10 --> F2s11 --> F2s12 --> F2s13 --> F2s14 --> F2s15 --> F2l2 --> F2s16 --> F2s17
end
%% forEachEmne-subFLOW
subgraph F3["forEachEmne-subFLOW"]
direction TB
F3s1["Set emne variables"]:::step
F3s2["Filter sprakvalg"]:::step
F3s3["Set sis_account_id"]:::step
F3s4["Update audit info (emne)"]:::flow
F3s5[["Call getCanvasAccount-by-sis_account_id-subFLOW"]]:::ext
F3s6["Cache Canvas account flags"]:::step
F3d1{Account exists?}:::decision
F3s7["Prepare parent UE list"]:::step
F3d2{Parent UE list empty?}:::decision
F3d3{Flere termin?}:::decision
F3s8["Aggregate UA nodes"]:::step
F3s9[["Call ForEachUE-linkUEUA2Emne-subFLOW"]]:::ext
F3s10(("Loop parent UE")):::loop
F3s11[["Call ForEachUEChild-linkUEUA2Emne-subFLOW"]]:::ext
F3s12["Prepare child UE list"]:::step
F3d4{Child fleretermin?}:::decision
F3s13(("Loop child UE")):::loop
F3s14["Set emneID"]:::step
F3s15[["Call getAndRegisterFSBrukerInUEUA-subFLOW"]]:::ext
F3s1 --> F3s2 --> F3s3 --> F3s4 --> F3s5 --> F3s6 --> F3d1
F3d1 -->|"no"| F3note1
F3d1 -->|"yes"| F3s7 --> F3d2
F3d2 -->|"empty"| F3note2
F3d2 -->|"has UE"| F3d3
F3d3 -->|"true"| F3s8 --> F3s9
F3d3 -->|"false"| F3s10 --> F3s9
F3s7 --> F3s12 --> F3d4
F3d4 -->|"true"| F3note3
F3d4 -->|"false"| F3s13 --> F3s11
F3s6 --> F3s14 --> F3s15
end
%% ForEachUE-linkUEUA2Emne-subFLOW
subgraph F4["ForEachUE-linkUEUA2Emne-subFLOW"]
direction TB
F4s1["Update audit info (UE)"]:::flow
F4s2["Set UE context"]:::step
F4s3["Clear UE scoped variables"]:::step
F4s4["Set term and sprak"]:::step
F4d1{UE has LMS rom?}:::decision
F4s5["Prepare term ids"]:::step
F4s6[["Call createUpdateEnrollmentTerm-subFLOW"]]:::ext
F4s7["Prepare Canvas course variables"]:::step
F4s8[["Call UE[CourseType]-createUpdateCourse-subFLOW"]]:::ext
F4s9["Prepare Canvas section variables"]:::step
F4s10[["Call UE[SectionType]-createUpdateSection-subFLOW"]]:::ext
F4s11["Prepare manual section variables"]:::step
F4s12[["Call UE[SectionType]-createUpdateSection-subFLOW"]]:::ext
F4s13["Group UA nodes"]:::step
F4l1(("For Each UA group")):::loop
F4s14[["Call forEachUAGroup-subFLOW"]]:::ext
F4s1 --> F4s2 --> F4s3 --> F4s4 --> F4d1
F4d1 -->|"yes"| F4s5 --> F4s6 --> F4s7 --> F4s8 --> F4s9 --> F4s10 --> F4s11 --> F4s12
F4d1 -->|"no"| F4s13
F4s12 --> F4s13 --> F4l1 --> F4s14
end
%% forEachUAGroup-subFLOW
subgraph F5["forEachUAGroup-subFLOW"]
direction TB
F5s1["Update audit info (UA group)"]:::flow
F5s2["Set UA list context"]:::step
F5s3["Check LMS flags"]:::step
F5s4["Prepare term ids"]:::step
F5s5[["Call createUpdateEnrollmentTerm-subFLOW"]]:::ext
F5s6["Prepare parent account"]:::step
F5d1{"What to create?"}:::decision
F5s7["Prepare section variables"]:::step
F5s8[["Call UA[SectionType]-createUpdateSection-subFLOW"]]:::ext
F5s9["Prepare course variables"]:::step
F5s10[["Call UA[CourseType]-createUpdateCourse-subFLOW"]]:::ext
F5s11["Prepare manual section"]:::step
F5s12[["Call UA[SectionType]-createUpdateSection-subFLOW"]]:::ext
F5s1 --> F5s2 --> F5s3 --> F5s4 --> F5s5 --> F5s6 --> F5d1
F5d1 -->|"section only"| F5s7 --> F5s8
F5d1 -->|"course + section"| F5s9 --> F5s10 --> F5s11 --> F5s12
F5d1 -->|"course as section"| F5s7 --> F5s8 --> F5s11 --> F5s12
end
%% ForEachUEChild-linkUEUA2Emne-subFLOW
subgraph F6["ForEachUEChild-linkUEUA2Emne-subFLOW"]
direction TB
F6s1["Update audit info (child UE)"]:::flow
F6s2["Set parentEmneKode"]:::step
F6t1["Build parent FS query"]:::step
F6s3[["Call fsQueryExecution-subFLOW"]]:::ext
F6s4["Extract parentEmneInfo"]:::step
F6s5["Set parent course SIS id"]:::step
F6s6[["Call getCourse-by-sis_course_id-subFLOW"]]:::ext
F6s7["Cache Canvas course flags"]:::step
F6d1{Parent course exists?}:::decision
F6s8["Prepare child section"]:::step
F6s9[["Call UE[SectionType]-createUpdateSection-subFLOW"]]:::ext
F6s10["Group UA nodes"]:::step
F6l1(("For Each UA group")):::loop
F6s11[["Call forEachUAGroup-subFLOW"]]:::ext
F6s1 --> F6s2 --> F6t1 --> F6s3 --> F6s4 --> F6s5 --> F6s6 --> F6s7 --> F6d1
F6d1 -->|"yes"| F6s8 --> F6s9
F6d1 -->|"no"| F6note1
F6s7 --> F6s10 --> F6l1 --> F6s11
end
%% getAndRegisterFSBrukerInUEUA-subFLOW
subgraph F7["getAndRegisterFSBrukerInUEUA-subFLOW"]
direction TB
F7s1["Update audit info (users)"]:::flow
F7s2["Init brukerErrorList"]:::step
F7t1["Build soknad FS query"]:::step
F7s3[["Call fsQueryExecution-subFLOW"]]:::ext
F7s4["Transform emneSoknaderList"]:::step
F7s5[["Call logArrayNameSize"]]:::ext
F7s6["Transform emneUEUASoknaderList"]:::step
F7l1(("For Each soknad")):::loop
F7s7["Set soknad and person data"]:::step
F7s8["Compute SIS user IDs"]:::step
F7s9["Collect UE list and UA SIS ids"]:::step
F7s10[["Call collectUAPartiPlasseringWithFSID-subFLOW"]]:::ext
F7s11[["Call ForEachUEinUEList-linkUEUA2Emne-subFLOW"]]:::ext
F7s12["Merge UEUASoknaderList"]:::step
F7t2["Build roller FS query (emne)"]:::step
F7s13[["Call fsQueryExecution-subFLOW"]]:::ext
F7s14["Transform emneRollerList"]:::step
F7s15[["Call logArrayNameSize"]]:::ext
F7s16["Transform emneUEUARollerList"]:::step
F7l2(("For Each rolle in emne")):::loop
F7s17[["Call ForEachUERolleInEmneUERollerList-subFLOW"]]:::ext
F7t3["Build roller FS query (UE)"]:::step
F7s18[["Call fsQueryExecution-subFLOW"]]:::ext
F7s19["Transform UERollerList"]:::step
F7s20[["Call logArrayNameSize"]]:::ext
F7s21["Transform UEFagRollerList"]:::step
F7l3(("For Each rolle in UE")):::loop
F7s22[["Call ForEachUERolleInUERollerList-subFLOW"]]:::ext
F7t4["Build roller FS query (timeplan)"]:::step
F7s23[["Call fsQueryExecution-subFLOW"]]:::ext
F7s24["Transform timePlanRollerList"]:::step
F7t5["Build roller FS query (UA)"]:::step
F7s25[["Call fsQueryExecution-subFLOW"]]:::ext
F7s26["Transform UARollerList"]:::step
F7s27["Transform timePlanUARolleList"]:::step
F7s28[["Call logArrayNameSize"]]:::ext
F7s29["Transform UAFagRollerList"]:::step
F7l4(("For Each rolle in UA")):::loop
F7s30[["Call ForEachUARolleInUARollerList-subFLOW"]]:::ext
F7s31["Summarise enrollment inputs"]:::step
F7l5(("For Each UE or UA enrollment")):::loop
F7s32[["Call deleteCreateEnrollment-subFLOW"]]:::ext
F7s33["Set auditInfoMapKey"]:::flow
F7s34["Validate brukerErrorList empty"]:::step
F7s1 --> F7s2 --> F7t1 --> F7s3 --> F7s4 --> F7s5 --> F7s6 --> F7l1
F7l1 --> F7s7 --> F7s8 --> F7s9 --> F7s10 --> F7s11 --> F7s12 --> F7t2
F7t2 --> F7s13 --> F7s14 --> F7s15 --> F7s16 --> F7l2 --> F7s17 --> F7t3 --> F7s18 --> F7s19 --> F7s20 --> F7s21 --> F7l3 --> F7s22 --> F7t4 --> F7s23 --> F7s24 --> F7t5 --> F7s25 --> F7s26 --> F7s27 --> F7s28 --> F7s29 --> F7l4 --> F7s30 --> F7s31 --> F7l5 --> F7s32 --> F7s33 --> F7s34
end
%% collectUAPartiPlasseringWithFSID-subFLOW
subgraph F8["collectUAPartiPlasseringWithFSID-subFLOW"]
direction TB
F8s1["Clear parti variables"]:::step
F8t1["Build parti FS query"]:::step
F8s2[["Call fsQueryExecution-subFLOW"]]:::ext
F8s3["Transform undervisningspartiplasseringerList"]:::step
F8s4["Transform UAListInPartiPlassering"]:::step
F8s1 --> F8t1 --> F8s2 --> F8s3 --> F8s4
end
%% ForEachUEinUEList-linkUEUA2Emne-subFLOW
subgraph F9["ForEachUEinUEList-linkUEUA2Emne-subFLOW"]
direction TB
F9s1["Set UE"]:::step
F9s2["Clear UE enrollment variables"]:::step
F9s3["Set course_id and SIS ids"]:::step
F9s4["Transform emneUEUASoknaderList"]:::step
F9l1(("For Each UA")):::loop
F9s5["Append UA enrollment entries"]:::step
F9s1 --> F9s2 --> F9s3 --> F9s4 --> F9l1 --> F9s5
end
%% ForEachUERolleInEmneUERollerList
subgraph F10["ForEachUERolleInEmneUERollerList-subFLOW"]
direction TB
F10s1["Set UE context"]:::step
F10s2["Clear role variables"]:::step
F10s3["Set course_id and SIS ids"]:::step
F10d1{Role code known?}:::decision
F10s4["Add role to emneUEUARollerList"]:::step
F10s1 --> F10s2 --> F10s3 --> F10d1 -->|"yes"| F10s4
end
%% ForEachUERolleInUERollerList
subgraph F11["ForEachUERolleInUERollerList-subFLOW"]
direction TB
F11s1["Set UE and emne context"]:::step
F11s2["Clear role variables"]:::step
F11s3["Set course_id and SIS ids"]:::step
F11d1{Role code known?}:::decision
F11s4["Add role to UEFagRollerList"]:::step
F11s1 --> F11s2 --> F11s3 --> F11d1 -->|"yes"| F11s4
end
%% ForEachUARolleInUARollerList
subgraph F12["ForEachUARolleInUARollerList-subFLOW"]
direction TB
F12s1["Set UA and UE context"]:::step
F12s2["Clear role variables"]:::step
F12s3["Set course_id and SIS ids"]:::step
F12d1{Role code known?}:::decision
F12s4["Add role to UAFagRollerList"]:::step
F12s1 --> F12s2 --> F12s3 --> F12d1 -->|"yes"| F12s4
end
%% Cross-flow links
F0s4 --> F1s1
F1l1 --> F2s1
F2l2 --> F3s1
F3s9 --> F5s1
F3s11 --> F5s1
F3s15 --> F7s1
F4l1 --> F5s1
F6l1 --> F5s1
F7s10 --> F8s1
F7s11 --> F9s1
F7s17 --> F10s1
F7s22 --> F11s1
F7s30 --> F12s1
F2s1 --> F13
F2s9 --> F14
F6s3 --> F14
F7s3 --> F14
F7s13 --> F14
F7s18 --> F14
F7s23 --> F14
F7s25 --> F14
F8s2 --> F14
F3s5 --> F15
F4s6 --> F16
F4s6 --> F16
F5s5 --> F16
F4s8 --> F17
F5s10 --> F17
F4s10 --> F18
F4s12 --> F18
F5s8 --> F18
F5s12 --> F18
F6s9 --> F18
F7s32 --> F19
F19s3 --> F20
F19s15 --> F21
F19s22 --> F22
F19s30 --> F23
F23s3 --> F24
F18s3 --> F24
F23s7 --> F25
F23s11 --> F26
F19s15 --> F21
F23s16 --> F21
F2s17 --> F27
F16s3 --> F28
F3s5 --> Canvas
F15s3 --> Canvas
F20s3 --> Canvas
F21s4 --> Canvas
F24s3 --> Canvas
F25s3 --> Canvas
F26s3 --> Canvas
F28s2 --> Canvas
%% Legend
subgraph Legend
direction TB
L1["Step"]:::step
L2[["External sub-flow call"]]:::ext
L3{"Decision"}:::decision
L4(("Loop")):::loop
L5["Note"]:::note
end
subgraph F13[extractOrgInfo.FSAccessInfo.CanvasAccessInfo-subFLOW]
B[Set Variable: orgInfo = payload] --> C[Set Variable: orgShortName = orgInfo.ShortName]
C --> D[Set Variable: orgFSID = orgInfo.FsId]
D --> E[Log Info: Running for orgShortName]
E --> F[Transform: FSAccessInfo]
F --> G[Set Variable: fsUserName from orgInfo.FSGraphQL]
G --> H[Set Variable: fsURL from orgInfo.FSGraphQL]
H --> I[HTTP GET: CanvasAccessToken]
I --> J[Transform: CanvasAccessInfo]
J --> K[Set Variable: canvasOAuthAccessToken]
K --> L[Set Variable: canvasInstitutionSubdomain]
end
subgraph F14[fsQueryExecution-subFLOW]
class F14 blue
F14s1([Start]) --> F14s2[Try: Begin]
F14s2 --> F14s3[Until Successful]
F14s3 --> F14s4[Do FS Query using FSQueryExpression and FSQueryVariables]
F14s4 --> F14s5[Validate: Response is not null]
F14s5 --> F14s6[Validate: No errors in payload]
F14s6 --> F14s7([Success])
F14s2 -.-> F14s8{Error Occurs?}
F14s8 -->|Yes| F14s9[On Error Propagate]
F14s9 --> F14s10[Log: Error Message]
F14s10 --> F14s11[Log: Payload]
F14s11 --> F14s12[Log: FSQueryVariables]
F14s12 --> F14s13[Log: FSQueryExpression]
F14s13 --> F14s14([End Error])
F14s8 -->|No| F14s7
F14s7 --> F14s15([End])
end
subgraph F15[getCanvasAccount-by-sis_account_id-subFLOW]
F15s1([Start]) --> F15s2[Try: Begin]
F15s2 --> F15s3[Set Variable: canvasGetAccountResponse = null]
F15s3 --> F15s4[Canvas API: Get account by sis_account_id]
F15s4 --> F15s5[Set Payload: canvasGetAccountResponse]
F15s5 --> F15s6{Canvas Error?}
F15s6 -->|Not Found| F15s7[On Error Continue: Set Payload = null]
F15s7 --> F15s10([End Success])
F15s6 -->|Error Other Than Not Found| F15s8[On Error Propagate]
F15s8 --> F15s9[Log: Unexpected Canvas Error with sis_account_id]
F15s9 --> F15s11([End Error])
F15s6 -->|No Error| F15s10
F15s10 --> F15s12([End])
end
subgraph F16["createUpdateEnrollmentTerm-subFLOW"]
F16s1["Logger: Flow started"]
F16s2["Transform: Nullify enrollment term responses"]
F16s3["Flow Ref: getEnrollmentTerm-by-sis_term_id-subFLOW"]
F16s4["Set Variable: enrollmentTermNotExistInLMS"]
F16s5{"Enrollment term exists in LMS?"}
F16s6["Logger: enrollmentTermNotExistInLMS"]
F16s7["Flow Ref: createNewEnrollmentTerm-subFLOW"]
F16s8["Logger: enrollmentTermExistInLMS, updating it..."]
F16s9["Set Variable: enrollment_term_id"]
F16s10["Flow Ref: updateEnrollmentTerm-subFlow"]
F16s1 --> F16s2 --> F16s3 --> F16s4 --> F16s5
F16s5 -- Yes --> F16s6 --> F16s7
F16s5 -- No --> F16s8 --> F16s9 --> F16s10
end
subgraph F17["createUpdateCourse-subFLOW"]
F17s1["Logger: Flow started"]
F17s2["Transform: Nullify course responses"]
F17s3["Flow Ref: getCourse-by-sis_course_id-subFLOW"]
F17s4["Set Variable: courseNotExistInLMS"]
F17s5{"Course exists in LMS?"}
F17s6["Logger: courseNotExistInLMS"]
F17s7["Flow Ref: createNewCourse-subFLOW"]
F17s8["Logger: courseExistInLMS, updating it..."]
F17s9["Set Variable: course_id"]
F17s10["Set Variable: course_code"]
F17s11["Set Variable: course_name"]
F17s12["Set Variable: enrollment_term_id"]
F17s13["Flow Ref: updateExistingCourse-subFlow"]
F17s1 --> F17s2 --> F17s3 --> F17s4 --> F17s5
F17s5 -- Yes --> F17s6 --> F17s7
F17s5 -- No --> F17s8 --> F17s9 --> F17s10 --> F17s11 --> F17s12 --> F17s13
end
subgraph F18["createUpdateSection-subFLOW"]
F18s1["Logger: Flow started"]
F18s2["Transform: Nullify section responses"]
F18s3["Flow Ref: getSection-by-sis_section_id-subFLOW"]
F18s4["Set Variable: sectionNotExistInLMS"]
F18s5{"Section exists in LMS?"}
F18s6["Logger: sectionNotExistInLMS"]
F18s7["Flow Ref: createNewSection-subFLOW"]
F18s8{"Is Manual Section?"}
F18s9["Logger: Manual section exists"]
F18s10["Logger: sectionExistInLMS, updating it..."]
F18s11["Set Variable: section_id"]
F18s12["Set Variable: section_name"]
F18s13["Flow Ref: updateExistingSection-subFlow"]
F18s1 --> F18s2 --> F18s3 --> F18s4 --> F18s5
F18s5 -- Yes --> F18s6 --> F18s7
F18s5 -- No --> F18s8
F18s8 -- Yes --> F18s9
F18s8 -- No --> F18s10 --> F18s11 --> F18s12 --> F18s13
end
subgraph F19["deleteCreateEnrollment-subFLOW"]
class F19 ex
F19s1(["Set sis_section_id"]):::step
F19s2{"Try getting users in section"}:::decision
F19s3(["Flow Ref: getUsersInSection-by_sis_section_id-subFLOW"]):::ext
F19s4(["Set sectionNotExist = false"]):::step
%% Error handling
F19s5{"Error: Section doesn't exist?"}:::decision
F19s6(["Set sectionNotExist = true"]):::step
F19s7(["Log unexpected Canvas error"]):::step
F19s8(["Set listOfSisUserIDsInSectionFromCanvas"]):::step
F19s9(["Set ListOfUsers2bDeletedInCanvasSection"]):::step
F19s10(["Log array size for delete list"]):::ext
%% Loop for deletions
F19s11{{"For Each user in DeleteList"}}:::loop
F19s12(["Set canvasUser2bDeleted"]):::step
F19s13(["Set sis_course_id"]):::step
F19s14(["Set enrollment_id"]):::step
F19s15(["Flow Ref: DeleteUserInSection-subFLOW"]):::ext
F19s16(["Set ListOfUsers2bCreatedInCanvasSection"]):::step
F19s17(["Log array size for create list"]):::ext
%% Loop for creations
F19s18{{"For Each user in CreateList"}}:::loop
F19s19(["Set canvasUser2bCreated"]):::step
F19s20(["Set enrollment_id"]):::step
F19s21(["Set payload: personProfil = user"]):::step
F19s22(["Flow Ref: Create/Update User"]):::ext
F19s23(["Reset course_id"]):::step
F19s24(["Set sis_section_id"]):::step
F19s25(["Set erAktivRolle"]):::step
F19s26(["Set fsRolleKode"]):::step
F19s27(["Set enrollmentType"]):::step
F19s28(["Set role_id"]):::step
F19s29(["Set limit_privilage = false"]):::step
F19s30(["Flow Ref: createSkipDeleteUserEnrollmentInSection-subFLOW"]):::ext
F19s1 --> F19s2
F19s2 --> F19s3 --> F19s4 --> F19s8
F19s2 --> F19s5
F19s5 -- Yes --> F19s6 --> F19s8
F19s5 -- No --> F19s7 --> F19s8
F19s8 --> F19s9 --> F19s10 --> F19s11
F19s11 --> F19s12 --> F19s13 --> F19s14 --> F19s15 --> F19s11
F19s11 --> F19s16 --> F19s17 --> F19s18
F19s18 --> F19s19 --> F19s20 --> F19s21 --> F19s22 --> F19s23 --> F19s24 --> F19s25 --> F19s26 --> F19s27 --> F19s28 --> F19s29 --> F19s30 --> F19s18
%% Class assignments
class F19s1,F19s4,F19s8,F19s9,F19s12,F19s13,F19s14,F19s16,F19s19,F19s20,F19s21,F19s23,F19s24,F19s25,F19s26,F19s27,F19s28,F19s29 step
class F19s3,F19s10,F19s15,F19s17,F19s22,F19s30 ext
class F19s2,F19s5,F19s11,F19s18 decision
class F19s7,note note
class F19s6,F19s10,F19s17 loop
end
subgraph F20["getUsersInSection-by_sis_section_id-subFLOW"]
F20s1["Try"]
F20s2["Set Variable: nullify canvasGetUsersInSectionResponse"]
F20s3["Canvas API: Get users in section by sis_section_id"]
F20s4["Set Payload: canvasGetUsersInSectionResponse"]
F20s5{"Error?"}
F20s6["Logger: Error received from Canvas"]
F20s1 --> F20s2 --> F20s3 --> F20s4 --> F20s5
F20s5 -- Yes --> F20s6
F20s5 -- No --> F20s4
end
subgraph F21["DeleteUserInSection-subFLOW"]
class F21 ex
F21s1(["Logger: Flow has been started"]):::step
F21s2{"Try"}:::decision
F21s3(["Set Variable: nullify canvasUpdateEnrollUserResponse"]):::step
F21s4(["Canvas API: Delete enrollment in course/section"]):::ext
F21s5{"Error occurred?"}:::decision
F21s6(["Logger: Cannot delete user enrollment"]):::step
F21s1 --> F21s2
F21s2 --> F21s3 --> F21s4 --> F21s5
F21s5 -- Yes --> F21s6
F21s5 -- No --> F21s2
%% Class assignments
class F21s1,F21s3,F21s6 step
class F21s4 ext
class F21s2,F21s5 decision
end
subgraph F22["forEachUserInUserListCreateUpdateUser-subFLOW"]
class F22 ex
F22s1(["Set variable: user = payload"]):::step
F22s2(["Set variable: name_of_user = fornavn + etternavn"]):::step
F22s3(["Set variable: brukernavn = user.feideBruker before @"]):::step
F22s4(["Set variable: unique_id = brukernavn@domain + sis_id_temp_DEV_only"]):::step
F22s5(["Set variable: sis_user_id = fs:orgFSID:personlopenummer + sis_id_temp_DEV_only"]):::step
F22s6(["Set variable: user_email_address"]):::step
F22s7(["Set variable: account_id = 1"]):::step
F22s8(["Flow Ref: createUpdateUser-subFLOW"]):::ext
F22s9(["Set variable: user_id from Canvas responses"]):::step
F22s10(["Set variable: FSUserID = user.id"]):::step
%% Connections
F22s1 --> F22s2 --> F22s3 --> F22s4 --> F22s5 --> F22s6 --> F22s7 --> F22s8 --> F22s9 --> F22s10
%% Class assignments
class F22s1,F22s2,F22s3,F22s4,F22s5,F22s6,F22s7,F22s9,F22s10 step
class F22s8 ext
end
subgraph F23["createSkipDeleteUserEnrollmentInSection-subFLOW"]
class F23 ex
F23s1(["Logger: subFlow has been started"]):::step
F23s2(["Transform: nullify canvasGetSectionResponse / canvasEnrollUser2SectionResponse / canvasGetUserEnrollmentsResponse"]):::step
F23s3(["Flow Ref: getSection-by-sis_section_id-subFLOW"]):::ext
F23s4(["Set variable: sectionNotExistInLMS = isEmpty(payload)"]):::step
F23s5{"Section exists in LMS?"}:::decision
%% When section does not exist
F23s6(["Logger: Section does not exist. Skipping user enrollment"]):::step
%% When section exists
F23s7(["Flow Ref: getUserEnrollment-by-user_id-subFLOW"]):::ext
F23s8(["Set variable: userEnrollmentNotExistInLMS = isEmpty(payload)"]):::step
F23s9{"Create new or update existing user enrollment?"}:::decision
F23s10(["Logger: Enrolling user"]):::step
F23s11(["Flow Ref: EnrollUser2Section-subFLOW"]):::ext
F23s12(["Logger: Deleting user enrollment"]):::step
F23s13(["Validation: userEnrollmentList has only one entry?"]):::step
F23s14(["Set variable: sis_course_id"]):::step
F23s15(["Set variable: enrollment_id"]):::step
F23s16(["Flow Ref: DeleteUserInSection-subFLOW"]):::ext
F23s17(["Logger: Skip updating or enrolling user"]):::step
%% Connections
F23s1 --> F23s2 --> F23s3 --> F23s4 --> F23s5
F23s5 -- No (section not exist) --> F23s6
F23s5 -- Yes (section exists) --> F23s7 --> F23s8 --> F23s9
F23s9 -- userEnrollmentNotExistInLMS && erAktivRolle --> F23s10 --> F23s11
F23s9 -- !userEnrollmentNotExistInLMS && !erAktivRolle --> F23s12 --> F23s13 --> F23s14 --> F23s15 --> F23s16
F23s9 -- otherwise --> F23s17
%% Class assignments
class F23s1,F23s2,F23s4,F23s6,F23s8,F23s10,F23s12,F23s13,F23s14,F23s15,F23s17 step
class F23s3,F23s7,F23s11,F23s16 ext
class F23s5,F23s9 decision
end
subgraph F24["getSection-by-sis_section_id-subFLOW"]
class F24 ex
F24s1{"Try"}:::decision
F24s2(["Set variable: canvasGetSectionResponse = null"]):::step
F24s3(["Canvas API: Get section by sis_section_id"]):::ext
F24s4(["Set payload = canvasGetSectionResponse"]):::step
F24s5{"Error occurred?"}:::decision
F24s6(["Set payload = null (resource does not exist)"]):::step
F24s7(["Logger: Unexpected error from Canvas"]):::step
F24s1 --> F24s2 --> F24s3 --> F24s4 --> F24s5
F24s5 -- Resource does not exist --> F24s6
F24s5 -- Other error --> F24s7
%% Class assignments
class F24s1,F24s5 decision
class F24s2,F24s4,F24s6,F24s7 step
class F24s3 ext
end
subgraph F25["getUserEnrollment-by-user_id-subFLOW"]
class F25 ex
F25s1{"Try"}:::decision
F25s2(["Set variable: canvasGetUserEnrollmentResponse = null"]):::step
F25s3(["Until Successful: Canvas API Get user enrollments by user_id or sis_user_id"]):::ext
F25s4(["Transform: Filter userEnrollmentList from canvasGetUserEnrollmentsResponse"]):::step
F25s5(["Validation: Is userEnrollmentList empty = false?"]):::step
F25s6(["Set payload = canvasGetUserEnrollmentsResponse"]):::step
F25s7{"Error occurred?"}:::decision
F25s8(["Set payload = null if Value expected false error"]):::step
F25s9(["Logger: Unexpected error from Canvas"]):::step
%% Connections
F25s1 --> F25s2 --> F25s3 --> F25s4 --> F25s5 --> F25s6
F25s1 --> F25s7
F25s7 -- Expected error --> F25s8
F25s7 -- Other error --> F25s9
%% Class assignments
class F25s1,F25s7 decision
class F25s2,F25s4,F25s5,F25s6,F25s8,F25s9 step
class F25s3 ext
end
subgraph F26["EnrollUser2Section-subFLOW"]
class F26 ex
F26s1{"Try"}:::decision
F26s2(["Set variable: canvasEnrollUser2SectionResponse = null"]):::step
F26s3(["Enroll user to section (Canvas API)"]):::ext
F26s4{"Error occurred?"}:::decision
F26s5(["Logger: Cannot enroll user to this section (expected 'resource does not exist' error)"]):::step
F26s6(["Logger: Unexpected error (on-error-propagate)"]):::step
%% Connections
F26s1 --> F26s2 --> F26s3 --> F26s4
F26s4 -- Resource does not exist --> F26s5
F26s4 -- Other errors --> F26s6
%% Class assignments
class F26s1,F26s4 decision
class F26s2,F26s3,F26s5,F26s6 step
class F26s3 ext
end
subgraph F27[auditLogRegister-subFLOW]
F27s1([Start]):::flow --> F27s2[Log: Started to register auditInfoMap in OAI-DB]:::step
F27s2 --> F27s3[Set Variable: OAIDBkey = auditInfoMap.key]:::step
%% Try block for DB registration
F27s3 --> F27s4[Try: Register audit log in DB]:::flow
F27s4 --> F27s5[Add audit log to DB]:::ext
F27s5 --> F27s6{DB Error?}:::decision
%% Error handling for DB registration
F27s6 -->|Yes| F27s7[Log ERROR: Cannot register auditInfo in DB]:::step
F27s7 --> F27s8([Continue]):::step
F27s6 -->|No| F27s9([DB Registration OK]):::step
%% Decision: audit successful?
F27s9 --> F27s10{auditInfoMap.success == false?}:::decision
%% If not successful
F27s10 -->|Yes| F27s11[Log: Caught Error, sending Slack warning]:::step
F27s11 --> F27s12[Slack: Report error with auditInfoMap]:::ext
F27s12 --> F27s13([Continue]):::step
%% If successful
F27s10 -->|No| F27s14[Log: Audit registered successfully]:::step
%% Second Try - send to Humio
F27s14 --> F27s15[Try: Send auditInfo to Humio]:::flow
F27s15 --> F27s16[Send auditInfo to Humio]:::ext
F27s16 --> F27s17{Humio Error?}:::decision
%% Humio error handling
F27s17 -->|Yes| F27s18[Log ERROR: Cannot send auditInfo to Humio]:::step
F27s18 --> F27s19([Continue]):::step
F27s17 -->|No| F27s20([Humio Send OK]):::step
F27s20 --> F27s21([End]):::flow
end
subgraph F28["F28 - getEnrollmentTerm-by-term_id-subFLOW"]
F28s1["Nullify canvasGetEnrollmentTermResponse"]:::step
F28s2["Call Canvas API: Get Enrollment Term by term_id"]:::ext
F28s3["Set payload = canvasGetEnrollmentTermResponse"]:::step
%% Error Handling
F28s4{"Error contains 'The specified resource does not exist.'?"}:::decision
F28s5["Set payload = null (continue error)"]:::step
F28s6["Log error and propagate"]:::step
%% Flow connections
F28s1 --> F28s2 --> F28s3 --> F28s4
F28s4 -->|Yes| F28s5
F28s4 -->|No| F28s6
end
Canvas(("Call to Canvas")):::red
Brukte spørringer mot FS (Graph)
query organisasjonsenheter($inst: String!) {
organisasjonsenheter(
first: 1000
filter: {eierInstitusjonsnummer: $inst, institusjonummer: $inst, skalEksporteresTilLms: true, erAktiv: true}
) {
nodes {
instituttnummer
fakultet {
navn {
nob
eng
nno
}
fakultetsnummer
}
lmsRomkode
navnAlleSprak {
nob
nno
eng
}
gruppenummer
organisasjon {
organisasjonskode
navn {
nob
eng
nno
}
}
}
}
}query emneHierarki($inst: String!, $aar: Int!, $termin: EmneIkkeUtloptITerminTerminbetegnelse!) {
emner(
filter: {eierInstitusjonsnummer: $inst, ikkeUtloptITermin: {arstall: $aar, terminbetegnelse: $termin}}
first: 1000
after: "MjA6MTIzNCwxMjM0LExNUyBURVNURU1ORSwx"
) {
...QueryEmneConnectionFragment
}
}
fragment QueryEmneConnectionFragment on QueryEmneConnection {
nodes {
id
kode
versjonskode
navnAlleSprak {
nob
eng
nno
}
sprakvalg {
tilbysSomUndervisningssprak
sprak {
iso6392Kode
}
}
organisasjonsenhet {
studieAnsvarlig {
navnAlleSprak {
nob
}
fakultet {
navn {
nob
}
fakultetsnummer
}
organisasjon {
organisasjonskode
navn {
nob
eng
nno
}
}
gruppenummer
instituttnummer
}
}
}
pageInfo {
endCursor
hasNextPage
}
}query studieprogramKullKlassHierarki($inst: String!) {
studieprogram(
filter: {eierInstitusjonsnummer: $inst, skalEksporteresTilLms: true, erAktiv: true}
first: 1000
) {
nodes {
kode
lmsRomkode
personroller {
gyldighetsperiode {
fraDato
tilDato
}
fsRolle {
imsRolle
kode
navn {
eng
lmsNavn
nno
nob
}
erAktiv
}
aktiv
}
organisasjonsenhet {
studieansvarlig {
lmsRomkode
instituttnummer
organisasjon {
organisasjonskode
navn {
nob
eng
nno
}
}
gruppenummer
fakultet {
fakultetsnummer
navn {
eng
nno
nob
}
}
navnAlleSprak {
eng
nno
nob
}
skalEksporteresTilLms
erAktiv
}
}
navnAlleSprak {
eng
nno
nob
}
kull(aktiv: true) {
lmsRomkode
navnAlleSprak {
en
no
}
skalEksporteresTilLms
termin {
arstall
betegnelse {
kode
navnAlleSprak {
en
nb
nn
}
}
}
tjenestenummerForLms
erAktiv
klasser(erAktiv: true, skalEksporteresTilLms: true) {
kode
lmsRomkode
skalEksporteresTilLms
erAktiv
navnAlleSprak {
und
}
}
}
skalEksporteresTilLms
skalPubliseres
}
pageInfo {
endCursor
hasNextPage
startCursor
}
totalCount
}
}
query EtterutdanningsKursAktivitet($inst: String!) {
evuKurs(
filter: {eierInstitusjonsnummer: $inst, skalEksporteresTilLms: true, erAktiv: true}
first: 1000
) {
nodes {
kurskode
skalEksporteresTilLms
tidsangivelseskode
erAktiv
navnAlleSprak {
eng
nno
nob
}
kursperiode {
fraDato
tilDato
}
tjenestenummerForLms
lmsRomkode
administrativtAnsvarligEnhet {
erAktiv
skalEksporteresTilLms
navnAlleSprak {
eng
nno
nob
}
lmsRomkode
instituttnummer
gruppenummer
fakultet {
navn {
eng
nno
nob
}
fakultetsnummer
}
organisasjon {
organisasjonskode
navn {
nob
eng
nno
}
}
}
}
pageInfo {
endCursor
hasNextPage
}
}
}
query emneHierarki($inst: String!, $aar: Int!, $termin: EmneIkkeUtloptITerminTerminbetegnelse!) {
emner(
filter: {eierInstitusjonsnummer: $inst, ikkeUtloptITermin: {arstall: $aar, terminbetegnelse: $termin}}
first: 1000
after: "MjA6MTIzNCwxMjM0LExNUyBURVNURU1ORSwx"
) {
...QueryEmneConnectionFragment
}
}
fragment QueryEmneConnectionFragment on QueryEmneConnection {
nodes {
id
kode
versjonskode
navnAlleSprak {
nob
eng
nno
}
sprakvalg {
tilbysSomUndervisningssprak
sprak {
iso6392Kode
}
}
tjenestenummerForLms
undervisningsenheter(filter: {skalEksporteresTilLms: true}) {
lmsRomkode
terminnummer
termin {
arstall
betegnelse {
kode
}
}
undervisningsaktiviteter {
kode
navnAlleSprak {
nob
nno
eng
}
terminnummer
skalEksporteresTilLms
lmsRomkode
}
undervisningenFolgesAv {
kode
navnAlleSprak {
nob
nno
eng
}
}
undervisningenFolger {
kode
navnAlleSprak {
nob
eng
nno
}
}
}
organisasjonsenhet {
studieAnsvarlig {
gruppenummer
instituttnummer
fakultet {
fakultetsnummer
navn {
eng
nno
nob
}
}
organisasjon {
organisasjonskode
}
skalEksporteresTilLms
navnAlleSprak {
eng
nno
nob
}
}
}
}
pageInfo {
endCursor
hasNextPage
}
}
query singleEmne($inst: String!, $emneKode: [String!]) {
emner(filter: {eierInstitusjonsnummer: $inst, emnekoder: $emneKode}) {
nodes {
kode
versjonskode
tjenestenummerForLms
undervisningsenheter {
terminnummer
termin {
arstall
betegnelse {
kode
navnAlleSprak {
en
nb
}
}
}
}
}
}
}
Feilhåndtering, konsekvenser av feil og overordnet risikoanalyse
Generelt vil status og dermed eventuelle feil være synlig og tilgjengelig for institusjonen via logg-oversikten. Det er også utarbeidet mulighet for at enkelt personer ved institusjonen kan melde seg på for mottak av feilmeldinger på epost daglig.
Videre har vi overvåkning av loggene via Humio for å fange opp feil-situasjoner utenfor institusjonens virkeområde, som f.eks. utilgjengelige API endepunkter og bugs i koden.
Hva skjer ved overload i kø?
Det skjer ikke. Men om vi skulle på grunn av noe feil ikke motta meldingene fra eksamensystemene, vil : Inspera vite om det og sende dem på nytt. Wiseflow: er basert på polling, så vi kan alltid polle igjen
Hva skjer med ufullstendige meldinger?
De vil feile og det vil vi oppdage i loggene og kan ta aksjon basert på det.
Inneholder meldingene personopplysninger?
Merk at det utveksles mange “meldinger” mellom ulike systemer i løpet av en integrasjon. Meldinger fra eksamensystemene inneholder ikke noe persondata, men data vi henter fra FS basert på de meldingene gjør. Filene vi arkiverer (som kommer fra eksamensystemene) kan også potensielt inneholde personopplysninger.
Noe om viktige feil/situasjoner som må passes ekstra på (som kan ha stor konsekvens) :
Oppgavene ikke arkiveres (miste meldinger)
Oppgaver arkiveres med feil info. (feiltolking eller feil logikk)
Oppgaver arkiveres med feil tilganger (slik at oppgaver som ikke skal være offentlige, arkiveres slik at de blir offentlig synlige)
Nødvendige tilganger
FS Graph:
? (Avventer fra FS)
Kommentarer