Commit c9161a32 authored by Zac Wood's avatar Zac Wood

Merge branch 'dev-v2' into 'master'

Dev v2

See merge request !36
parents 8970cc67 160d7746
Pipeline #3490 passed with stage
in 2 minutes and 22 seconds
......@@ -2,38 +2,17 @@ stages:
- test
- build
test_api:
image: ruby:2.5
test:
image: starefossen/ruby-node:latest
stage: test
script:
- cd schedules_api
- cd schedules
- bundle install
- rails db:migrate
- rails test
- rubocop
test_web:
image: node:9
stage: test
script:
- cd schedules_web
- yarn
- yarn build
build_web:
stage: build
retry: 2
image:
name: gcr.io/kaniko-project/executor:debug
entrypoint: [""]
script:
- mkdir -p /root/.docker
- echo "{\"auths\":{\"$CI_REGISTRY\":{\"username\":\"$CI_REGISTRY_USER\",\"password\":\"$CI_REGISTRY_PASSWORD\"}}}" > /root/.docker/config.json
- /kaniko/executor --context $CI_PROJECT_DIR/schedules_web --dockerfile $CI_PROJECT_DIR/schedules_web/Dockerfile --destination $CI_REGISTRY_IMAGE/web:$CI_COMMIT_TAG
only:
- tags
build_api:
build:
stage: build
retry: 2
image:
......@@ -42,6 +21,6 @@ build_api:
script:
- mkdir -p /root/.docker
- echo "{\"auths\":{\"$CI_REGISTRY\":{\"username\":\"$CI_REGISTRY_USER\",\"password\":\"$CI_REGISTRY_PASSWORD\"}}}" > /root/.docker/config.json
- /kaniko/executor --context $CI_PROJECT_DIR/schedules_api --dockerfile $CI_PROJECT_DIR/schedules_api/Dockerfile --destination $CI_REGISTRY_IMAGE/api:$CI_COMMIT_TAG
- /kaniko/executor --context $CI_PROJECT_DIR/schedules --dockerfile $CI_PROJECT_DIR/schedules/Dockerfile --destination $CI_REGISTRY_IMAGE/api:$CI_COMMIT_TAG
only:
- tags
# Schedules
Schedules is a web app that allows students to import their class schedules into popular calendar managers. It consists of an API written in Ruby on Rails and a web client built with React.
Schedules is a web app that is written with Ruby on Rails and allows students to import their class schedules into popular calendar managers.
The project manager for Schedules is Zac Wood.
......@@ -29,9 +29,9 @@ the SRCT code respository, with SSH.
Run `cd schedules/` to enter the cloned directory
## Setting up API
## Setting up Project
Execute `cd schedules_api/` to enter the API directory.
Execute `cd schedules/` to enter the Project directory.
### Install dependencies
To install the project dependencies, run the `bundle install` command.
......@@ -41,32 +41,20 @@ To populate your local database, run `rake db:migrate` and `rake db:seed`. This
**NOTE:** Sometimes Patriot Web doesn't appriciate being parsed. If you're having problems,
please let us know in [Slack](https://srct.slack.com/)!
## Setting up client
### Install dependencies
To install the React client's dependencies, run the `yarn` command from the `/schedules_web` directory.
## Development servers
While developing for schedules, it is useful to have development servers for both the React client and the Ruby on Rails API running.
While developing for schedules, it is useful to have development servers running.
### API
To start the API, run the `rails server` command in the `/schedules_api` directory. The API should now be accessible from `localhost:3000`
To start the Project, run the `rails server` command in the `/schedules` directory. The website should now be accessible from `localhost:3000`
### Client
To start the development server for the React client, run the `yarn start` command from the `/schedules_web` directory. The client should now be available from `localhost:8080`.
## Testing
Before you make a commit, you should ensure you new code passes the project's tests.
It is recommended that you write tests for any new code you add, but this is not required.
### API
To run the API's tests, run the command `rails test` from the `schedules_api` directory.
### Client
To run the client's test, run the `yarn test` command from the `schedules_web` directory.
To run the Project's tests, run the command `rails test` from the `schedules` directory.
## Opening issues
......@@ -80,4 +68,4 @@ A great tool for making sure your code meets the project's style is [RuboCop](ht
gem install rubocop
Then, when inside the `/schedules_api/` directory, you can run the command `rubocop` to see where your style does not match the project's.
Then, when inside the `/schedules/` directory, you can run the command `rubocop` to see where your style does not match the project's.
......@@ -13,3 +13,6 @@ Style/SymbolArray:
Metrics/BlockLength:
Enabled: false
Style/ClassAndModuleChildren:
Enabled: false
FROM ruby:2.5
FROM starefossen/ruby-node:latest
RUN mkdir /api
WORKDIR /api
......@@ -10,5 +10,6 @@ ENV RAILS_ENV production
RUN bundle install
RUN export SECRET_KEY_BASE=$(rails secret)
RUN rake assets:precompile
RUN rails db:migrate
RUN rails db:seed
......@@ -6,17 +6,19 @@ git_source(:github) do |repo_name|
end
# Bundle edge Rails instead: gem 'rails', github: 'rails/rails'
gem 'rails', '~> 5.1.6'
gem 'rails', '5.1.6.1'
# Use sqlite3 as the database for Active Record
gem 'sqlite3'
# gem 'pg'
# Use Puma as the app server
gem 'puma', '~> 3.7'
# Use SCSS for stylesheets
gem 'sass-rails', '~> 5.0'
# Turbolinks makes navigating your web application faster. Read more: https://github.com/turbolinks/turbolinks
gem 'turbolinks', '~> 5'
# Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder
gem 'jbuilder', '~> 2.5'
gem 'uglifier'
# Use Redis adapter to run Action Cable in production
# gem 'redis', '~> 4.0'
# Use ActiveModel has_secure_password
......@@ -70,3 +72,7 @@ gem 'apipie-rails'
# Markdown for API docs
gem 'maruku'
# gem 'jquery-rails'
# gem 'font-awesome-sass', '~> 5.3.1'
GEM
remote: https://rubygems.org/
specs:
actioncable (5.1.6)
actionpack (= 5.1.6)
actioncable (5.1.6.1)
actionpack (= 5.1.6.1)
nio4r (~> 2.0)
websocket-driver (~> 0.6.1)
actionmailer (5.1.6)
actionpack (= 5.1.6)
actionview (= 5.1.6)
activejob (= 5.1.6)
actionmailer (5.1.6.1)
actionpack (= 5.1.6.1)
actionview (= 5.1.6.1)
activejob (= 5.1.6.1)
mail (~> 2.5, >= 2.5.4)
rails-dom-testing (~> 2.0)
actionpack (5.1.6)
actionview (= 5.1.6)
activesupport (= 5.1.6)
actionpack (5.1.6.1)
actionview (= 5.1.6.1)
activesupport (= 5.1.6.1)
rack (~> 2.0)
rack-test (>= 0.6.3)
rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.0, >= 1.0.2)
actionview (5.1.6)
activesupport (= 5.1.6)
actionview (5.1.6.1)
activesupport (= 5.1.6.1)
builder (~> 3.1)
erubi (~> 1.4)
rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.0, >= 1.0.3)
activejob (5.1.6)
activesupport (= 5.1.6)
activejob (5.1.6.1)
activesupport (= 5.1.6.1)
globalid (>= 0.3.6)
activemodel (5.1.6)
activesupport (= 5.1.6)
activerecord (5.1.6)
activemodel (= 5.1.6)
activesupport (= 5.1.6)
activemodel (5.1.6.1)
activesupport (= 5.1.6.1)
activerecord (5.1.6.1)
activemodel (= 5.1.6.1)
activesupport (= 5.1.6.1)
arel (~> 8.0)
activesupport (5.1.6)
activesupport (5.1.6.1)
concurrent-ruby (~> 1.0, >= 1.0.2)
i18n (>= 0.7, < 2)
minitest (~> 5.1)
tzinfo (~> 1.1)
addressable (2.5.2)
public_suffix (>= 2.0.2, < 4.0)
apipie-rails (0.5.10)
apipie-rails (0.5.14)
rails (>= 4.1)
arel (8.0.0)
ast (2.4.0)
......@@ -57,81 +57,89 @@ GEM
childprocess (0.9.0)
ffi (~> 1.0, >= 1.0.11)
coderay (1.1.2)
concurrent-ruby (1.0.5)
concurrent-ruby (1.1.3)
crass (1.0.4)
erubi (1.7.1)
execjs (2.7.0)
ffi (1.9.25)
globalid (0.4.1)
activesupport (>= 4.2.0)
httparty (0.16.2)
httparty (0.16.3)
mime-types (~> 3.0)
multi_xml (>= 0.5.2)
i18n (1.0.1)
i18n (1.2.0)
concurrent-ruby (~> 1.0)
icalendar (2.4.1)
icalendar (2.5.2)
ice_cube (~> 0.16)
ice_cube (0.16.3)
jaro_winkler (1.5.1)
jbuilder (2.7.0)
jbuilder (2.8.0)
activesupport (>= 4.2.0)
multi_json (>= 1.2)
listen (3.1.5)
rb-fsevent (~> 0.9, >= 0.9.4)
rb-inotify (~> 0.9, >= 0.9.7)
ruby_dep (~> 1.2)
loofah (2.2.2)
loofah (2.2.3)
crass (~> 1.0.2)
nokogiri (>= 1.5.9)
mail (2.7.0)
mail (2.7.1)
mini_mime (>= 0.1.1)
maruku (0.7.3)
method_source (0.9.0)
mini_mime (1.0.0)
method_source (0.9.2)
mime-types (3.2.2)
mime-types-data (~> 3.2015)
mime-types-data (3.2018.0812)
mini_mime (1.0.1)
mini_portile2 (2.3.0)
minitest (5.11.3)
multi_json (1.13.1)
multi_xml (0.6.0)
nio4r (2.3.1)
nokogiri (1.8.3)
nokogiri (1.8.5)
mini_portile2 (~> 2.3.0)
parallel (1.12.1)
parser (2.5.1.2)
parser (2.5.3.0)
ast (~> 2.4.0)
pg (1.1.3)
powerpack (0.1.2)
pry (0.11.3)
pry (0.12.2)
coderay (~> 1.1.0)
method_source (~> 0.9.0)
pry-doc (0.13.4)
pry-doc (0.13.5)
pry (~> 0.11)
yard (~> 0.9.11)
public_suffix (3.0.2)
puma (3.11.4)
rack (2.0.5)
public_suffix (3.0.3)
puma (3.12.0)
rack (2.0.6)
rack-cors (1.0.2)
rack-test (1.0.0)
rack-test (1.1.0)
rack (>= 1.0, < 3)
rails (5.1.6)
actioncable (= 5.1.6)
actionmailer (= 5.1.6)
actionpack (= 5.1.6)
actionview (= 5.1.6)
activejob (= 5.1.6)
activemodel (= 5.1.6)
activerecord (= 5.1.6)
activesupport (= 5.1.6)
rails (5.1.6.1)
actioncable (= 5.1.6.1)
actionmailer (= 5.1.6.1)
actionpack (= 5.1.6.1)
actionview (= 5.1.6.1)
activejob (= 5.1.6.1)
activemodel (= 5.1.6.1)
activerecord (= 5.1.6.1)
activesupport (= 5.1.6.1)
bundler (>= 1.3.0)
railties (= 5.1.6)
railties (= 5.1.6.1)
sprockets-rails (>= 2.0.0)
rails-dom-testing (2.0.3)
activesupport (>= 4.2.0)
nokogiri (>= 1.6)
rails-html-sanitizer (1.0.4)
loofah (~> 2.2, >= 2.2.2)
railties (5.1.6)
actionpack (= 5.1.6)
activesupport (= 5.1.6)
railties (5.1.6.1)
actionpack (= 5.1.6.1)
activesupport (= 5.1.6.1)
method_source
rake (>= 0.8.7)
thor (>= 0.18.1, < 2.0)
rainbow (3.0.0)
rake (12.3.1)
rake (12.3.2)
rb-fsevent (0.10.3)
rb-inotify (0.9.10)
ffi (>= 0.5.0, < 2)
......@@ -144,12 +152,12 @@ GEM
ruby-progressbar (~> 1.7)
unicode-display_width (~> 1.0, >= 1.0.1)
ruby-progressbar (1.10.0)
rubyXL (3.3.29)
rubyXL (3.3.31)
nokogiri (>= 1.4.4)
rubyzip (>= 1.1.6)
ruby_dep (1.5.0)
rubyzip (1.2.1)
sass (3.5.7)
rubyzip (1.2.2)
sass (3.7.2)
sass-listen (~> 4.0.0)
sass-listen (4.0.0)
rb-fsevent (~> 0.9, >= 0.9.4)
......@@ -160,9 +168,9 @@ GEM
sprockets (>= 2.8, < 4.0)
sprockets-rails (>= 2.0, < 4.0)
tilt (>= 1.1, < 3)
selenium-webdriver (3.13.0)
selenium-webdriver (3.141.0)
childprocess (~> 0.5)
rubyzip (~> 1.2)
rubyzip (~> 1.2, >= 1.2.2)
spring (2.0.2)
activesupport (>= 4.2)
spring-watcher-listen (2.0.1)
......@@ -176,16 +184,15 @@ GEM
activesupport (>= 4.0)
sprockets (>= 3.0.0)
sqlite3 (1.3.13)
thor (0.20.0)
thor (0.20.3)
thread_safe (0.3.6)
tilt (2.0.8)
turbolinks (5.1.1)
turbolinks-source (~> 5.1)
turbolinks-source (5.1.0)
tilt (2.0.9)
tzinfo (1.2.5)
thread_safe (~> 0.1)
uglifier (4.1.20)
execjs (>= 0.3.0, < 3)
unicode-display_width (1.4.0)
web-console (3.6.2)
web-console (3.7.0)
actionview (>= 5.0)
activemodel (>= 5.0)
bindex (>= 0.4.0)
......@@ -193,9 +200,9 @@ GEM
websocket-driver (0.6.5)
websocket-extensions (>= 0.1.0)
websocket-extensions (0.1.3)
xpath (3.1.0)
xpath (3.2.0)
nokogiri (~> 1.8)
yard (0.9.14)
yard (0.9.16)
PLATFORMS
ruby
......@@ -210,11 +217,12 @@ DEPENDENCIES
listen (>= 3.0.5, < 3.2)
maruku
nokogiri
pg
pry
pry-doc
puma (~> 3.7)
rack-cors
rails (~> 5.1.6)
rails (= 5.1.6.1)
rubocop (~> 0.58.2)
rubyXL
sass-rails (~> 5.0)
......@@ -222,8 +230,8 @@ DEPENDENCIES
spring
spring-watcher-listen (~> 2.0.0)
sqlite3
turbolinks (~> 5)
tzinfo-data
uglifier
web-console (>= 3.3.0)
BUNDLED WITH
......
//= require FileSaver
//= require cart
const elementFromString = string => {
const html = new DOMParser().parseFromString(string, 'text/html');
return html.body.firstChild;
};
document.addEventListener('DOMContentLoaded', () => {
this.cart = new Cart();
initGlobalListeners();
});
const setSemester = async select => {
const url = new URL(window.location.href);
url.searchParams.set('semester_id', select.value);
window.open(url.toString(), '_self');
};
const initGlobalListeners = () => {
const semesterSelect = document.getElementById('semester-select');
semesterSelect.onchange = () => setSemester(semesterSelect);
};
class Cart {
constructor() {
this.isOpen = false;
this._courses = [];
const cartData = document.getElementById('cart-data');
if (cartData) {
this._courses = JSON.parse(cartData.dataset.cart);
}
}
toggle() {
const list = document.getElementById('cart');
const icon = document.getElementById('schedule-icon');
if (this.isOpen) {
list.style.display = 'none';
icon.style.color = 'black';
} else {
list.style.display = 'block';
icon.style.color = 'green';
}
this.isOpen = !this.isOpen;
}
set courses(courses) {
this._courses = courses;
for (const courseId in this._courses) {
if (this._courses[courseId].length === 0) delete this._courses[courseId];
}
document.getElementById('course-counter').innerText = Object.keys(this._courses).length;
}
async toggleSection(section) {
const resp = await fetch(`/sessions/cart?&section_id=${section.id}`, { cache: 'no-store' });
const json = await resp.json();
this.courses = json;
}
includesSection(obj) {
for (const key in this._courses) {
const list = this._courses[key];
if (list.includes(obj.id)) return true;
}
return false;
}
}
/**
* Either adds or removes a section from the cart depending on
* if it is currently in the cart.
*/
const addOrRemoveFromCart = async (event, sectionNode) => {
event && event.stopPropagation();
const section = { ...sectionNode.dataset };
await this.cart.addSection(section);
if (this.cart.includesSection(section)) {
sectionNode.classList.add('selected');
} else {
sectionNode.classList.remove('selected');
}
};
const initListeners = () => {
const items = Array.from(document.querySelectorAll('.section-item'));
items.forEach(item => (item.onclick = e => addOrRemoveFromCart(e, item)));
};
document.addEventListener('DOMContentLoaded', initListeners);
// Place all the behaviors and hooks related to the matching controller here.
// All this logic will automatically be available in application.js.
{
"compilerOptions": {
"lib": ["es2015", "dom"]
}
}
document.addEventListener('DOMContentLoaded', () => {
const eventsTemplate = document.querySelector('#events');
if (eventsTemplate) {
const eventsJSON = eventsTemplate.dataset.events;
const events = JSON.parse(eventsJSON);
window.events = events;
$('#calendar').fullCalendar({
defaultDate: new Date(2019, 0, 14),
defaultView: 'agendaWeek',
header: false,
events: renderEvents,
columnHeaderFormat: 'dddd',
allDaySlot: false,
});
}
initListeners();
});
const renderEvents = (start, end, timezone, callback) => {
callback(window.events);
};
const remove = async item => {
await window.cart.toggleSection({ ...item.dataset });
location.reload(true);
};
/**
* Generates a URL for the current sections in the schedule
* and sets the link in the modal to it.
*/
const setUrlInModal = () => {
document.getElementById('calendar-link').innerText = `${window.location.protocol}//${window.location.hostname}/api/schedules?section_ids=${window.cart._courses.join(',')}`;
};
const downloadIcs = async () => {
const response = await fetch(`${window.location.protocol}//${window.location.hostname}/api/schedules?section_ids=${window.cart._courses.join(',')}`);
const text = await response.text();
const blob = new Blob([text], { type: 'text/calendar;charset=utf-8' });
saveAs(blob, 'GMU Schedule.ics');
};
const addToSystemCalendar = () => {
window.open(`webcal://${window.location.hostname}/api/schedules?section_ids=${window.cart._courses.join(',')}`);
};
const initListeners = () => {
const items = Array.from(document.querySelectorAll('.section-item'));
items.forEach(item => (item.onclick = () => remove(item)));
document.getElementById('open-modal-btn').onclick = setUrlInModal;
document.getElementById('download-ics').onclick = downloadIcs;
document.getElementById('add-to-system').onclick = addToSystemCalendar;
document.getElementById('share-url').innerText = `${window.location.protocol}//${window.location.hostname}/schedule/view?section_ids=${window.cart._courses.join(',')}`;
};
document.addEventListener('DOMContentLoaded', () => {
const eventsTemplate = document.querySelector('#events');
if (eventsTemplate) {
const eventsJSON = eventsTemplate.dataset.events;
const events = JSON.parse(eventsJSON);
window.events = events;
$('#calendar').fullCalendar({
defaultDate: new Date(2019, 0, 14),
defaultView: 'agendaWeek',
header: false,
events: renderEvents,
columnHeaderFormat: 'dddd',
allDaySlot: false,
});
}