feat: 🎸 Hämta lärare och skolkontakter från api-skolplattfomen och visa lärarens namn i schemat (#589)

* feat: 🎸 Get list of teachers from api-skolplattfomen

* feat: 🎸 teachers and school contacts in hooks

* style: 💄 lint

* chore: 🤖 fakeData for matching some lessons with teachers

* feat: 🎸 Teacher's names in timetable (where available)

* test: 💍 fix failing tests (add mocks for new calls)
This commit is contained in:
Kajetan Kazimierczak 2021-12-10 10:33:52 +01:00 committed by GitHub
parent 0e95486e5d
commit b7dbd356c6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 449 additions and 5 deletions

View File

@ -13,7 +13,9 @@ import {
NewsItem,
Notification,
ScheduleItem,
SchoolContact,
Skola24Child,
Teacher,
TimetableEntry,
toMarkdown,
URLSearchParams,
@ -248,6 +250,21 @@ export class ApiHjarntorget extends EventEmitter implements Api {
return Promise.resolve([])
}
public async getTeachers(child: EtjanstChild): Promise<Teacher[]> {
if (!this.isLoggedIn) {
throw new Error('Not logged in...')
}
return Promise.resolve([])
}
public async getSchoolContacts(child: EtjanstChild): Promise<SchoolContact[]> {
if (!this.isLoggedIn) {
throw new Error('Not logged in...')
}
return Promise.resolve([])
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
async getNews(_child: EtjanstChild): Promise<NewsItem[]> {
if (!this.isLoggedIn) {

View File

@ -17,7 +17,9 @@ import {
ScheduleItem,
Skola24Child,
SSOSystem,
Teacher,
TimetableEntry,
SchoolContact,
URLSearchParams,
User,
wrap,
@ -271,6 +273,39 @@ export class ApiSkolplattformen extends EventEmitter implements Api {
return parse.classmates(data)
}
public async getTeachers(child: EtjanstChild): Promise<Teacher[]> {
if (this.isFake) return fakeResponse(fake.teachers(child))
const session = this.getRequestInit()
const schoolForms = (child.status || '').split(';')
let teachers: Teacher[] = []
for(let i = 0; i< schoolForms.length; i+=1){
const url = routes.teachers(child.sdsId, schoolForms[i])
// eslint-disable-next-line no-await-in-loop
const response = await this.fetch(`teachers_${schoolForms[i]}`, url, session)
// eslint-disable-next-line no-await-in-loop
const data = await response.json()
teachers = [
...teachers,
...parse.teachers(data)
]
}
return teachers
}
public async getSchoolContacts(child: EtjanstChild): Promise<SchoolContact[]> {
if(this.isFake) return fakeResponse(fake.schoolContacts(child))
const url = routes.schoolContacts(child.sdsId, child.schoolId || '')
const session = this.getRequestInit()
const response = await this.fetch('schoolContacts', url, session)
const data = await response.json()
return parse.schoolContacts(data)
}
public async getSchedule(
child: EtjanstChild,
from: DateTime,
@ -524,6 +559,8 @@ export class ApiSkolplattformen extends EventEmitter implements Api {
return parse.timetable(json, year, week, lang)
}
public async logout() {
this.isFake = false
this.personalNumber = undefined

View File

@ -2,5 +2,7 @@ export * from './data'
export * from './children'
export * from './menu'
export * from './classmates'
export * from './teachers'
export * from './timetable'
export * from './schoolContacts'
export * from './news'

View File

@ -0,0 +1,47 @@
import { SchoolContact, Child } from '@skolplattformen/api';
import { children } from './children'
export const schoolContacts = (child: Child): SchoolContact[] => schoolContactData.get(child.id) ?? []
const [child1,child2] = children()
const schoolContactData = new Map<string, SchoolContact[]>([
[
child1.id, [
{
title: "Expedition",
name: null,
phone: "508 000 00",
email: "",
schoolName: "Vallaskolan",
className: null,
},
{
title: "Rektor",
name: "Alvar Sträng",
phone: "08-50800001",
email: "alvar.strang@edu.stockholm.se",
schoolName: null,
className: null,
}
]],
[
child2.id, [
{
title: "Expedition",
name: null,
phone: "508 000 00",
email: "",
schoolName: "Vallaskolan",
className: null,
},
{
title: "Rektor",
name: "Alvar Sträng",
phone: "08-50800001",
email: "alvar.strang@edu.stockholm.se",
schoolName: null,
className: null,
}
]]
])

View File

@ -0,0 +1,81 @@
import { Teacher, Child } from '@skolplattformen/api';
import { children } from './children'
export const teachers = (child: Child): Teacher[] => teacherData.get(child.id) ?? []
const [child1,child2] = children()
const teacherData = new Map<string, Teacher[]>([
[
child1.id, [
{
id: 15662220,
firstname: "Cecilia",
sisId: null,
lastname: "Test",
email: "cecilia.test@edu.stockholm.se",
phoneWork: null,
active: true,
status: " S",
timeTableAbbreviation: 'CTE',
},
{
id: 15662221,
firstname: "Anna",
lastname: "Test",
sisId: null,
email: "anna.test@edu.stockholm.se",
phoneWork: '08000000',
active: true,
status: " GR",
timeTableAbbreviation: 'ATE',
},
{
id: 15662221,
firstname: "Greta",
lastname: "Test",
sisId: null,
email: null,
phoneWork: '08000001',
active: true,
status: " F",
timeTableAbbreviation: 'GTE',
},
]],
[
child2.id, [
{
id: 15662220,
firstname: "Cecilia",
sisId: null,
lastname: "Test",
email: "cecilia.test@edu.stockholm.se",
phoneWork: null,
active: true,
status: " S",
timeTableAbbreviation: 'CTE',
},
{
id: 15662221,
firstname: "Anna",
lastname: "Test",
sisId: null,
email: "anna.test@edu.stockholm.se",
phoneWork: '08000000',
active: true,
status: " GR",
timeTableAbbreviation: 'ATE',
},
{
id: 15662221,
firstname: "Greta",
lastname: "Test",
sisId: null,
email: null,
phoneWork: '08000001',
active: true,
status: " F",
timeTableAbbreviation: 'GTE',
},
]],
])

View File

@ -26,7 +26,7 @@ export const timetable = (child: Skola24Child): TimetableEntry[] => {
blockName: '',
dayOfWeek: 1,
location: '221',
teacher: 'KUr',
teacher: 'CTe',
timeEnd: '11:35:00',
timeStart: '09:40:00',
dateStart: '2021-04-12T09:40:00.000+02:00',
@ -55,7 +55,7 @@ export const timetable = (child: Skola24Child): TimetableEntry[] => {
blockName: '',
dayOfWeek: 1,
location: '215',
teacher: 'HAl',
teacher: 'ATe, GTe',
timeEnd: '15:45:00',
timeStart: '14:40:00',
dateStart: '2021-04-12T14:40:00.000+02:00',
@ -69,7 +69,7 @@ export const timetable = (child: Skola24Child): TimetableEntry[] => {
blockName: '',
dayOfWeek: 1,
location: '304',
teacher: 'DNi',
teacher: 'CTe,ATe',
timeEnd: '14:25:00',
timeStart: '13:40:00',
dateStart: '2021-04-12T13:40:00.000+02:00',

View File

@ -0,0 +1,50 @@
import { EtjanstResponse } from '../'
import { schoolContacts } from '../schoolContacts'
let response: EtjanstResponse
beforeEach(() => {
response = {
"Success": true,
"Error": null,
"Data": [
{
"Title": "Expedition",
"Name": null,
"Phone": "508 000 00",
"Email": "",
"SchoolName": "Påhittade skolan",
"ClassName": null
},
{
"Title": "Rektor",
"Name": "Andersson, Anna Bella Cecilia",
"Phone": "08-508 000 00",
"Email": "anna.anderssonn@edu.stockholm.se",
"SchoolName": null,
"ClassName": null
}
]
}
})
it('parses teachers correctly', () => {
expect(schoolContacts(response)).toEqual([
{
title: 'Expedition',
name: null,
phone: '508 000 00',
email: '',
schoolName: 'Påhittade skolan',
className: null
},
{
title: 'Rektor',
name: 'Andersson, Anna Bella Cecilia',
phone: '08-508 000 00',
email: 'anna.anderssonn@edu.stockholm.se',
schoolName: null,
className: null
}
])
})

View File

@ -0,0 +1,68 @@
import { EtjanstResponse } from '../'
import { teachers } from '../teachers'
let response: EtjanstResponse
beforeEach(() => {
response = {
"Success": true,
"Error": null,
"Data": [
{
"ID": 156735,
"BATCH": "GR",
"SIS_ID": "F154239A-EA4A-4C6C-A112-0B9581132E3D",
"USERNAME": "anna.andersson",
"SCHOOL_SIS_ID": "DE2E1293-0F40-4B91-9D91-1E99355DC257",
"EMAILADDRESS": null,
"STATUS": " GR",
"ERRORCODE": 0,
"FIRSTNAME": "Anna",
"LASTNAME": "Andersson",
"ACTIVE": true,
"TELWORK": "08 508 0000000"
},
{
"ID": 156690,
"BATCH": "GR",
"SIS_ID": "9EC59FCA-80AD-4774-AABD-427040207E33",
"USERNAME": "gunnar.grymm",
"SCHOOL_SIS_ID": "DE2E1293-0F40-4B91-9D91-1E99355DC257",
"EMAILADDRESS": "gunnar.grymm@edu.stockholm.se",
"STATUS": " F",
"ERRORCODE": 0,
"FIRSTNAME": "Gunnar",
"LASTNAME": "Grymm",
"ACTIVE": true,
"TELWORK": null
}
]
}
})
it('parses teachers correctly', () => {
expect(teachers(response)).toEqual([
{
id: 156735,
sisId: 'F154239A-EA4A-4C6C-A112-0B9581132E3D',
firstname: 'Anna',
lastname: 'Andersson',
email: null,
phoneWork: '08 508 0000000',
active: true,
status: ' GR',
timeTableAbbreviation: 'AAN'
},
{
id: 156690,
sisId: '9EC59FCA-80AD-4774-AABD-427040207E33',
firstname: 'Gunnar',
lastname: 'Grymm',
email: 'gunnar.grymm@edu.stockholm.se',
phoneWork: null,
active: true,
status: ' F',
timeTableAbbreviation: 'GGR'
},
])
})

View File

@ -6,5 +6,7 @@ export * from './menu'
export * from './news'
export * from './notifications'
export * from './schedule'
export * from './schoolContacts'
export * from './teachers'
export * from './timetable'
export * from './user'

View File

@ -0,0 +1,22 @@
import { etjanst } from './etjanst'
import { SchoolContact } from '@skolplattformen/api'
export const schoolContact = ({
title,
name,
phone,
email,
schoolName,
className,
}: any): SchoolContact => ({
title,
name,
phone,
email,
schoolName,
className,
})
export const schoolContacts = (data: any): SchoolContact[] =>
etjanst(data).map(schoolContact)

View File

@ -0,0 +1,29 @@
import { etjanst } from './etjanst'
import { Teacher } from '@skolplattformen/api'
const abbreviate = (firstname?: string, lastname?: string): string =>
`${firstname?.substr(0,1)}${lastname?.substr(0,2)}`.toUpperCase()
export const teacher = ({
id,
sisId,
firstname,
lastname,
emailaddress,
telwork,
active,
status,
}: any): Teacher => ({
id,
sisId,
firstname,
lastname,
email: emailaddress,
phoneWork: telwork,
active,
status,
timeTableAbbreviation: abbreviate(firstname, lastname)
})
export const teachers = (data: any): Teacher[] =>
etjanst(data).map(teacher)

View File

@ -20,6 +20,12 @@ export const calendar = (childId: string) =>
export const classmates = (childId: string) =>
`${urlLoggedIn}/contacts/GetStudentsByClass?studentId=${childId}`
export const teachers = (childId: string, schoolForm: string) =>
`${urlLoggedIn}/contacts/GetTeachersByStudent?studentId=${childId}&schoolForm=${schoolForm}`
export const schoolContacts = (childId: string, schoolId: string) =>
`${urlLoggedIn}/contacts/GetSchoolContacts?schoolId=${schoolId}&studentId=${childId}&schoolForm=Klasslista`
export const user =
'https://etjanst.stockholm.se/vardnadshavare/base/getuserdata'

View File

@ -13,6 +13,8 @@ import {
EtjanstChild,
TimetableEntry,
ScheduleItem,
SchoolContact,
Teacher
} from './types'
export interface Api extends EventEmitter {
@ -30,7 +32,9 @@ export interface Api extends EventEmitter {
getNewsDetails(child: EtjanstChild, item: NewsItem): Promise<any>
getMenu(child: EtjanstChild): Promise<MenuItem[]>
getNotifications(child: EtjanstChild): Promise<Notification[]>
getTeachers(child: EtjanstChild): Promise<Teacher[]>
getSchedule(child: EtjanstChild, from: DateTime, to: DateTime): Promise<ScheduleItem[]>
getSchoolContacts(child: EtjanstChild): Promise<SchoolContact[]>
getSkola24Children(): Promise<Skola24Child[]>
getTimetable(child: Skola24Child, week: number, year: number, lang: Language): Promise<TimetableEntry[]>
logout(): Promise<void>

View File

@ -214,3 +214,24 @@ export interface TimetableEntry extends Subject {
dateStart: string
dateEnd: string
}
export interface Teacher {
id: number
sisId: string
firstname: string
lastname: string
email?: string
phoneWork?: string
active: boolean
status: string
timeTableAbbreviation: string
}
export interface SchoolContact {
title?: string
name?: string
phone?: string
email?: string
schoolName: string
className: string
}

View File

@ -22,6 +22,8 @@ const createApi = () => ({
getNewsDetails: jest.fn(),
getNotifications: jest.fn(),
getSchedule: jest.fn(),
getSchoolContacts: jest.fn(),
getTeachers: jest.fn(),
getTimetable: jest.fn(),
getUser: jest.fn(),
})

View File

@ -9,7 +9,9 @@ import {
NewsItem,
Notification,
ScheduleItem,
SchoolContact,
Skola24Child,
Teacher,
TimetableEntry,
User,
} from '@skolplattformen/api'
@ -201,8 +203,26 @@ export const useSchedule = (child: Child, from: string, to: string) =>
api.getSchedule(child, DateTime.fromISO(from), DateTime.fromISO(to))
)
export const useSchoolContacts = (child: Child) =>
hook<SchoolContact[]>(
'SCHOOL_CONTACTS',
`schoolContacts_${child.id}`,
[],
(s) => s.schoolContacts,
(api) => () => api.getSchoolContacts(child)
)
export const useTeachers = (child: Child) =>
hook<Teacher[]>(
'TEACHERS',
`teachers_${child.id}`,
[],
(s) => s.teachers,
(api) => () => api.getTeachers(child)
)
export const useTimetable = (
child: Skola24Child,
child: Child,
week: number,
year: number,
lang: Language
@ -212,9 +232,31 @@ export const useTimetable = (
`timetable_${child.personGuid}_${week}_${year}_${lang}`,
[],
(s) => s.timetable,
(api) => () => api.getTimetable(child, week, year, lang)
(api) => async () => {
const tt = await api.getTimetable(child, week, year, lang)
const ts = await api.getTeachers(child)
tt.forEach((element) => {
element.teacher = replaceTeacherInitials(element.teacher, ts)
})
return tt
}
)
const replaceTeacherInitials = (
initials: string,
teachers: Teacher[]
): string => {
if (!initials || teachers?.length == 0) return initials
const arr = initials.split(',') || [initials]
const arr2 = arr.map((element) => {
const t = teachers.find(
(t) => t.timeTableAbbreviation === element.trim().toUpperCase()
)
return t ? `${t.firstname} ${t.lastname}` : element
})
return arr2.join(', ')
}
export const useUser = () =>
hook<User>(
'USER',

View File

@ -6,7 +6,9 @@ import {
NewsItem,
Notification,
ScheduleItem,
SchoolContact,
Skola24Child,
Teacher,
TimetableEntry,
User,
} from '@skolplattformen/api'
@ -77,3 +79,5 @@ export const newsDetails = createReducer<NewsItem[]>('NEWS_DETAILS')
export const notifications = createReducer<Notification[]>('NOTIFICATIONS')
export const schedule = createReducer<ScheduleItem[]>('SCHEDULE')
export const timetable = createReducer<TimetableEntry[]>('TIMETABLE')
export const teachers = createReducer<Teacher[]>('TEACHERS')
export const schoolContacts = createReducer<SchoolContact[]>('SCHOOL_CONTACTS')

View File

@ -9,7 +9,9 @@ import {
newsDetails,
notifications,
schedule,
schoolContacts,
skola24Children,
teachers,
timetable,
user,
} from './reducers'
@ -23,7 +25,9 @@ const appReducer = combineReducers({
newsDetails,
notifications,
schedule,
schoolContacts,
skola24Children,
teachers,
timetable,
user,
})

View File

@ -7,7 +7,9 @@ import {
NewsItem,
Notification,
ScheduleItem,
SchoolContact,
Skola24Child,
Teacher,
TimetableEntry,
User,
} from '@skolplattformen/api'
@ -64,6 +66,8 @@ export type EntityName =
| 'NEWS_DETAILS'
| 'NOTIFICATIONS'
| 'SCHEDULE'
| 'SCHOOL_CONTACTS'
| 'TEACHERS'
| 'TIMETABLE'
| 'ALL'
export interface EntityAction<T> extends Action<EntityActionType> {
@ -88,6 +92,8 @@ export interface EntityStoreRootState {
newsDetails: EntityMap<NewsItem>
notifications: EntityMap<Notification[]>
schedule: EntityMap<ScheduleItem[]>
schoolContacts: EntityMap<SchoolContact[]>
teachers: EntityMap<Teacher[]>
timetable: EntityMap<TimetableEntry[]>
}