Example for how data is fetched for Spring 2022, period P3, school ABE:
+Example for how data is fetched for Spring 2025, period P3, school ABE:
- Course data is retrieved from KOPPS API for Courses, endpoint /api/kopps/v2/courses/offerings. Data - for the current page was fetched from + Course data is retrieved from LADOK Mellanlager API, endpoint /sokUtbildningstillfalle. In this + example, data is fetched from - {`${koppsApiUrl}courses/offerings?from=20221&skip_coordinator_info=true`} + {`${ladokApiUrl}/sokUtbildningstillfalle?striktKod=false&kodEllerBenamning=*&startPeriod=VT2025&organisation=AIJ,AEH,AFO,AA,ADD,ADB,ADC,ADA,ADI,ADJ,ADH,AFB,AFD,AFC,AGD,AFA,AF,AFF,AID,AFK,AM,AMB,AHG,AFV,AEB,AEE,AFG,AEF,AGE,ALD,AEK,AFL,AGC,AIG,AHC,AGF,AFI,AKC,ALB,AFJ,ALC,AE,AEA,ADF,AKA,AK,AKB,AIE,AIB,AIC,AIA,AI,AFM,AEG,AHE,AHF,AIF,A,AAB,AAC,AD,AFH,AFE,ALF,AFT,ALA,AL,AHD,AFP,AHB,AHA,AH,AGI,AGA,AG,AGB,AEC,ALI,AED`} .
- For course memos, offerings that didn’t start during the 20221 semester are filtered out. This is done by - discarding offerings that doesn’t meet the criteria: course.first_yearsemester == 20221, - course.first_period===20221P3, SCHOOL_MAP[course.school_code]=== 'ABE'. Date used to determine if memo - was published before the offering started is course.offered_semesters[{{20221}}].start_date. + For course memos, offerings that didn’t start during the VT2025 semester are filtered out. This is done by + discarding offerings that doesn’t meet the criteria: course.startPeriod == VT2025, + course.forstaUndervisningsdatum.kthStudyPeriod == 3, SCHOOL_MAP[course.school_code] === 'ABE'. Date + used to determine if memo was published before the offering started is course.forstaUndervisningsdatum.date.
An earlier version of Publish new course analysis and course data had the option to upload course memos @@ -34,7 +34,7 @@ const englishMemosSummarySection = {
Here is a summary of the number of published course memos for the selected school, year and study period. The - data used is taken from Kopps and About course. + data used is taken from LADOK and About course.
The table shows the number of courses that started and number of course memos that were published during the @@ -70,23 +70,24 @@ const englishMemosSummarySection = { } const swedishMemosSummarySection = { - courseDataApiDescription: koppsApiUrl => ( + courseDataApiDescription: ladokApiUrl => (
Exempel för hur data hämtas för en termin - VT 2022, period P3:
+Exempel för hur data hämtas för en termin - VT 2025, period P3, school ABE:
- Kursdata hämtas från KOPPS API för kurser, endpoint: /api/kopps/v2/courses/offerings. För att hämta data för VT - 2022 så används:{' '} + Kursdata hämtas från LADOK Mellanlager API, endpoint: /sokUtbildningstillfalle. För att hämta data för VT 2025, + period P3, school ABE så används:{' '} - {`${koppsApiUrl}courses/offerings?from=20221&skip_coordinator_info=true`} + {`${ladokApiUrl}/sokUtbildningstillfalle?striktKod=false&kodEllerBenamning=*&startPeriod=VT2025&organisation=AIJ,AEH,AFO,AA,ADD,ADB,ADC,ADA,ADI,ADJ,ADH,AFB,AFD,AFC,AGD,AFA,AF,AFF,AID,AFK,AM,AMB,AHG,AFV,AEB,AEE,AFG,AEF,AGE,ALD,AEK,AFL,AGC,AIG,AHC,AGF,AFI,AKC,ALB,AFJ,ALC,AE,AEA,ADF,AKA,AK,AKB,AIE,AIB,AIC,AIA,AI,AFM,AEG,AHE,AHF,AIF,A,AAB,AAC,AD,AFH,AFE,ALF,AFT,ALA,AL,AHD,AFP,AHB,AHA,AH,AGI,AGA,AG,AGB,AEC,ALI,AED`} . Kurs-PM med kursomgångar som inte startar under VT 2022 filtreras bort. Detta görs genom att förkasta - kursomgångar som inte uppfyller kriterierna: course.first_yearsemester == 20221, course.first_period===20221P3, - SCHOOL_MAP[course.school_code]=== 'ABE'. Datum som används för att avgöra om kurs-PM publicerades - innan kursomgången startade är: course.offered_semesters[{{20221}}].start_date. + kursomgångar som inte uppfyller kriterierna: course.startPeriod == VT2025, + course.forstaUndervisningsdatum.kthStudyPeriod == 3, SCHOOL_MAP[course.school_code] === 'ABE'. Datum + som används för att avgöra om kurs-PM publicerades innan kursomgången startade är: + course.forstaUndervisningsdatum.date.
En tidigare version av Publicera ny kursanalys och kursdata innehöll möjligheten att ladda upp kurs-PM @@ -99,7 +100,7 @@ const swedishMemosSummarySection = {
Här visas en sammanställning över antalet publicerade kurs-PM för vald skola, år och läsperiod. Den data som - används hämtas från Kopps och Om kursen. + används hämtas från LADOK och Om kursen.
Tabellen visar antalet kurser som startade och antalet kurs-PM som publicerades under den valda läsperioden. De diff --git a/public/js/app/pages/__tests__/CourseStatisticsPageCompilationSV.test.js b/public/js/app/pages/__tests__/CourseStatisticsPageCompilationSV.test.js index ee1ad852..3d711fdf 100644 --- a/public/js/app/pages/__tests__/CourseStatisticsPageCompilationSV.test.js +++ b/public/js/app/pages/__tests__/CourseStatisticsPageCompilationSV.test.js @@ -69,7 +69,7 @@ const memos2021 = { totalNumberOfMemosPublishedBeforeDeadline: 36, }, documentType: 'courseMemo', - koppsApiBasePath: 'https://api-r.referens.sys.kth.se/api/kopps/v2/', + ladokApiBasePath: 'https://ladok-mellanlagring-lab.azure-api.net', documentsApiBasePath: 'http://localhost/api/kurs-pm-data', school: 'allSchools', periods: ['0', '1', '2', '3', '4', '5'], @@ -129,7 +129,7 @@ const memos2020 = { totalNumberOfMemosPublishedBeforeDeadline: 45, }, documentType: 'courseMemo', - koppsApiBasePath: 'https://api-r.referens.sys.kth.se/api/kopps/v2/', + ladokApiBasePath: 'https://ladok-mellanlagring-lab.azure-api.net', documentsApiBasePath: 'http://localhost/api/kurs-pm-data', school: 'allSchools', periods: ['0', '1', '2', '3', '4', '5'], diff --git a/server/apiCalls/koppsCourseData.js b/server/apiCalls/koppsCourseData.js deleted file mode 100644 index 3b4f2231..00000000 --- a/server/apiCalls/koppsCourseData.js +++ /dev/null @@ -1,43 +0,0 @@ -'use strict' - -const log = require('@kth/log') -const redis = require('kth-node-redis') -const connections = require('@kth/api-call').Connections -const { server: config } = require('../configuration') - -const koppsOpts = { - log, - https: true, - redis, - cache: config.cache, - timeout: 5000, - defaultTimeout: config.koppsApi.defaultTimeout, - retryOnESOCKETTIMEDOUT: true, - useApiKey: false, // skip key -} - -config.koppsApi.doNotCallPathsEndpoint = true // skip checking _paths, because kopps doesnt have it -config.koppsApi.connected = true - -const koppsConfig = { - koppsApi: config.koppsApi, -} - -const api = connections.setup(koppsConfig, koppsConfig, koppsOpts) - -async function getCoursesAndOfferings(semester) { - // ?? semester - const { client } = api.koppsApi - const uri = `${config.koppsApi.basePath}courses/offerings?from=${semester}&skip_coordinator_info=true` - try { - return await client.getAsync({ uri, useCache: true }) - } catch (err) { - log.debug('Kopps is not available', err) - return err - } -} - -module.exports = { - koppsApi: api, - getCoursesAndOfferings, -} diff --git a/server/apiCalls/ladokApi.js b/server/apiCalls/ladokApi.js index 01d90199..ece0e207 100644 --- a/server/apiCalls/ladokApi.js +++ b/server/apiCalls/ladokApi.js @@ -51,9 +51,21 @@ async function getPeriods() { } } +async function getAllCourseRounds(query, lang) { + try { + const courseRounds = await client.getAllCourseRounds(query, lang) + + return courseRounds + } catch (error) { + log.error(error) + return undefined + } +} + module.exports = { getCourseAndRounds, getExaminationModules, getLadokSyllabuses, getPeriods, + getAllCourseRounds, } diff --git a/server/apiCalls/transformers/memos.js b/server/apiCalls/transformers/memos.js index 89a06978..5b98bd88 100644 --- a/server/apiCalls/transformers/memos.js +++ b/server/apiCalls/transformers/memos.js @@ -10,11 +10,14 @@ const { findMemosForApplicationCode } = require('./docs') const _memosPerCourseOffering = async (parsedOfferings, memos) => { const courseOfferings = [] await parsedOfferings.forEach(offering => { - const { courseCode, firstSemester, courseRoundApplications } = offering - const [courseRoundApplication] = courseRoundApplications - const { course_round_application_code: applicationCode } = courseRoundApplication + const { courseCode, firstSemester, courseRoundApplicationCode } = offering let courseMemoInfo = {} - const memosForApplicationCode = findMemosForApplicationCode(memos, courseCode, firstSemester, applicationCode) + const memosForApplicationCode = findMemosForApplicationCode( + memos, + courseCode, + firstSemester, + courseRoundApplicationCode + ) if (memosForApplicationCode.length === 1) { const [publishedMemo] = memosForApplicationCode diff --git a/server/apiCalls/transformers/memos.test.js b/server/apiCalls/transformers/memos.test.js index a0056472..cf726ce0 100644 --- a/server/apiCalls/transformers/memos.test.js +++ b/server/apiCalls/transformers/memos.test.js @@ -12,11 +12,7 @@ const offering_SF1625_202121 = { connectedPrograms: 'TTGTM-2', courseCode: 'SF1625', period: 'P1', - courseRoundApplications: [ - { - course_round_application_code: '1', - }, - ], + courseRoundApplicationCode: '1', } const memo_SF1625_202121_base = (courseCode = 'SF1625') => ({ courseCode, @@ -39,11 +35,7 @@ const offering_SF1625_202122 = { connectedPrograms: '', courseCode: 'SF1625', period: 'P1', - courseRoundApplications: [ - { - course_round_application_code: '2', - }, - ], + courseRoundApplicationCode: '2', } const memo_SF1625_202122_base = (courseCode = 'SF1625') => ({ courseCode, @@ -119,28 +111,28 @@ describe('Memos functions to count memos for one school', () => { const { combinedMemosPerSchool } = await memosPerSchool(offerings, memos) expect(combinedMemosPerSchool).toMatchInlineSnapshot(` - { - "schools": { - "ABE": { - "numberOfCourses": 1, - "numberOfMemosPublishedBeforeDeadline": 1, - "numberOfMemosPublishedBeforeStart": 1, - "numberOfUniqPdfMemos": 0, - "numberOfUniqWebAndPdfMemos": 1, - "numberOfUniqWebMemos": 1, - "uniqueCourseCodeDates": [ - "SF1625-2021-08-30-2021-10-29", - ], - "uniqueCourseCodeDatesWithoutMemo": [], - }, - }, - "totalCourses": 1, - "totalNumberOfMemosPublishedBeforeDeadline": 1, - "totalNumberOfMemosPublishedBeforeStart": 1, - "totalNumberOfPdfMemos": 0, - "totalNumberOfWebMemos": 1, - } - `) +{ + "schools": { + "ABE": { + "numberOfCourses": 1, + "numberOfMemosPublishedBeforeDeadline": 1, + "numberOfMemosPublishedBeforeStart": 1, + "numberOfUniqPdfMemos": 0, + "numberOfUniqWebAndPdfMemos": 1, + "numberOfUniqWebMemos": 1, + "uniqueCourseCodeDates": [ + "SF1625-2021-08-30-2021-10-29", + ], + "uniqueCourseCodeDatesWithoutMemo": [], + }, + }, + "totalCourses": 1, + "totalNumberOfMemosPublishedBeforeDeadline": 1, + "totalNumberOfMemosPublishedBeforeStart": 1, + "totalNumberOfPdfMemos": 0, + "totalNumberOfWebMemos": 1, +} +`) expect(Object.keys(combinedMemosPerSchool.schools).length).toBe(1) expect(combinedMemosPerSchool.schools.ABE.numberOfCourses).toBe(1) @@ -191,67 +183,63 @@ describe('Memos functions to count memos for one school', () => { const { combinedMemosPerSchool, offeringsWithMemos } = await memosPerSchool(offerings, memos) expect(offeringsWithMemos.length).toBe(1) expect(offeringsWithMemos).toMatchInlineSnapshot(` - [ - { - "connectedPrograms": "TTGTM-2", - "courseCode": "SF1625", - "courseMemoInfo": { - "applicationCodes": [ - "1", - ], - "courseCode": "SF1625", - "isPdf": false, - "lastChangeDate": "Tue Aug 02 2021 10:19:17 GMT+0000 (Coordinated Universal Time)", - "memoCommonLangAbbr": "sv", - "memoEndPoint": "SF162520212-1", - "memoName": "SF1625CMEDT1 (Startdatum 2021-08-30, Svenska)", - "publishedData": { - "offeringStartTime": "2021-08-30", - "publishedBeforeDeadline": true, - "publishedBeforeStart": true, - "publishedTime": "2021-08-02", - }, - "semester": "20212", - "version": 1, - }, - "courseRoundApplications": [ - { - "course_round_application_code": "1", - }, - ], - "departmentName": "ABE/Geoinformatik", - "endDate": "2021-10-29", - "firstSemester": "20212", - "period": "P1", - "schoolMainCode": "ABE", - "startDate": "2021-08-30", - }, - ] - `) +[ + { + "connectedPrograms": "TTGTM-2", + "courseCode": "SF1625", + "courseMemoInfo": { + "applicationCodes": [ + "1", + ], + "courseCode": "SF1625", + "isPdf": false, + "lastChangeDate": "Tue Aug 02 2021 10:19:17 GMT+0000 (Coordinated Universal Time)", + "memoCommonLangAbbr": "sv", + "memoEndPoint": "SF162520212-1", + "memoName": "SF1625CMEDT1 (Startdatum 2021-08-30, Svenska)", + "publishedData": { + "offeringStartTime": "2021-08-30", + "publishedBeforeDeadline": true, + "publishedBeforeStart": true, + "publishedTime": "2021-08-02", + }, + "semester": "20212", + "version": 1, + }, + "courseRoundApplicationCode": "1", + "departmentName": "ABE/Geoinformatik", + "endDate": "2021-10-29", + "firstSemester": "20212", + "period": "P1", + "schoolMainCode": "ABE", + "startDate": "2021-08-30", + }, +] +`) expect(combinedMemosPerSchool).toMatchInlineSnapshot(` - { - "schools": { - "ABE": { - "numberOfCourses": 1, - "numberOfMemosPublishedBeforeDeadline": 1, - "numberOfMemosPublishedBeforeStart": 1, - "numberOfUniqPdfMemos": 0, - "numberOfUniqWebAndPdfMemos": 1, - "numberOfUniqWebMemos": 1, - "uniqueCourseCodeDates": [ - "SF1625-2021-08-30-2021-10-29", - ], - "uniqueCourseCodeDatesWithoutMemo": [], - }, - }, - "totalCourses": 1, - "totalNumberOfMemosPublishedBeforeDeadline": 1, - "totalNumberOfMemosPublishedBeforeStart": 1, - "totalNumberOfPdfMemos": 0, - "totalNumberOfWebMemos": 1, - } - `) +{ + "schools": { + "ABE": { + "numberOfCourses": 1, + "numberOfMemosPublishedBeforeDeadline": 1, + "numberOfMemosPublishedBeforeStart": 1, + "numberOfUniqPdfMemos": 0, + "numberOfUniqWebAndPdfMemos": 1, + "numberOfUniqWebMemos": 1, + "uniqueCourseCodeDates": [ + "SF1625-2021-08-30-2021-10-29", + ], + "uniqueCourseCodeDatesWithoutMemo": [], + }, + }, + "totalCourses": 1, + "totalNumberOfMemosPublishedBeforeDeadline": 1, + "totalNumberOfMemosPublishedBeforeStart": 1, + "totalNumberOfPdfMemos": 0, + "totalNumberOfWebMemos": 1, +} +`) }) test('One course (one offering) and this offering has a memo published after course start', async () => { diff --git a/server/apiCalls/transformers/memosCourseOfferings.test.js b/server/apiCalls/transformers/memosCourseOfferings.test.js index d12bf688..5d02da29 100644 --- a/server/apiCalls/transformers/memosCourseOfferings.test.js +++ b/server/apiCalls/transformers/memosCourseOfferings.test.js @@ -12,11 +12,7 @@ const offering_SF1625_202121 = { connectedPrograms: 'TTGTM-2', courseCode: 'SF1625', period: 'P1', - courseRoundApplications: [ - { - course_round_application_code: '1', - }, - ], + courseRoundApplicationCode: '1', } const memo_SF1625_202121_base = { courseCode: 'SF1625', @@ -40,11 +36,7 @@ const offering_SF1625_202122 = { connectedPrograms: '', courseCode: 'SF1625', period: 'P1', - courseRoundApplications: [ - { - course_round_application_code: '2', - }, - ], + courseRoundApplicationCode: '2', } const memo_SF1625_202122_base = { courseCode: 'SF1625', @@ -65,43 +57,39 @@ describe('Count memos and courses', () => { const offeringsWithMemos = await memosPerCourseOffering(offerings, memos) expect(offeringsWithMemos).toMatchInlineSnapshot(` - [ - { - "connectedPrograms": "TTGTM-2", - "courseCode": "SF1625", - "courseMemoInfo": { - "applicationCodes": [ - "1", - ], - "courseCode": "SF1625", - "isPdf": false, - "lastChangeDate": "Tue Aug 02 2021 10:19:17 GMT+0000 (Coordinated Universal Time)", - "memoCommonLangAbbr": "sv", - "memoEndPoint": "SF162520212-1", - "memoName": "CMEDT1 (Startdatum 2021-08-30, Svenska)", - "publishedData": { - "offeringStartTime": "2021-08-30", - "publishedBeforeDeadline": true, - "publishedBeforeStart": true, - "publishedTime": "2021-08-02", - }, - "semester": "20212", - "version": 1, - }, - "courseRoundApplications": [ - { - "course_round_application_code": "1", - }, - ], - "departmentName": "ABE/Geoinformatik", - "endDate": "2021-10-29", - "firstSemester": "20212", - "period": "P1", - "schoolMainCode": "ABE", - "startDate": "2021-08-30", - }, - ] - `) +[ + { + "connectedPrograms": "TTGTM-2", + "courseCode": "SF1625", + "courseMemoInfo": { + "applicationCodes": [ + "1", + ], + "courseCode": "SF1625", + "isPdf": false, + "lastChangeDate": "Tue Aug 02 2021 10:19:17 GMT+0000 (Coordinated Universal Time)", + "memoCommonLangAbbr": "sv", + "memoEndPoint": "SF162520212-1", + "memoName": "CMEDT1 (Startdatum 2021-08-30, Svenska)", + "publishedData": { + "offeringStartTime": "2021-08-30", + "publishedBeforeDeadline": true, + "publishedBeforeStart": true, + "publishedTime": "2021-08-02", + }, + "semester": "20212", + "version": 1, + }, + "courseRoundApplicationCode": "1", + "departmentName": "ABE/Geoinformatik", + "endDate": "2021-10-29", + "firstSemester": "20212", + "period": "P1", + "schoolMainCode": "ABE", + "startDate": "2021-08-30", + }, +] +`) }) test('One course (two offerings) and one offering has a published memo', async () => { @@ -116,23 +104,19 @@ describe('Count memos and courses', () => { expect(offeringWithoutMemos.courseMemoInfo).toMatchInlineSnapshot(`{}`) expect(offeringWithoutMemos).toMatchInlineSnapshot(` - { - "connectedPrograms": "", - "courseCode": "SF1625", - "courseMemoInfo": {}, - "courseRoundApplications": [ - { - "course_round_application_code": "2", - }, - ], - "departmentName": "ABE/Geoinformatik", - "endDate": "2021-10-29", - "firstSemester": "20212", - "period": "P1", - "schoolMainCode": "ABE", - "startDate": "2021-08-30", - } - `) +{ + "connectedPrograms": "", + "courseCode": "SF1625", + "courseMemoInfo": {}, + "courseRoundApplicationCode": "2", + "departmentName": "ABE/Geoinformatik", + "endDate": "2021-10-29", + "firstSemester": "20212", + "period": "P1", + "schoolMainCode": "ABE", + "startDate": "2021-08-30", +} +`) }) test('One course (two offerings) and each offering has a published memo', async () => { const offerings = [offering_SF1625_202121, offering_SF1625_202122] diff --git a/server/apiCalls/transformers/offerings-memos.test.js b/server/apiCalls/transformers/offerings-memos.test.js index 72031b31..8b5195bd 100644 --- a/server/apiCalls/transformers/offerings-memos.test.js +++ b/server/apiCalls/transformers/offerings-memos.test.js @@ -5,45 +5,50 @@ import { filterOfferingsForMemos, semestersInParsedOfferings } from './offerings describe('Memos functions to parse and filter offerings', () => { test('parse and filter offering for memos', () => { const expectedCourse = { - connected_programs: [{ code: 'CINTE2', study_year: '2020', spec_code: 'iNTeresting' }], - course_code: 'SF1624', - department_name: 'CBH/Fiber- och Polymerteknologi', - first_yearsemester: '20202', - first_period: '20202P1', - offered_semesters: [{ end_date: '2021-01-10', semester: '20202', start_date: '2020-10-10' }], - school_code: 'BIO', + delAvProgram: [{ kod: 'CINTE2', arskurs: '2020', inriktning: 'iNTeresting' }], + kod: 'SF1624', + organisation: { name: 'CBH/Fiber- och Polymerteknologi' }, + startperiod: { inDigits: '20202' }, + forstaUndervisningsdatum: { date: '2020-10-10', kthStudyPeriod: '1' }, + sistaUndervisningsdatum: { date: '2021-01-10' }, + schoolCode: 'CBH', } + const tooOldCourse = { - connected_programs: [{ code: 'CINTE1', study_year: '2020', spec_code: 'k' }], - course_code: 'SF1625', - first_yearsemester: '20192', - first_period: '20192P1', - offered_semesters: [], - school_code: 'CBH', + delAvProgram: [{ kod: 'CINTE1', arskurs: '2020', inriktning: 'k' }], + kod: 'SF1625', + startperiod: { inDigits: '20192' }, + forstaUndervisningsdatum: { date: '2019-08-01', kthStudyPeriod: '1' }, + sistaUndervisningsdatum: { date: '2019-10-10' }, + schoolCode: 'CBH', } + const wrongSchoolCourse = { - connected_programs: [{ code: 'CINTE1', study_year: '2020', spec_code: 'D' }], - course_code: 'SF1629', - first_yearsemester: '20202', - first_period: '20202P1', - offered_semesters: [], - school_code: 'ITM', + delAvProgram: [{ kod: 'CINTE1', arskurs: '2020', inriktning: 'D' }], + kod: 'SF1629', + organisation: { name: 'ITM/Production' }, + startperiod: { inDigits: '20202' }, + forstaUndervisningsdatum: { date: '2020-10-10', kthStudyPeriod: '1' }, + sistaUndervisningsdatum: { date: '2021-01-10' }, + schoolCode: 'ITM', } + const courses = [expectedCourse, tooOldCourse, wrongSchoolCourse] const parsedOfferings = filterOfferingsForMemos(courses, ['20201', '20202'], ['1', '2'], 'CBH') + expect(parsedOfferings.length).toBe(1) const [offering] = parsedOfferings - expect(offering.firstSemester).toBe(expectedCourse.first_yearsemester) - expect(offering.startDate).toBe(expectedCourse.offered_semesters[0].start_date) - expect(offering.endDate).toBe(expectedCourse.offered_semesters[0].end_date) + expect(offering.firstSemester).toBe('20202') + expect(offering.startDate).toBe('2020-10-10') + expect(offering.endDate).toBe('2021-01-10') expect(offering.schoolMainCode).toBe('CBH') - expect(offering.departmentName).toBe(expectedCourse.department_name) + expect(offering.departmentName).toBe(expectedCourse.organisation.name) expect(offering.connectedPrograms).toBe('CINTE2-iNTeresting-2020') - expect(offering.courseCode).toBe(expectedCourse.course_code) + expect(offering.courseCode).toBe('SF1624') expect(offering.period).toBe('P1') const semestersInMemos = semestersInParsedOfferings(parsedOfferings) - expect(semestersInMemos.length).toBe(1) + expect(semestersInMemos).toEqual(['20202']) }) }) diff --git a/server/apiCalls/transformers/offerings.js b/server/apiCalls/transformers/offerings.js index 095e39fd..f4df403b 100644 --- a/server/apiCalls/transformers/offerings.js +++ b/server/apiCalls/transformers/offerings.js @@ -2,14 +2,14 @@ const { isCorrectSchool, SCHOOL_MAP } = require('./schools') /** * Creates string of programs in list. - * @param {[]} programs Programs as returned by '/api/kopps/v2/courses/offerings' in 'connected_programs'. + * @param {[]} programs Programs as returned by courseOfferings[].delAvProgram. * @returns {string} String with program data, separated by comma */ function _getProgramList(programs) { const programsList = (programs && programs.map( - ({ code, study_year: studyYear, spec_code: specCode }) => + ({ kod: code, arskurs: studyYear, inriktning: specCode }) => `${code}${specCode ? '-' + specCode : ''}-${studyYear}` )) || [] @@ -20,11 +20,11 @@ function _getProgramList(programs) { /** * Creates object of offering to save essential informations. - * @param {string} firstSemester semester when course offering is started + * @param {string} firstSemester semester when course offering is started (We use startPeriod from ladok) * @param {number} startDate date when course offering is started - * @param {{}} course Each course as returned by '/api/kopps/v2/courses/offerings' in 'courses'. - * @param {string} course.course_code - The course code - * @param {string} course.department_name - The ddepartment name + * @param {{}} courseOffering Each courseOffering as returned by 'SokUtbildningsTillfalleSlimItem' in 'courses'. + * @param {string} courseOffering.kod - The course code + * @param {string} courseOffering.organisation.name - The ddepartment name * @returns {{}} Object with course offering data */ function _formOffering(firstSemester, startDate, endDate, course) { @@ -32,10 +32,10 @@ function _formOffering(firstSemester, startDate, endDate, course) { endDate, firstSemester, startDate, - schoolMainCode: SCHOOL_MAP[course.school_code] || 'Others', - departmentName: course.department_name, - connectedPrograms: _getProgramList(course.connected_programs), - courseCode: course.course_code, + schoolMainCode: SCHOOL_MAP[course.schoolCode] || 'Others', + departmentName: course?.organisation?.name, + connectedPrograms: _getProgramList(course.delAvProgram), + courseCode: course.kod, } } /** @@ -58,32 +58,14 @@ function _sortOfferedSemesters(offeredSemesters) { } /** - * @param {Object[]} courseOfferedSemesters Courses offerings - * @param {Object} courseOfferedSemesters[] Course offering - * @returns {{}} Array, containing offered semesters and startDate - */ -function _findCourseStartEndDates(courseOfferedSemesters) { - const offeredSemesters = Array.isArray(courseOfferedSemesters) ? courseOfferedSemesters : [] - const sortedArray = _sortOfferedSemesters(offeredSemesters) - - const firstOffering = sortedArray.at(0) ?? {} - const lastOffering = sortedArray.at(-1) ?? {} - - const { start_date: courseStartDate = '' } = firstOffering - const { semester: lastSemester = '', end_date: courseEndDate = '', end_week: courseEndWeek } = lastOffering - - return { endDate: courseEndDate, endWeek: courseEndWeek, lastSemester, startDate: courseStartDate } -} - -/** - * Parses courses offerings from Kopps and returns an object with one list for course memos which are created before course starts: + * Parses courses offerings from ladok and returns an object with one list for course memos which are created before course starts: * - List containing offerings that starts with semester parameter. This is used for course memos. - * @param {Object[]} courses Courses as returned by '/api/kopps/v2/courses/offerings'. - * @param {string} courses[].first_yearsemester - The start semester of a course - * @param {Object[]} courses[].offered_semesters - The list of offered semesters of a course - * @param {string} courses[].offered_semesters[].end_date - The end date of a course offering - * @param {string} courses[].offered_semesters[].semester - The current semester of a course offering - * @param {string} courses[].offered_semesters[].start_date - The start date of a course offering + * @param {Object[]} courseOfferings CourseOfferings as returned by 'SokUtbildningsTillfalleSlimItem'. + * @param {string} courseOfferings[].first_yearsemester - The start semester of a course + * @param {Object[]} courseOfferings[].offered_semesters - The list of offered semesters of a course + * @param {string} courseOfferings[].offered_semesters[].end_date - The end date of a course offering + * @param {string} courseOfferings[].offered_semesters[].semester - The current semester of a course offering + * @param {string} courseOfferings[].offered_semesters[].start_date - The start date of a course offering * @param {Object[]} chosenSemesters Semesters strings for which data is fetched * @param {string} chosenSemesters[] Semester string chosen by user, 5 digits in string format * @param {Object[]} chosenPeriods Periods strings for which data is fetched, 0-5 @@ -91,30 +73,25 @@ function _findCourseStartEndDates(courseOfferedSemesters) { * @param {string} chosenSchool School name, or if all schools are chosen then 'allSchools * @returns {[]} Array, containing offerings’ relevant data */ -function filterOfferingsForMemos(courses = [], chosenSemesters = [], chosenPeriods = [], chosenSchool = '') { +function filterOfferingsForMemos(courseOfferings = [], chosenSemesters = [], chosenPeriods = [], chosenSchool = '') { const parsedOfferings = [] - // const courses = [coursesR[0], coursesR[1]] - if (Array.isArray(courses)) { - courses.forEach(course => { + if (Array.isArray(courseOfferings)) { + courseOfferings.forEach(course => { // eslint-disable-next-line camelcase - const { - first_yearsemester: firstSemester, - first_period: firstYearAndPeriod, - offered_semesters: courseOfferedSemesters, - school_code: schoolCode, - course_round_applications: courseRoundApplications, - } = course + const { schoolCode, tillfalleskod: courseRoundApplicationCode } = course - const firstPeriodNumber = firstYearAndPeriod.substr(-1) - const firstPerioLabel = firstYearAndPeriod.substr(-2) + const firstSemester = course?.startperiod?.inDigits + const startDate = course?.forstaUndervisningsdatum?.date + const endDate = course?.sistaUndervisningsdatum?.date + const firstPeriodNumber = course?.forstaUndervisningsdatum?.kthStudyPeriod + const firstPerioLabel = firstPeriodNumber ? `P${firstPeriodNumber}` : undefined const isStartedInChosenPeriods = chosenSemesters.includes(String(firstSemester)) && chosenPeriods.includes(String(firstPeriodNumber)) const isChosenSchool = isCorrectSchool(chosenSchool, schoolCode) if (isStartedInChosenPeriods && isChosenSchool) { - const { endDate, startDate } = _findCourseStartEndDates(courseOfferedSemesters) const offering = _formOffering(firstSemester, startDate, endDate, course) - parsedOfferings.push({ ...offering, period: firstPerioLabel, courseRoundApplications }) + parsedOfferings.push({ ...offering, period: firstPerioLabel, courseRoundApplicationCode }) } }) } @@ -123,7 +100,6 @@ function filterOfferingsForMemos(courses = [], chosenSemesters = [], chosenPerio module.exports = { filterOfferingsForMemos, - findCourseStartEndDates: _findCourseStartEndDates, semestersInParsedOfferings, sortOfferedSemesters: _sortOfferedSemesters, } diff --git a/server/controllers/statisticsCtrl.js b/server/controllers/statisticsCtrl.js index 9f7d5da5..5fe8f7f4 100644 --- a/server/controllers/statisticsCtrl.js +++ b/server/controllers/statisticsCtrl.js @@ -3,7 +3,7 @@ const log = require('@kth/log') const languageUtils = require('@kth/kth-node-web-common/lib/language') const memoApi = require('../apiCalls/memoApi') -const koppsCourseData = require('../apiCalls/koppsCourseData') +const ladokApi = require('../apiCalls/ladokApi') const { filterOfferingsForMemos, semestersInParsedOfferings } = require('../apiCalls/transformers/offerings') const { memosPerSchool } = require('../apiCalls/transformers/memos') @@ -14,6 +14,19 @@ const paths = require('../server').getPaths() const { getServerSideFunctions } = require('../utils/serverSideRendering') const { createStatisticsServerSideContext } = require('../ssr-context/createStatisticsServerSideContext') +// Add mapping function for school codes +function _mapSchoolCode(school) { + const schoolMapping = { + ABE: 'A', + CBH: 'C', + EES: 'J', + ITM: 'I', + SCI: 'S', + } + + return schoolMapping[school] || school +} + async function getIndex(req, res, next) { const lang = languageUtils.getLanguage(res) || 'sv' try { @@ -65,21 +78,10 @@ async function getIndex(req, res, next) { } } -async function _getCoursesPerSemester(semester) { - // TODO: FETCH DATA FROM KOOPPS AND FROM KURS-PM/KURSANALYS API depending on document type - const { body: courses } = await koppsCourseData.getCoursesAndOfferings(semester) - // Object with one array, containing offerings’ relevant data for memo +async function _getCourseOfferings(startPeriod, organisation) { + const startPeriods = startPeriod.join(',') - return courses -} - -async function _getCourses(semesters) { - const courses = [] - for await (const semester of semesters) { - const courseOfferingsPerSemester = await _getCoursesPerSemester(semester) - courses.push(...courseOfferingsPerSemester) - } - return courses + return await ladokApi.getAllCourseRounds({ startPeriod: startPeriods, organisation }, 'en') } async function fetchMemoStatistics(req, res, next) { @@ -92,13 +94,16 @@ async function fetchMemoStatistics(req, res, next) { if (!periods) log.error('periods must be set', periods) if (!school) log.error('school must be set', school) - const chosenSemesters = seasons.map(season => `${year}${season}`).sort() + const chosenSemesters = seasons.map(season => `${season == 1 ? 'VT' : 'HT'}${year}`).sort() + const chosenSemestersInDigits = seasons.map(season => `${year}${season}`).sort() + const sortedPeriods = periods.sort() try { - const courses = await _getCourses(chosenSemesters) + const mappedSchool = _mapSchoolCode(school) + const courseOfferings = await _getCourseOfferings(chosenSemesters, mappedSchool) - const parsedOfferings = filterOfferingsForMemos(courses, chosenSemesters, sortedPeriods, school) + const parsedOfferings = filterOfferingsForMemos(courseOfferings, chosenSemestersInDigits, sortedPeriods, school) // // Semesters found in parsed offerings. Not necessary, startSemesters is the same. const semestersInMemos = semestersInParsedOfferings(parsedOfferings) @@ -112,9 +117,7 @@ async function fetchMemoStatistics(req, res, next) { return res.json({ combinedMemosPerSchool, // small table // in kursinfo-admin-web combinedMemosDataPerSchool, documentType: 'courseMemo', - koppsApiBasePath: `${serverConfig.koppsApi.https ? 'https' : 'http'}://${serverConfig.koppsApi.host}${ - serverConfig.koppsApi.basePath - }`, + ladokApiBasePath: serverConfig.ladokMellanlagerApi.baseUrl, documentsApiBasePath: `${serverConfig.nodeApi.kursPmDataApi.https ? 'https' : 'http'}://${ serverConfig.nodeApi.kursPmDataApi.host }${serverConfig.nodeApi.kursPmDataApi.proxyBasePath}`, @@ -124,7 +127,7 @@ async function fetchMemoStatistics(req, res, next) { seasons, semesters: chosenSemesters, semestersInMemos, - totalOfferings: courses.length, + totalOfferings: courseOfferings.length, year, }) } catch (error) {