Created by pH14 Solutions for Galvanize and UBC's CPSC 319 2019W1.
pH14 Solutions consists of:
- Andrea Tamez
- Braxton Hall
- Cindy Hsu
- Christopher Powroznik
- Kwangsoo Yeo
- Masahiro Toyomura
The Galvanize Interview Scheduler consists of six pages.
- Login. Clicking the
Login
button will redirect you to Microsoft's login process. On completion, if you are an Administrator on the Enterprise Account, you will be redirected to the Candidates page. - Candidates. This is the main landing page of the portal. It lists every Candidate in the system.
- Click
New Candidate
to bring up a form for entering Candidate data. On submission they will be added as a row on the main table. The only required field for submission isEmail
. - Each row represents on saved Candidate.
Candidate URL
will open a new tab in your browser (theSubmission
page) with the Candidate's availability form or booked schedule.Select
will reopen the Candidate creation form for that Candidate, allowing you to edit their fields.Send Availability
appears on Candidates who do not yet have a booked interview schedule. This will email the Candidate with a link to theSubmission
page.Cancel Meetings
appears on Candidates who already have booked meetings. This will cancel all meeting events on your Outlook Enterprise platform with the Candidate, and send the Candidate an email notifying them of the cancellation.
- Click
- Submission. This page is used for sending availabilities for Candidates, or viewing their interview schedule. It is accessible by a user of the system from the
Candidate
page (so the adminitrator can update the availability directly), or via direct link through an email (so a Candidate can submit their own availability). If a Candidate already has a schedule, this page will instead show their currently booked schedule for reference for either the Candidate or administrator. - Rooms. This page displays all rooms registered with your Outlook Enterprise account. Set a room to
eligible
to have it considered in the scheduling process. - Scheduling. This page is for creating and booking Interviews with Candidates. The table includes a list of every Candidate who has already submitted an availability.
- Click
Select
on a Candidate to bring up the Interviewers pane. This is a list of all Interviewers in the Interviewers group on your Outlook Enterprise account.- Enter a custom Outlook group name and click
Refresh
to bring up a list of Interviewers in that Outlook group.
- Enter a custom Outlook group name and click
- For each Interviewer, select their
Preference
from the drop down menu to group them with another Interviewer in the interviewing process. - For each Interviewer, select the number of minutes needed for their Interview under
Time Needed
. If this value is set to0
, they will not be considered in the scheduling process. - Click
Generate Schedules
to see up to three generated schedules on the Schedules pane.- This is a list of possible schedules, each one taking into account the availability of the Candidate, the working hours of each interviewer, and the availability of the room.
- Click
Select
on your desired schedule to bring up the Actions pane.- This view contains all the details of the Schedule for review.
- Click
Schedule & Send Emails
to book the meetings with the Interviewers on Outlook, add the event to the room's calendar, add and save the meetings to the Candidate for viewing on theSubmission
page, and send an email to the Candidate alerting them of the completion of scheduling.
- Click
- About. That page is likely where you're reading this! Click a document to read more about the making of this project, or an author to view their GitHub profile. You may view the prior documents for this project as well as look over this exact document.
On any page, click Logout
when you're finished working.
Endpoints
All authorized routes require the request header token
, which should be set to the authenticated token provided by the login process.
- Candidates
GET /resource/candidate
- The request body should be JSON object that contains the field
id
paired with the id of the candidate to be fetched. - If successful, response status will be set to
200
.- If the request was authenticated, an
ICandidate
will be returned. - If the request was not authenticated but there was a Candidate matching that ID in the system, an
ICandidate
will be returned, but all fields will be trimmed except forfirstName
,availability
andschedule
.
- If the request was authenticated, an
- Else, response status will be set to
404
.
- The request body should be JSON object that contains the field
GET /resource/candidates
- If successful, response status will be set to
200
, and a JSON array of everyICandidate
in the system will be returned. - If the user is not authenticated, response status will be set to
401
. - If unsuccessful for any other reason, response status will be set to
400
.
- If successful, response status will be set to
POST /resource/candidate
- Saves an
ICandidate
to the database. The request body data should be set to theICandidate
to be saved. Theemail
field is required and must be a valid email string. If noid
field is present, one will be generated. Theid
will be a minimum six character hash of an atomic counter in the database concatenated with a 32 character random string. - If successful, the response status will be set to
200
, and the response will contain theICandidate
that was saved to the database. - If the user is not authenticated, response status will be set to
401
. - If unsuccessful for any other reason, response status will be set to
400
.
- Saves an
DELETE /resource/candidate
- The request body should be JSON object that contains the field
id
paired with the id of the candidate to be deleted from the database. - If successful, the responds with
200 true
. - If the user is not authenticated, response status will be set to
401
. - If unsuccessful for any other reason, response status will be set to
400
.
- The request body should be JSON object that contains the field
- Rooms
GET /resource/rooms
- If successful, response status will be set to
200
, and a JSON array ofIRoom
objects representing every room registered with Galvanize's Enterprise account will be returned. The fieldeligible
will be set totrue
if the primary key of the room was saved in the database, andfalse
otherwise. Theeligible
field marks whether or not a room will be considered in the scheduling process. - If the user is not authenticated, response status will be set to
401
. - If unsuccessful for any other reason, response status will be set to
400
.
- If successful, response status will be set to
POST /resource/room
- Saves the primary key of an
IRoom
to the database. The request body data should be set to theIRoom
whose key is to be saved. This decides the eligibility of the room in the scheduling process. Theid
field is required. - If successful, the response status will be set to
200
, and the response will contain theICandidate
that was saved to the database. - If the user is not authenticated, response status will be set to
401
. - If unsuccessful for any other reason, response status will be set to
400
.
- Saves the primary key of an
DELETE /resource/room
- The request body should be JSON object that contains the field
id
paired with the id of the room whose key is to be deleted from the database. This decides the eligibility of the room in the scheduling process. - If successful, the responds with
200 true
. - If the user is not authenticated, response status will be set to
401
. - If unsuccessful for any other reason, response status will be set to
400
.
- The request body should be JSON object that contains the field
- Interviewers
GET /resource/interviewers
- If successful, response status will be set to
200
, and a JSON array ofIInterviewer
objects representing every employee registered with Galvanize's Enterprise account in the group denoted bygroupName
will be returned.groupName
is to be set in the request query, specifies which group in Outlook to retrieve members from.groupName
is not set, the system will default to its environment'sINTERVIEWER_GROUP_NAME
, described further below and in.env.sample
. - If the user is not authenticated, response status will be set to
401
. - If unsuccessful for any other reason, response status will be set to
400
.
- If successful, response status will be set to
- Schedules
GET /resource/schedules
- Calculates viable schedules and returns them. The query should be set to a valid
IGetSchedulesOptions
object. There may be cycles and chains in thepreferences
array, as the system will attempt to group everyone in the cycle/chain together into one interview. However, having mismatched minutes within a cycle/chain may lead to undefined behaviour. - If successful, response status will be set to
200
, and a JSON array of up to threeISchedule
objects will be returned.- The calendars, timezones, and individual working hours of every room and interviewer are compared with the availability of the supplied
ICandidate
. - Every room marked
eligible
is then scored by their averave overlap with interviewers' schedules, their largest timeslot length, their capacity, and their average timeslot length. - Rooms are then selected in the order of their scores at the privous step and are filled up with iterviews back to back if possible, and a break is inserted every four hours of consecutive interviews.
- Ten schedules are generated with slight variation in inputs and parameters. Then the schedule with the fewest room switches, the schedule with the most interviewers actually places, and the schedule that does the best across both categories are all returned (if any viable schedules were found at all).
- The calendars, timezones, and individual working hours of every room and interviewer are compared with the availability of the supplied
- If the user is not authenticated, response status will be set to
401
. - If unsuccessful for any other reason, response status will be set to
400
.
- Calculates viable schedules and returns them. The query should be set to a valid
POST /resource/schedule
- Saves the provided
ISchedule
timeslots to theICandidate
, emails the candidate, alerting them that the schedule has been made, and books an events for every meeting with each interviewer and room on the Outlook Enterprise account. This will not work on Candidates that already have a schedule. - The request body should be set to the
ISchedule
to save. - If successful, the response status will be set to
200
. - If the user is not authenticated, response status will be set to
401
. - If unsuccessful for any other reason, response status will be set to
400
.
- Saves the provided
DELETE /resource/schedule
- Cancels the specified candidate's schedule. An email is sent to the candidate, alerting them of the cancellation, and every event with the candidate on the Outlook Enterprise account is cancelled.
- The request body should be JSON object that contains the field
id
paired with the id of the candidate whose schedule is to be cancelled. - If successful, the response status will be set to
200
. - If the user is not authenticated, response status will be set to
401
. - If unsuccessful for any other reason, response status will be set to
400
.
- Availability
POST /sendavailability
- Sends an availibity form invitation to the selected candidate in the form of an email from the administrator.
- The request body data should be set to the
ICandidate
representing the candidate to be emailed. - If successful, the response status will be set to
200
. - If the user is not authenticated, response status will be set to
401
. - If unsuccessful for any other reason, response status will be set to
400
.
POST /submitavailability
- The request body should be JSON object that contains the field
id
paired with theid
of the candidate that will be updated, as well as the fieldavailability
that contains a JSON array ofITimeslots
. If these timeslots contain fields other thatnstart
andend
, these excess fields will be clipped and not saved. Allstart
times must be beforeend
times. - If successful, the response status will be set to
200
. - If the
id
of the candidate is not found, response status will be set to404
. - If unsuccessful for any other reason, response status will be set to
400
.
- The request body should be JSON object that contains the field
- Authorization
GET /login
- Initiates login process with Microsoft via a redirect. At the end of the login process, the token provided by Microsoft is saved in the database.
POST /authenticate
- Checks whether or not the provided authorization token is active in the database. The token expires after 30 minutes.
GET /logout
- Deletes the provided authorization token from the database.
- Misc
GET /health
- Responds with status set to
200
.
- Responds with status set to
POST /saveauth
- This is an endpoint created specifically for testing and is disabled unless the environment variable
PRODUCTION
is exactly set tofalse
. - The token sent in the
token
header is not a Microsoft token, but instead a long string matching theTEST_SECRET_KEY
environment variable. - This endpoint saves an authorization with Microsoft into the database without the login process. This allows for automated testing without manually logging in.
- The body of the request should be a JSON object with a field
token
paired with the Microsoft authorization token that is to be saved in the database. - If successful, the response status will be set to
200
. - If the provided token does not match the
TEST_SECRET_KEY
variable that is being used in the backend, response status will be set to401
. - If unsuccessful for any other reason, response status will be set to
400
.
- This is an endpoint created specifically for testing and is disabled unless the environment variable
POST /setconfig
- This is an endpoint created specifically for testing and is disabled unless the environment variable
PRODUCTION
is exactly set tofalse
. - The token sent in the
token
header is not a Microsoft token, but instead a long string matching theTEST_SECRET_KEY
environment variable. - This endpoint can override any config field (usually environment variables) being used in the backend. This is useful to the test suite, as not all the Microsoft Graph endpoints are useable by the token generated by the test suite, so this endpoint allows the test suite to replace some of them with other endpoints.
- The body of the request should be a JSON object with a field
key
paired with the key of the config instance to be overwritten, as well as the fieldvalue
with the value to be set at that key. - If successful, the response status will be set to
200
. - If the provided token does not match the
TEST_SECRET_KEY
variable that is being used in the backend, response status will be set to401
. - If unsuccessful for any other reason, response status will be set to
400
.
- This is an endpoint created specifically for testing and is disabled unless the environment variable
Types
interface ICandidate {
email: string;
id?: string;
phoneNumber?: string;
firstName?: string;
lastName?: string;
position?: string;
notes?: string;
availability?: ITimeslot[];
schedule?: ITimeslot[];
}
interface IRoom {
id: string;
name: string,
eligible: boolean;
email: string;
capacity: number;
}
interface IInterviewer {
id: string;
email: string;
firstName: string;
lastName: string;
}
interface ISchedule {
candidate: ICandidate;
meetings: IMeeting[];
}
interface IGetSchedulesOptions {
preferences: IPreference[];
candidate: ICandidate;
}
interface IPreference {
interviewer: IInterviewer;
preference?: IInterviewer;
minutes: number;
}
interface ITimeslot {
start: string; // ISO formatted time
end: string; // ISO formatted time
note?: string;
// note may include a room number for the ICandidate schedule
}
interface IMeeting extends ITimeslot {
interviewers: IInterviewer[];
room: IRoom;
}
The Interview Scheduler queries for data from your Microsoft Office Enterprise account regularly to ensure that it does not store anything that's stale. In order for this to work, some small steps need to be taken to ensure your Enterprise account is ready.
-
Ensure that the rooms in your office are registered as Resources on your Enterprise platform. An administrator can register them here. As well, for each room, under Resource Scheduling,
- Turn on "Automatically process even invitations and cancellations."
- Remove "Limit event duration."
- Open "Scheduling permissions."
More information can be found here if logged in as the resource and here.
-
Ensure you have added a your employees who are candidates for conducting an interview together in an Office group. The recommended group name is
Interviewers
. Any group can be retrieved as "Interviewers," however the system will default toInterviewers
. To change this default, refer to the Environment section below. -
Give all administrators of the system the requisite permissions in your Azure Portal.
- Navigate to Home > Users > User > Assigned Roles
- Set the users' permissions. In order for them to effectively leverage the system, they must have permission to manage enterprise apps, view other users' schedules, and edit other users' schedules.
- As well, you must grant admin consent for each user.
- Navigate to Home > Azure Active Directory > App Registrations > Applicaton > API Permissions
- Grant Admin Consent for Application, and have the user login.
-
Register an app on Azure. Afterward, edit the application's authentication permissions to allow for Microsoft redirects to and from the application.
- Navigate to Home > Azure Active Directory > App Registrations > Applicaton > Authentication
- Register the following addressed
<UI_URL>
<UI_URL>/candidates
<SERVER_URL>
<SERVER_URL>/callback
-
Under your Enterprise Azure portal's OAuth scope page, open the needed Microsoft Graph API permissions.
- Navigate to Home > Azure Active Directory > App Registrations > Applicaton > View API permissions
- Turn on the following permissions
- Calendars.ReadWrite.Shared Delegated
- Directory.Read.All Application
- Group.Read.All Application
- Group.Selected Application
- Mail.Send Application
- Place.Read.All Application
- User.Read.All Application
The application relies on several environment variables for further configuration. These environment variables are further detailed under the Development header, and described individually in the .env.sample
, included in the project directory.
Some evironment variables of note are described here.
INTERVIEWER_GROUP_NAME
- This controls the default name of the group whose members will be retrieved from Outlook when attempting to retrieve all interviewers.
- If this variable is not set, the system will default it to
Interviewer
.
OAUTH_SCOPES
- This gives the backend permissions to interface with Microsoft Graph. They should match the OAuth scopes described above in Outlook Setup.
PRODUCTION
- If this evironment variable is set to
false
, two endpoints in the system backend will be opened that allow for editing the environment of the backend remotely if a secret key is provided. This is of course only to be used by local testing. - If this variable is not set, the system will default it to
true
.
- If this evironment variable is set to
TEST_SECRET_KEY
- This variable allows you to set a password that for using the two test endpoints in the system backend that allow for editing the environment of the backend remotely. These endpoints are only opened by the
PRODUCTION
variable described above. - This variable has no default value.
- This variable allows you to set a password that for using the two test endpoints in the system backend that allow for editing the environment of the backend remotely. These endpoints are only opened by the
OAUTH_APP_ID
- This is the identifying string for the application that Azure uses to open its endpoints to the Interview Scheduler.
- In Azure, navigate to Home > Active Directory > App Registrations > Application > Application (client) ID
OAUTH_APP_PASSWORD
- This is paired with the
OAUTH_APP_ID
to indentify the Interview Scheduler. - In Azure, navigate to Home > Active Directory > App Registrations > Application > Certificates & secrets > New client secret
- This is paired with the
To deploy, create Docker images for the frontend and the backend, and host them on your cloud services platform of choice.
From the project root directory,
docker image build -f Dockerfile_frontend -t glvnzschedui .
docker image build -f Dockerfile_backend -t glvnzschedserver .
To save these images to files to be used on other machines,
docker save glvnzschedui:latest | gzip > glvnzschedui.tar.gz
docker save glvnzschedserver:latest | gzip > glvnzschedserver.tar.gz
Our website is hosted on a single ECS cluster which comprises of two EC2 instances, representing frontend and backend.
You can upload docker images to ECR (Amazon's repositories) through docker tags, so that they can be referenced in tasks:
### Configure your AWS access key/secret if this is the first run
### You must install the aws-cli, available on homebrew
aws configure
### Authenticate with ECR (expires after a few hours)
$(aws ecr get-login --no-include-email --region us-east-1)
### docker tag <image> <tag>, and push to AWS
docker tag galvanize-scheduling_backend:latest 373316253232.dkr.ecr.us-east-1.amazonaws.com/galvanizebackend
docker push 373316253232.dkr.ecr.us-east-1.amazonaws.com/galvanizebackend
docker tag galvanize-scheduling_frontend:latest 373316253232.dkr.ecr.us-east-1.amazonaws.com/galvanizefrontend
docker push 373316253232.dkr.ecr.us-east-1.amazonaws.com/galvanizefrontend
You can use the existing docker-compose file to build the images, but the frontend build args
must be updated to wherever you plan to host before building, so that the redirects go to the correct location instead of localhost
:
args:
- "SERVER_ADDRESS=https://server.ph14interviews.com"
- "PUBLIC_ADDRESS=https://book.ph14interviews.com"
- "DEFAULT_GROUP=${INTERVIEWER_GROUP_NAME}"
We have one task definition for each service, which pulls the latest image from the ECR repository, applies the correct environment variables/entrypoint commands/mounted volumes/port forwarding, and then runs the image. Network mode can be host
or bridge
, depending if you want to remap the server ports to another value. Our services are configured to run on different EC2 instances so that the Microsoft callbacks can be routed more easily. EC2 instances must be running an ECS compatible AMI (OS image), have the "AmazonEC2ContainerServiceforEC2Role" policy, and belong to the same server region to be detected as part of the cluster. Running tasks can be done through the aws-cli
after they are defined, when the docker images are updated, rebuilt, and pushed to ECR. Alternatively, you can start
and stop
tasks through the web console.
For example:
### Find the ARNs (resource numbers) of the currently running tasks
~/w/galvanize-scheduling ❯❯❯ aws ecs list-tasks --cluster galvanize-cluster ✘ 255 develop ✭ ✱
{
"taskArns": [
"arn:aws:ecs:us-east-1:373316253232:task/galvanize-cluster/509bd537429f48e3a8982a3e8556dd91",
"arn:aws:ecs:us-east-1:373316253232:task/galvanize-cluster/ae11a9ef1ff44a209ebd996d9f03538b"
]
}
### Stop tasks
aws ecs stop-task --cluster galvanize-cluster --task arn:aws:ecs:us-east-1:3733
16253232:task/galvanize-cluster/509bd537429f48e3a8982a3e8556dd91
{ "task": { ..., "taskDefinitionArn": "arn:aws:ecs:us-east-1:373316253232:task-definition/backend-task:9" } }
### Re-run the task, which will pull the latest image
~/w/galvanize-scheduling ❯❯❯ aws ecs run-task --cluster galvanize-cluster --task-definition backend-task:9
{ "tasks": [...] }
Microsoft requires all callback URLs to be secure, so we added a domain name to the site and used Amazon Certificate Manager to generate SSL certificates. Then we used a load balancer to redirect calls for the server subdomain to port 8080 of the EC2 instance running the backend service. Attributes
were used to make sure tasks ran on the correct EC2 instance, so that the load balancer would hit the right service, and the port openings (managed by security groups on the instance) were correct.
The load balancers have a static DNS name in the description, which you can add to the DNS records of whatever registar your domain name belongs to. This will make (HTTP or HTTPS) calls to the domain name route to the load balancer, and the load balancer will route to the correct ports of the EC2 instances, which your servers should be listening on. The instances will also not need elastic IPs, because the load balancer can map to it through resource numbers.
More information on hosting Docker images on AWS can be found here.
The development process requires installation of several dependencies. This includes:
Node v10.16.3
(andnpm
). Both the frontend and backend systems run in Node, and npm is used to manage dependencies.Docker
. Used to automatically deploy dummy containers during the testing process.amazon/dynamodb-local
. This image is used to mock the database.- To install this, run
docker pull amazon/dynamodb-local
. This needs to done only once. docker-compose
(optional). This is used for orchestrating multiple containers at once. This can allow for building and deploying test instances of the project's micro services all in one command.
Environment variables are used to manage the configuration of the project's micro services.
- Create a
.env
file and put it in the root directory of the project. (touch .env
)- This file should never be committed to version control.
- Copy
.env.sample
to the new.env
file. (cat .env.sample > .env
) - Modify as necessary to your environment.
.env.sample
includes further documentation for each environment variable.
You can remotely connect to your local backend service through chrome://inspect
or by creating a Attach to node.js
run configuration in Webstorm/IntelliJ.
npm run stop-backend
to stop any existing containers using the ports- (optional)
npm run build-backend
to update the docker image npm run debug-backend
to start an instance of the backend and db that does not persist data
In Webstorm, create an attachment run configuration with localhost
on port 9229
. This is the default Node inspection port. Add breakpoints and hit debug, the program should now pause when the breakpoint is hit by any external calls.
Simply run sh install_dependencies.sh
from the root directory of the project. This will update the dependencies of the adapter
, frontend
, and backend
packages.
Unit Tests
To run unit tests of the backend, from the root directory run
npm run test:backend
Integration Tests
- If you already have an instance of the backend running:
npm run test:adapter
- If you would like to do startup + teardown:
npm run test:integration
. You may have to stop existing containers first (npm run stop-backend
).
Note for devs: test:integration
does not re-build the image.
Customizing the Test Suites
There are some flags in integration.spec.ts
that turn certain tests on and off. Some rely the specific integration with the ph14solutions active directory.
Using a different directory to run tests against would require updating those tests, or you can disable them by setting the flags to false.