Skip to content

Commit 57671eb

Browse files
committed
Add sessionize import script
1 parent f624278 commit 57671eb

File tree

8 files changed

+390
-0
lines changed

8 files changed

+390
-0
lines changed

package-lock.json

Lines changed: 6 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
"build": "npm run clean && tsc && NODE_ENV=production rollup --config rollup.config.js",
1111
"clean": "rimraf dist out-tsc",
1212
"deploy": "npm run build && NODE_ENV=production firebase deploy",
13+
"experimental:sessionize:import": "ts-node-script ./scripts/experimental-sessionize-import",
1314
"firestore:copy": "ts-node-script ./scripts/firestore-copy",
1415
"firestore:init": "firebase functions:config:set schedule.enabled=true && firebase deploy --except hosting && ts-node-script ./scripts/firestore-init",
1516
"fix": "concurrently npm:fix:*",
@@ -99,6 +100,7 @@
99100
"jest-runner-prettier": "^0.3.6",
100101
"jest-runner-stylelint": "^2.3.7",
101102
"jest-runner-tsc": "^1.6.0",
103+
"moment": "^2.29.0",
102104
"nunjucks": "^3.2.2",
103105
"prettier": "2.1.2",
104106
"prettier-plugin-package": "1.1.0",
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { importSchedule } from './schedule';
2+
import { importSessions } from './sessions';
3+
import { importSpeakers } from './speakers';
4+
5+
// TODO: accept sessionize file as a param
6+
// TODO: support not having a schedule
7+
// TODO: support multiple tracks on multiple days
8+
9+
importSpeakers()
10+
.then(() => importSessions())
11+
.then(() => importSchedule())
12+
.then(() => {
13+
console.log('Finished');
14+
process.exit();
15+
})
16+
.catch((err: Error) => {
17+
console.log(err);
18+
process.exit();
19+
});
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
import moment from 'moment';
2+
import data from '../../sessionize.json';
3+
import { Schedule } from '../../src/models/schedule';
4+
import { Timeslot } from '../../src/models/timeslot';
5+
import { Track } from '../../src/models/track';
6+
import { firestore } from '../firebase-config';
7+
import { SessionizeSession } from './types';
8+
9+
export const importSchedule = async () => {
10+
const schedule: Schedule = await convertSchedule();
11+
const { length } = await save(schedule);
12+
console.log(`Imported data for ${length} day(s)`);
13+
};
14+
15+
const convertTracks = (): Track[] => {
16+
return data.rooms.map(({ name, id }) => ({ title: name, sessionizeId: id }));
17+
};
18+
19+
const getSessionids = async (): Promise<{ [id: string]: string }> => {
20+
const { docs } = await firestore.collection('sessions').get();
21+
const ids: { [id: string]: string } = {};
22+
23+
docs.forEach((doc) => {
24+
const { sessionizeId } = doc.data() as SessionizeSession;
25+
ids[sessionizeId] = doc.id;
26+
});
27+
28+
return ids;
29+
};
30+
31+
const convertSchedule = async (): Promise<Schedule> => {
32+
const sessionIds = await getSessionids();
33+
const schedule: Schedule = {};
34+
35+
for (const sessionData of data.sessions) {
36+
console.log(`Importing session ${sessionData.title} at ${sessionData.startsAt}`);
37+
const date = moment(sessionData.startsAt).format('YYYY-MM-DD');
38+
if (!schedule[date]) {
39+
schedule[date] = {
40+
date,
41+
dateReadable: moment(sessionData.startsAt).format('MMMM D'),
42+
timeslots: [],
43+
tracks: convertTracks(),
44+
};
45+
}
46+
47+
const timeslot: Timeslot = {
48+
endTime: time(sessionData.endsAt),
49+
startTime: time(sessionData.startsAt),
50+
sessions: [
51+
{
52+
items: [sessionIds[sessionData.id]],
53+
},
54+
],
55+
};
56+
schedule[date].timeslots.push(timeslot);
57+
}
58+
59+
return schedule;
60+
};
61+
62+
const time = (date: string): string => {
63+
return moment(date).format('HH:mm');
64+
};
65+
66+
const save = (schedule: Schedule) => {
67+
const { length } = Object.keys(schedule);
68+
if (length === 0) {
69+
throw new Error('No schedule days found!');
70+
}
71+
console.log(`Importing ${length} day(s)...`);
72+
73+
const batch = firestore.batch();
74+
75+
Object.keys(schedule).forEach(async (date) => {
76+
batch.set(firestore.collection('schedule').doc(date), schedule[date]);
77+
});
78+
79+
return batch.commit();
80+
};
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
import data from '../../sessionize.json';
2+
import { firestore } from '../firebase-config';
3+
import { Answer, SessionizeSession } from './types';
4+
import { categoryItem, nameToId, notConfirmedSpeaker, questionAnswer } from './utils';
5+
6+
export const importSessions = async () => {
7+
const sessions: SessionizeSession[] = convertSessions();
8+
const { length } = await save(sessions);
9+
console.log(`Imported data for ${length} sessions`);
10+
};
11+
12+
const cleanTags = (text: string): string[] => {
13+
return text
14+
.split(',')
15+
.map((dirtyTag) => dirtyTag.trim())
16+
.filter(Boolean);
17+
};
18+
19+
const cleanComplexity = (text: string): string => {
20+
return text === 'Introductory and overview' ? 'Beginner' : text;
21+
};
22+
23+
const convertSpeakerIds = (ids: string[]): string[] => {
24+
return ids.map((sessionizeId) => {
25+
const speaker = data.speakers.find(({ id }) => id === sessionizeId);
26+
if (speaker) {
27+
return nameToId(speaker.fullName);
28+
} else {
29+
throw new Error(`Unable to find speaker with id ${sessionizeId}`);
30+
}
31+
});
32+
};
33+
34+
const matchIcon = ({
35+
title,
36+
isServiceSession,
37+
}: {
38+
title: string;
39+
isServiceSession: boolean;
40+
}): string => {
41+
if (isServiceSession) {
42+
switch (true) {
43+
case title.toLowerCase().includes('break'):
44+
return 'coffee-break';
45+
case title.toLowerCase().includes('closing'):
46+
case title.toLowerCase().includes('welcome'):
47+
return 'opening';
48+
case title.toLowerCase().includes('lunch'):
49+
return 'lunch';
50+
case title.toLowerCase().includes('party'):
51+
return 'party';
52+
default:
53+
return '';
54+
}
55+
}
56+
return '';
57+
};
58+
59+
const getTags = (answers: Answer[], items: number[]): string[] => {
60+
const isKeynote = categoryItem('Session format', items).toLowerCase() === 'keynote';
61+
const tags = [questionAnswer('Tags', answers), isKeynote ? 'keynote' : ''].join(',');
62+
return cleanTags(tags);
63+
};
64+
65+
const convertSessions = (): SessionizeSession[] => {
66+
const sessions: SessionizeSession[] = [];
67+
for (const sessionData of data.sessions) {
68+
if (notConfirmedSpeaker(sessionData.speakers)) {
69+
console.log(`Skipping session ${sessionData.title}`);
70+
continue;
71+
}
72+
console.log(`Importing session ${sessionData.title}`);
73+
sessions.push({
74+
sessionizeId: sessionData.id,
75+
complexity: cleanComplexity(categoryItem('Level', sessionData.categoryItems)),
76+
description: sessionData.description || '',
77+
icon: matchIcon(sessionData),
78+
image: '',
79+
language: 'en',
80+
speakers: convertSpeakerIds(sessionData.speakers),
81+
tags: getTags(sessionData.questionAnswers, sessionData.categoryItems),
82+
title: sessionData.title,
83+
});
84+
}
85+
return sessions;
86+
};
87+
88+
const save = async (sessions: SessionizeSession[]) => {
89+
if (sessions.length === 0) {
90+
throw new Error('No sessions found!');
91+
}
92+
console.log(`Importing ${sessions.length} sessions...`);
93+
94+
const collectionRef = firestore.collection('sessions');
95+
const { docs } = await collectionRef.get();
96+
const batch = firestore.batch();
97+
98+
sessions.forEach(async (session) => {
99+
const existingDoc = docs.find((doc) => doc.data().sessionizeId === session.sessionizeId);
100+
if (existingDoc) {
101+
batch.set(existingDoc.ref, session);
102+
} else {
103+
batch.set(collectionRef.doc(), session);
104+
}
105+
});
106+
107+
return batch.commit();
108+
};
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
import data from '../../sessionize.json';
2+
import { Badge } from '../../src/models/badge';
3+
import { Social } from '../../src/models/social';
4+
import { firestore } from '../firebase-config';
5+
import { Link, SessionizeSpeaker } from './types';
6+
import { nameToId, notConfirmedSpeaker, questionAnswer } from './utils';
7+
8+
export const importSpeakers = async () => {
9+
const speakers: SessionizeSpeaker[] = convertSpeakers();
10+
await save(speakers);
11+
};
12+
13+
const selectBadges = (company: string): Badge[] => {
14+
return [
15+
{
16+
description: 'Google',
17+
link: 'https://www.google.com',
18+
name: 'google',
19+
},
20+
].filter(({ name }) => name.toLowerCase() === company.toLowerCase());
21+
};
22+
23+
const selectSocials = (links: Link[]): Social[] => {
24+
const approvedSources = [
25+
'facebook',
26+
'github',
27+
'instagram',
28+
'linkedin',
29+
'twitter',
30+
'website',
31+
'youtube',
32+
];
33+
const approvedLinks = links.filter(({ linkType }) =>
34+
approvedSources.includes(linkType.toLowerCase())
35+
);
36+
37+
return approvedLinks.map((link) => {
38+
return {
39+
icon: link.linkType.toLowerCase(),
40+
link: link.url,
41+
name: link.title,
42+
};
43+
});
44+
};
45+
46+
const convertSpeakers = (): SessionizeSpeaker[] => {
47+
const speakers: SessionizeSpeaker[] = [];
48+
for (const speakerData of data.speakers) {
49+
if (notConfirmedSpeaker([speakerData.id])) {
50+
console.log(`Skipping speaker ${speakerData.fullName}`);
51+
continue;
52+
}
53+
console.log(`Importing speaker ${speakerData.fullName}`);
54+
speakers.push({
55+
sessionizeId: speakerData.id,
56+
badges: selectBadges(questionAnswer('Company', speakerData.questionAnswers)),
57+
bio: speakerData.bio,
58+
company: questionAnswer('Company', speakerData.questionAnswers),
59+
companyLogo: '',
60+
companyLogoUrl: '',
61+
country: questionAnswer('Location', speakerData.questionAnswers),
62+
featured: speakerData.isTopSpeaker,
63+
name: speakerData.fullName,
64+
order: 0,
65+
photo: '',
66+
photoUrl: speakerData.profilePicture,
67+
pronouns: questionAnswer('Pronouns', speakerData.questionAnswers),
68+
shortBio: '',
69+
socials: selectSocials(speakerData.links),
70+
title: speakerData.tagLine,
71+
});
72+
}
73+
return speakers;
74+
};
75+
76+
const save = async (speakers: SessionizeSpeaker[]) => {
77+
if (speakers.length === 0) {
78+
throw new Error('No speakers found!');
79+
}
80+
console.log(`Importing ${speakers.length} speakers...`);
81+
82+
const collectionRef = firestore.collection('speakers');
83+
const { docs } = await collectionRef.get();
84+
const batch = firestore.batch();
85+
86+
speakers.forEach(async (speaker) => {
87+
const existingDoc = docs.find((doc) => doc.data().sessionizeId === speaker.sessionizeId);
88+
if (existingDoc) {
89+
batch.set(existingDoc.ref, speaker);
90+
} else {
91+
const id = nameToId(speaker.name);
92+
batch.set(collectionRef.doc(id), speaker);
93+
}
94+
});
95+
96+
const { length } = await batch.commit();
97+
console.log(`Imported data for ${length} speakers`);
98+
};
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { Session } from '../../src/models/session';
2+
import { Speaker } from '../../src/models/speaker';
3+
4+
export interface Question {
5+
id: number;
6+
question: string;
7+
questionType: string;
8+
sort: number;
9+
}
10+
11+
export interface Answer {
12+
questionId: number;
13+
answerValue: string;
14+
}
15+
16+
export interface Link {
17+
title: string;
18+
url: string;
19+
linkType: string;
20+
}
21+
22+
export type SessionizeSpeaker = Speaker & {
23+
sessionizeId: string;
24+
};
25+
26+
export type SessionizeSession = Session & {
27+
sessionizeId: string;
28+
};

0 commit comments

Comments
 (0)