From f6f0815fd5e6d8505f91e23d3f6102350bc4125c Mon Sep 17 00:00:00 2001 From: Walukagga Patrick Date: Mon, 11 Jun 2018 15:37:07 +0300 Subject: [PATCH 1/5] #158212180 Allow Cross Origin Requests (#38) * [Feature158212180] Enable cors * [Feature 158212180] Add flask_cors requirement * [Feature 158212180] Refactor return statement to checkroom * [Feature 158212180] Refactor to return room as an object * [Feature 158212180] Refactor to return room as an object --- api/room/schema.py | 7 +++---- app.py | 4 ++++ fixtures/room/room_fixtures.py | 12 ++++++------ requirements.txt | 1 + 4 files changed, 14 insertions(+), 10 deletions(-) diff --git a/api/room/schema.py b/api/room/schema.py index 950f2d25d..c61afbcd0 100644 --- a/api/room/schema.py +++ b/api/room/schema.py @@ -50,8 +50,8 @@ def mutate(self, info, room_id, **kwargs): class Query(graphene.ObjectType): all_rooms = graphene.List(Room) - get_room_by_id = graphene.List( - lambda: Room, + get_room_by_id = graphene.Field( + Room, room_id=graphene.Int() ) @@ -64,8 +64,7 @@ def resolve_get_room_by_id(self, info, room_id): check_room = query.filter(RoomModel.id == room_id).first() if not check_room: raise GraphQLError("Room not found") - result = query.filter(RoomModel.id == room_id) - return result + return check_room class Mutation(graphene.ObjectType): diff --git a/app.py b/app.py index 3a6575b88..18f702dfd 100644 --- a/app.py +++ b/app.py @@ -1,4 +1,5 @@ from flask import Flask +from flask_cors import CORS from flask_graphql import GraphQLView @@ -9,8 +10,11 @@ def create_app(config_name): app = Flask(__name__) + CORS(app) + app.config.from_object(config[config_name]) config[config_name].init_app(app) + app.add_url_rule( '/mrm', view_func=GraphQLView.as_view( diff --git a/fixtures/room/room_fixtures.py b/fixtures/room/room_fixtures.py index cc0ccef5a..86a744363 100644 --- a/fixtures/room/room_fixtures.py +++ b/fixtures/room/room_fixtures.py @@ -64,15 +64,15 @@ room_query_by_id_response = { "data": { - "getRoomById": [{ - "capacity": 6, - "name": "Entebbe", - "roomType": "meeting" - } - ] + "getRoomById": { + "capacity": 6, + "name": "Entebbe", + "roomType": "meeting" + } } } + room_query_by_nonexistant_id = '''{ getRoomById(roomId: 100) { id diff --git a/requirements.txt b/requirements.txt index 6f1c4caf4..e891796ea 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,7 @@ alembic==0.9.8 coverage==4.5.1 Flask==0.12.2 +Flask-Cors==3.0.4 Flask-Script==2.0.6 Flask-GraphQL==1.4.1 graphene-sqlalchemy==2.0.0 From e01244f13bb1dead21ecd0b3ec3ba2322f6b16aa Mon Sep 17 00:00:00 2001 From: Walukagga Patrick Date: Mon, 11 Jun 2018 15:41:15 +0300 Subject: [PATCH 2/5] [Bug #158107020] Refactor files to return an object instead of list (#35) From 47b1f8e1ac6297ada31706acdeec996fe4fe30bf Mon Sep 17 00:00:00 2001 From: Fidelis Ojeah Date: Tue, 12 Jun 2018 10:30:27 +0100 Subject: [PATCH 3/5] Update config.yml (#36) [chore #158211220]: upgrade infrastructure from zonal to regional - Modified terraform config to make instance groups across regions (rather than single region) - Created extra health checks - Increases availability --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index ac6546a9a..42cc173f9 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -81,7 +81,7 @@ jobs: gcloud --quiet config set compute/zone ${GOOGLE_COMPUTE_ZONE} - run: name: Deploy to gcloud - command: gcloud beta compute instance-groups managed rolling-action replace mrm-backend-instance-group --max-unavailable=1 --min-ready=280 --zone=europe-west1-b + command: gcloud beta compute instance-groups managed rolling-action replace mrm-backend-instance-group --region europe-west1 workflows: version: 2 From 9c03d6d1a31a161d50b5bb74c487bb8e12c568b5 Mon Sep 17 00:00:00 2001 From: Namuli Joyce Date: Tue, 12 Jun 2018 15:25:49 +0300 Subject: [PATCH 4/5] #156874253 decode token to return UserCredentials (#39) * [ft156874253]Add decode token to return UserCredentials * [ft156874253] Fix Flake8 error * Change pakage name to authentication.py --- helpers/auth/__init__.py | 0 helpers/auth/authentication.py | 37 ++++++++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+) create mode 100644 helpers/auth/__init__.py create mode 100644 helpers/auth/authentication.py diff --git a/helpers/auth/__init__.py b/helpers/auth/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/helpers/auth/authentication.py b/helpers/auth/authentication.py new file mode 100644 index 000000000..4fb4aa4e2 --- /dev/null +++ b/helpers/auth/authentication.py @@ -0,0 +1,37 @@ +import jwt +from flask import request, jsonify + + +class Authentication: + """ Authenicate token + :methods + decode_token + get_token + """ + + def get_token(self): + token = request.headers.get('token') # get token from headers + return token + + def decode_token(self): + """ + Decodes the auth token + :param + :return + integer|string + """ + + try: + auth_token = self.get_token() + payload = jwt.decode(auth_token, verify=False) + return payload['UserInfo'] # Return User Info + except jwt.ExpiredSignatureError: + return jsonify({ + 'message': 'Signature expired. Please log in again.'}), 401 + except jwt.InvalidTokenError: + return jsonify({ + 'message': 'Invalid token. Please Provide a valid token!' + }), 401 + + +Auth = Authentication() From d5cc963844c7cbe3d63815869275bcb7029c4e8f Mon Sep 17 00:00:00 2001 From: owenbob <30471763+owenbob@users.noreply.github.com> Date: Wed, 13 Jun 2018 18:13:21 +0300 Subject: [PATCH 5/5] #157385654 View room schedule (#30) * Add untracked files after merge * [Fix] Pull Updated changes to develop * [Feature #157385654] Add getRoomSchedule query * [Feature #157385654]Add view room schedule functionality This commit enables a user to view room schedule from the google calendar api. One is able to specify the calendar_id and the number of days they need and will then receive information containing the event start time and summary of the event * [Feature #157385654]Add tests Add tests for the RoomSchedule query that contains google calendar intergration * [Feature #157385654]Add tests Add tests for the RoomSchedule query that contains google calendar intergration * [Fix #157385654]Remove _pycache_ files * [Feature #157385654] Add getRoomSchedule query * [Feature #157385654]Add view room schedule functionality This commit enables a user to view room schedule from the google calendar api. One is able to specify the calendar_id and the number of days they need and will then receive information containing the event start time and summary of the event * [Feature #157385654]Add tests Add tests for the RoomSchedule query that contains google calendar intergration * [Feature #157385654]Add tests Add tests for the RoomSchedule query that contains google calendar intergration * [Feature #157385654]Add latest changes from develop * [fix #157385654]Fix flake8 issues * [Fix #157385654] Fix failing test. * [Feature #157385654]Add tests for events * [Feature #157385654] Add getRoomSchedule query * [Feature #157385654]Add view room schedule functionality This commit enables a user to view room schedule from the google calendar api. One is able to specify the calendar_id and the number of days they need and will then receive information containing the event start time and summary of the event * [Feature #157385654]Add tests Add tests for the RoomSchedule query that contains google calendar intergration * [Feature #157385654]Add tests Add tests for the RoomSchedule query that contains google calendar intergration * [Feature #157385654] Add getRoomSchedule query * [Feature #157385654]Add view room schedule functionality This commit enables a user to view room schedule from the google calendar api. One is able to specify the calendar_id and the number of days they need and will then receive information containing the event start time and summary of the event * [Feature #157385654]Add tests Add tests for the RoomSchedule query that contains google calendar intergration * [Feature #157385654]Add tests Add tests for the RoomSchedule query that contains google calendar intergration * [Feature #157385654]Add latest changes from develop * [Fix #157385654] Fix failing test. * [Feature #157385654]Add tests for events * [Fix #157385654]Run migrations with calendar_id field * [Fix #157385654]Pull remote changes * [Fix #157385654]Fix failing test fixtures * [Fix #157385654]Fix flake8 errors * [Fix #157385654]Fix migration issues * [Fix #157385654] Add descriptive error This commit enables raising of a more descriptive error when a calandarId given on the query is not assigned to any calendar on ocnverge * [Fix]Add google api credentials to .env example * [Feature #157385654] Add getRoomSchedule query * [Feature #157385654]Add view room schedule functionality This commit enables a user to view room schedule from the google calendar api. One is able to specify the calendar_id and the number of days they need and will then receive information containing the event start time and summary of the event * [Feature #157385654]Add tests Add tests for the RoomSchedule query that contains google calendar intergration * [Feature #157385654]Add tests Add tests for the RoomSchedule query that contains google calendar intergration * [Feature #157385654] Add getRoomSchedule query * [Feature #157385654]Add view room schedule functionality This commit enables a user to view room schedule from the google calendar api. One is able to specify the calendar_id and the number of days they need and will then receive information containing the event start time and summary of the event * [Feature #157385654]Add tests Add tests for the RoomSchedule query that contains google calendar intergration * [Feature #157385654]Add tests Add tests for the RoomSchedule query that contains google calendar intergration * [Feature #157385654]Add latest changes from develop * [Fix #157385654] Fix failing test. * [Feature #157385654]Add tests for events * [Feature #157385654] Add getRoomSchedule query * [Feature #157385654]Add view room schedule functionality This commit enables a user to view room schedule from the google calendar api. One is able to specify the calendar_id and the number of days they need and will then receive information containing the event start time and summary of the event * [Feature #157385654]Add tests Add tests for the RoomSchedule query that contains google calendar intergration * [Feature #157385654] Add getRoomSchedule query * [Feature #157385654]Add view room schedule functionality This commit enables a user to view room schedule from the google calendar api. One is able to specify the calendar_id and the number of days they need and will then receive information containing the event start time and summary of the event * [Feature #157385654]Add latest changes from develop * [Fix #157385654] Fix failing test. * [Feature #157385654]Add tests for events * [Fix #157385654]Run migrations with calendar_id field * [Fix #157385654]Pull remote changes * [Fix #157385654]Fix failing test fixtures * [Fix #157385654]Fix flake8 errors * [Fix #157385654]Fix migration issues * [Fix #157385654] Add descriptive error This commit enables raising of a more descriptive error when a calandarId given on the query is not assigned to any calendar on ocnverge * [Fix]Add google api credentials to .env example * [fix] Add new changes from develop * [fix] Add new changes from develop * [Fix]Fix failing test --- .env.example | 4 ++ alembic/versions/06c4fd428167_.py | 40 +++++++++++ ...9418e_add_calndarid_field_to_room_table.py | 40 +++++++++++ .../versions/21ca82d232ff_add_calendarid.py | 40 +++++++++++ alembic/versions/631fe5fea815_.py | 24 +++++++ alembic/versions/a3a250e2805a_.py | 40 +++++++++++ api/room/models.py | 2 + api/room/schema.py | 31 +++++++++ credentials.json | 1 + fixtures/location/all_locations_fixtures.py | 24 +++---- fixtures/room/room_fixtures.py | 69 +++++++++++++++---- helpers/calendar/__init__.py | 0 helpers/calendar/credentials.py | 36 ++++++++++ helpers/calendar/events.py | 46 +++++++++++++ requirements.txt | 1 + tests/base.py | 1 + tests/test_calendars/__init__.py | 0 tests/test_calendars/test_credentials.py | 23 +++++++ tests/test_calendars/test_events.py | 25 +++++++ tests/test_rooms/test_query_rooms.py | 11 +-- tests/test_rooms/test_room_model.py | 1 + tests/test_rooms/test_room_schedule.py | 39 +++++++++++ 22 files changed, 467 insertions(+), 31 deletions(-) create mode 100644 alembic/versions/06c4fd428167_.py create mode 100644 alembic/versions/16706f39418e_add_calndarid_field_to_room_table.py create mode 100644 alembic/versions/21ca82d232ff_add_calendarid.py create mode 100644 alembic/versions/631fe5fea815_.py create mode 100644 alembic/versions/a3a250e2805a_.py create mode 100644 credentials.json create mode 100644 helpers/calendar/__init__.py create mode 100644 helpers/calendar/credentials.py create mode 100644 helpers/calendar/events.py create mode 100644 tests/test_calendars/__init__.py create mode 100644 tests/test_calendars/test_credentials.py create mode 100644 tests/test_calendars/test_events.py create mode 100644 tests/test_rooms/test_room_schedule.py diff --git a/.env.example b/.env.example index bf6c520a3..7c4c38f6b 100644 --- a/.env.example +++ b/.env.example @@ -4,3 +4,7 @@ export SECRET_KEY="some-very-long-string-of-random-characters" export DEV_DATABASE_URL="" # Db for Development. export TEST_DATABASE_URL="" # Db for Testing export DATABASE_URL="" # Db for Production +export API_KEY="" # Google API app Api key +export OOATH2_CLIENT_ID="" # Google API app ooath2_client_id +export OOATH2_CLIENT_SECRET="" # Google API app ooath2_client_secret + diff --git a/alembic/versions/06c4fd428167_.py b/alembic/versions/06c4fd428167_.py new file mode 100644 index 000000000..ec031d186 --- /dev/null +++ b/alembic/versions/06c4fd428167_.py @@ -0,0 +1,40 @@ +"""empty message + +Revision ID: 06c4fd428167 +Revises: +Create Date: 2018-06-05 12:29:10.404447 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '06c4fd428167' +down_revision = None +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_table('devices') + op.add_column('rooms', sa.Column('calendar_id', sa.String(), nullable=True)) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_column('rooms', 'calendar_id') + op.create_table('devices', + sa.Column('id', sa.INTEGER(), nullable=False), + sa.Column('name', sa.VARCHAR(), autoincrement=False, nullable=False), + sa.Column('device_type', sa.VARCHAR(), autoincrement=False, nullable=False), + sa.Column('date_added', sa.VARCHAR(), autoincrement=False, nullable=True), + sa.Column('last_seen', sa.VARCHAR(), autoincrement=False, nullable=True), + sa.Column('location', sa.VARCHAR(), autoincrement=False, nullable=False), + sa.Column('resource_id', sa.INTEGER(), autoincrement=False, nullable=True), + sa.ForeignKeyConstraint(['resource_id'], ['resources.id'], name='devices_resource_id_fkey'), + sa.PrimaryKeyConstraint('id', name='devices_pkey') + ) + # ### end Alembic commands ### diff --git a/alembic/versions/16706f39418e_add_calndarid_field_to_room_table.py b/alembic/versions/16706f39418e_add_calndarid_field_to_room_table.py new file mode 100644 index 000000000..3d35f2419 --- /dev/null +++ b/alembic/versions/16706f39418e_add_calndarid_field_to_room_table.py @@ -0,0 +1,40 @@ +"""Add calndarId field to room table + +Revision ID: 16706f39418e +Revises: c231314bba8d +Create Date: 2018-06-07 15:51:04.456452 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '16706f39418e' +down_revision = 'c231314bba8d' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_table('devices') + op.add_column('rooms', sa.Column('calendar_id', sa.String(), nullable=True)) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_column('rooms', 'calendar_id') + op.create_table('devices', + sa.Column('id', sa.INTEGER(), nullable=False), + sa.Column('name', sa.VARCHAR(), autoincrement=False, nullable=False), + sa.Column('device_type', sa.VARCHAR(), autoincrement=False, nullable=False), + sa.Column('date_added', sa.VARCHAR(), autoincrement=False, nullable=False), + sa.Column('last_seen', sa.VARCHAR(), autoincrement=False, nullable=False), + sa.Column('location', sa.VARCHAR(), autoincrement=False, nullable=False), + sa.Column('resource_id', sa.INTEGER(), autoincrement=False, nullable=True), + sa.ForeignKeyConstraint(['resource_id'], ['resources.id'], name='devices_resource_id_fkey'), + sa.PrimaryKeyConstraint('id', name='devices_pkey') + ) + # ### end Alembic commands ### diff --git a/alembic/versions/21ca82d232ff_add_calendarid.py b/alembic/versions/21ca82d232ff_add_calendarid.py new file mode 100644 index 000000000..d7fc0cc50 --- /dev/null +++ b/alembic/versions/21ca82d232ff_add_calendarid.py @@ -0,0 +1,40 @@ +"""Add calendarId + +Revision ID: 21ca82d232ff +Revises: b3be355ffe21 +Create Date: 2018-06-13 13:08:35.409236 + +""" +from alembic import op +import sqlalchemy as sa +from sqlalchemy.dialects import postgresql + +# revision identifiers, used by Alembic. +revision = '21ca82d232ff' +down_revision = 'b3be355ffe21' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_table('devices') + op.add_column('rooms', sa.Column('calendar_id', sa.String(), nullable=False)) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_column('rooms', 'calendar_id') + op.create_table('devices', + sa.Column('id', sa.INTEGER(), nullable=False), + sa.Column('name', sa.VARCHAR(), autoincrement=False, nullable=False), + sa.Column('device_type', sa.VARCHAR(), autoincrement=False, nullable=False), + sa.Column('date_added', postgresql.TIMESTAMP(), autoincrement=False, nullable=False), + sa.Column('last_seen', postgresql.TIMESTAMP(), autoincrement=False, nullable=False), + sa.Column('location', sa.VARCHAR(), autoincrement=False, nullable=False), + sa.Column('resource_id', sa.INTEGER(), autoincrement=False, nullable=True), + sa.ForeignKeyConstraint(['resource_id'], ['resources.id'], name='devices_resource_id_fkey'), + sa.PrimaryKeyConstraint('id', name='devices_pkey') + ) + # ### end Alembic commands ### diff --git a/alembic/versions/631fe5fea815_.py b/alembic/versions/631fe5fea815_.py new file mode 100644 index 000000000..4644012fe --- /dev/null +++ b/alembic/versions/631fe5fea815_.py @@ -0,0 +1,24 @@ +"""empty message + +Revision ID: 631fe5fea815 +Revises: b3be355ffe21, 06c4fd428167 +Create Date: 2018-06-07 14:07:21.597872 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '631fe5fea815' +down_revision = ('b3be355ffe21', '06c4fd428167') +branch_labels = None +depends_on = None + + +def upgrade(): + pass + + +def downgrade(): + pass diff --git a/alembic/versions/a3a250e2805a_.py b/alembic/versions/a3a250e2805a_.py new file mode 100644 index 000000000..db147602a --- /dev/null +++ b/alembic/versions/a3a250e2805a_.py @@ -0,0 +1,40 @@ +"""empty message + +Revision ID: a3a250e2805a +Revises: 16706f39418e +Create Date: 2018-06-08 15:32:37.991700 + +""" +from alembic import op +import sqlalchemy as sa +from sqlalchemy.dialects import postgresql + +# revision identifiers, used by Alembic. +revision = 'a3a250e2805a' +down_revision = '16706f39418e' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_table('devices') + op.add_column('rooms', sa.Column('calendar_id', sa.String(), nullable=True)) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_column('rooms', 'calendar_id') + op.create_table('devices', + sa.Column('id', sa.INTEGER(), nullable=False), + sa.Column('name', sa.VARCHAR(), autoincrement=False, nullable=False), + sa.Column('device_type', sa.VARCHAR(), autoincrement=False, nullable=False), + sa.Column('date_added', postgresql.TIMESTAMP(), autoincrement=False, nullable=False), + sa.Column('last_seen', postgresql.TIMESTAMP(), autoincrement=False, nullable=False), + sa.Column('location', sa.VARCHAR(), autoincrement=False, nullable=False), + sa.Column('resource_id', sa.INTEGER(), autoincrement=False, nullable=True), + sa.ForeignKeyConstraint(['resource_id'], ['resources.id'], name='devices_resource_id_fkey'), + sa.PrimaryKeyConstraint('id', name='devices_pkey') + ) + # ### end Alembic commands ### diff --git a/api/room/models.py b/api/room/models.py index f65ed5363..a88d86289 100644 --- a/api/room/models.py +++ b/api/room/models.py @@ -13,6 +13,7 @@ class Room(Base, Utility): room_type = Column(String, nullable=False) capacity = Column(Integer, nullable=False) image_url = Column(String) + calendar_id = Column(String) floor_id = Column(Integer, ForeignKey('floors.id')) resources = relationship('Resource') @@ -24,4 +25,5 @@ def __init__(self, **kwargs): self.room_type = kwargs['room_type'] self.capacity = kwargs['capacity'] self.image_url = kwargs['image_url'] + self.calendar_id = kwargs['calendar_id'] self.floor_id = kwargs['floor_id'] diff --git a/api/room/schema.py b/api/room/schema.py index c61afbcd0..cf0b98079 100644 --- a/api/room/schema.py +++ b/api/room/schema.py @@ -5,6 +5,7 @@ from api.room.models import Room as RoomModel from utilities.utility import validate_empty_fields, update_entity_fields +from helpers.calendar.events import RoomSchedules class Room(SQLAlchemyObjectType): @@ -12,6 +13,10 @@ class Meta: model = RoomModel +class Calendar(graphene.ObjectType): + events = graphene.String() + + class CreateRoom(graphene.Mutation): class Arguments: name = graphene.String(required=True) @@ -19,6 +24,7 @@ class Arguments: capacity = graphene.Int(required=True) image_url = graphene.String() floor_id = graphene.Int(required=True) + calendar_id = graphene.String(required=True) room = graphene.Field(Room) def mutate(self, info, **kwargs): @@ -53,6 +59,16 @@ class Query(graphene.ObjectType): get_room_by_id = graphene.Field( Room, room_id=graphene.Int() + ) + room_schedule = graphene.Field( + Calendar, + calendar_id=graphene.String(), + days=graphene.Int(), + ) + room_schedule = graphene.Field( + Calendar, + calendar_id=graphene.String(), + days=graphene.Int(), ) def resolve_all_rooms(self, info): @@ -66,6 +82,21 @@ def resolve_get_room_by_id(self, info, room_id): raise GraphQLError("Room not found") return check_room + def resolve_room_schedule(self, info, calendar_id, days): + query = Room.get_query(info) + check_calendar_id = query.filter( + RoomModel.calendar_id == calendar_id + ).first() + if not check_calendar_id: + raise GraphQLError("CalendarId given not assigned to any room on converge") # noqa: E501 + room_schedule = RoomSchedules.get_room_schedules( + self, + calendar_id, + days) + return Calendar( + events=room_schedule + ) + class Mutation(graphene.ObjectType): create_room = CreateRoom.Field() diff --git a/credentials.json b/credentials.json new file mode 100644 index 000000000..53e4ad8e1 --- /dev/null +++ b/credentials.json @@ -0,0 +1 @@ +{"access_token": "ya29.Gl3ZBUb8ewx_F9NdEtMIDxH1XPsKOJA64dJwSqd7I6SuDbynB-UHCDw58VtcbjgYZPFrEKrk_99kvJolLRGGuXAUXBRLzAZ85ZaRUkPen-ehtjsycrPbMefC6wV7rMo", "client_id": "753057157806-4n8rnuuvn9iagp7md6s6448omdqi0iif.apps.googleusercontent.com", "client_secret": "DV154Q4dUxR-gaGtiBmonItI", "refresh_token": "1/scqhTOtBcRLGoC5a5brOZElp6a__tUbdbe17p8R7o9g", "token_expiry": "2018-06-13T12:59:51Z", "token_uri": "https://www.googleapis.com/oauth2/v4/token", "user_agent": null, "revoke_uri": "https://accounts.google.com/o/oauth2/revoke", "id_token": null, "id_token_jwt": null, "token_response": {"access_token": "ya29.Gl3ZBUb8ewx_F9NdEtMIDxH1XPsKOJA64dJwSqd7I6SuDbynB-UHCDw58VtcbjgYZPFrEKrk_99kvJolLRGGuXAUXBRLzAZ85ZaRUkPen-ehtjsycrPbMefC6wV7rMo", "token_type": "Bearer", "expires_in": 3600}, "scopes": ["https://www.googleapis.com/auth/calendar.readonly"], "token_info_uri": "https://www.googleapis.com/oauth2/v3/tokeninfo", "invalid": false, "_class": "OAuth2Credentials", "_module": "oauth2client.client"} diff --git a/fixtures/location/all_locations_fixtures.py b/fixtures/location/all_locations_fixtures.py index fd74ab472..ff709b9e2 100644 --- a/fixtures/location/all_locations_fixtures.py +++ b/fixtures/location/all_locations_fixtures.py @@ -63,18 +63,18 @@ }''' expected_response_pass_an_arg = { - "errors": [ - { - "message": "Unknown argument \"locationId\" on field \"allLocations\" of type \"Query\".", # noqa: E501 - "locations": [ - { - "line": 3, - "column": 22 - } - ] - } - ] - } + "errors": [ + { + "message": "Unknown argument \"locationId\" on field \"allLocations\" of type \"Query\".", # noqa: E501 + "locations": [ + { + "line": 3, + "column": 22 + } + ] + } + ] + } all_location_no_hierachy = '''{ allLocations{ diff --git a/fixtures/room/room_fixtures.py b/fixtures/room/room_fixtures.py index 86a744363..1f40c756e 100644 --- a/fixtures/room/room_fixtures.py +++ b/fixtures/room/room_fixtures.py @@ -4,6 +4,7 @@ mutation { createRoom( name: "Mbarara", roomType: "Meeting", capacity: 4, floorId: 1, + calendarId:"andela.com_3836323338323230343935@resource.calendar.google.com", imageUrl: "https://www.officelovin.com/wp-content/uploads/2016/10/andela-office-main-1.jpg") { # noqa: E501 room { name @@ -73,7 +74,7 @@ } -room_query_by_nonexistant_id = '''{ +room_with_non_existant_id = '''{ getRoomById(roomId: 100) { id name @@ -81,16 +82,56 @@ } ''' -room_query_by_nonexistant_id_response = { - "errors": [{ - "message": "Room not found", - "locations": [{ - "line": 2, - "column": 5 - } - ] - }], - "data": { - "getRoomById": null - } -} +room_query_with_non_existant_id_response = { + "errors": [ + { + "message": "Room not found", + "locations": [ + { + "line": 2, + "column": 5 + } + ] + } + ], + "data": { + "getRoomById": null + } + } + +room_schedule_query = ''' + { + roomSchedule( + calendarId:"andela.com_3836323338323230343935@resource.calendar.google.com", + days:7){ + events + } + } + ''' + +room_schedule_query_with_non_existant_calendar_id = ''' + { + roomSchedule( + calendarId:"andela.com_38363230343935@resource.calendar.google.com", + days:7){ + events + } + } + ''' + +room_schedule_of_non_existant_calendar_id_response = { + "errors": [ + { + "message": "CalendarId given not assigned to any room on converge", # noqa: E501 + "locations": [ + { + "line": 1, + "column": 2 + } + ] + } + ], + "data": { + "roomSchedule": null + } + } diff --git a/helpers/calendar/__init__.py b/helpers/calendar/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/helpers/calendar/credentials.py b/helpers/calendar/credentials.py new file mode 100644 index 000000000..35b652e25 --- /dev/null +++ b/helpers/calendar/credentials.py @@ -0,0 +1,36 @@ +import os + +from apiclient.discovery import build +from httplib2 import Http +from oauth2client import file, client, tools # noqa +from oauth2client.client import OAuth2WebServerFlow # noqa + + +class Credentials(): + """Define api credentials + :methods + set_api_credentials + """ + + def set_api_credentials(self): + """ + Setup the Calendar API + """ + SCOPES = 'https://www.googleapis.com/auth/calendar' + store = file.Storage('credentials.json') + credentials = store.get() + + if not credentials or credentials.invalid: + # Create a flow object. This object holds the client_id, + # client_secret, and + # SCOPES. It assists with OAuth 2.0 steps to get user + # authorization and credentials. + flow = OAuth2WebServerFlow( + os.getenv('OOATH2_CLIENT_ID'), + os.getenv('OOATH2_CLIENT_SECRET'), + SCOPES) + credentials = tools.run_flow(flow, store) + api_key = os.getenv('API_KEY') + service = build('calendar', 'v3', developerKey=api_key, + http=credentials.authorize(Http())) + return service diff --git a/helpers/calendar/events.py b/helpers/calendar/events.py new file mode 100644 index 000000000..f114fe2ca --- /dev/null +++ b/helpers/calendar/events.py @@ -0,0 +1,46 @@ +import datetime + +from .credentials import Credentials + + +class RoomSchedules(Credentials): + """Create and get room schedules + :methods + create_room_event_schedules + get_room_event_schedules + """ + + # define schedule methods here + def get_room_schedules(self, calendar_id, days): + """ Get room schedules. This method is responsible + for getting all the events on a rooms calendar. + :params + - calendar_id + - days(Time limit for the schedule you need) + """ + + service = Credentials.set_api_credentials(self) + # 'Z' indicates UTC time + now = datetime.datetime.utcnow().isoformat() + 'Z' + + new_time = ( + datetime.datetime.now() + datetime.timedelta(days=days) + ).isoformat() + 'Z' + + events_result = service.events().list( + calendarId=calendar_id, + timeMin=now, + timeMax=new_time, + singleEvents=True, + orderBy='startTime').execute() + + calendar_events = events_result.get('items', []) + output = [] + if not calendar_events: + return('No upcoming events found.') + for event in calendar_events: + event_details = {} + event_details["start"] = event['start'].get('dateTime', event['start'].get('date')) # noqa: E501 + event_details["summary"] = event.get("summary") + output.append(event_details) + return output diff --git a/requirements.txt b/requirements.txt index e891796ea..83c827248 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,6 +4,7 @@ Flask==0.12.2 Flask-Cors==3.0.4 Flask-Script==2.0.6 Flask-GraphQL==1.4.1 +google-api-python-client==1.6.7 graphene-sqlalchemy==2.0.0 graphene==2.1 MarkupSafe==1.0 diff --git a/tests/base.py b/tests/base.py index 23107bbf4..24b532e61 100644 --- a/tests/base.py +++ b/tests/base.py @@ -40,6 +40,7 @@ def setUp(self): room_type='meeting', capacity=6, floor_id=floor.id, + calendar_id='andela.com_3836323338323230343935@resource.calendar.google.com', # noqa: E501 image_url="https://www.officelovin.com/wp-content/uploads/2016/10/andela-office-main-1.jpg") # noqa: E501 room.save() resource = Resource(name='Markers', diff --git a/tests/test_calendars/__init__.py b/tests/test_calendars/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/test_calendars/test_credentials.py b/tests/test_calendars/test_credentials.py new file mode 100644 index 000000000..c347bd948 --- /dev/null +++ b/tests/test_calendars/test_credentials.py @@ -0,0 +1,23 @@ +"""This module deals with testing Calendar Integration with focus on + googleapi credentials +""" +from helpers.calendar.credentials import Credentials +from tests.base import BaseTestCase + +import sys +import os +sys.path.append(os.getcwd()) + + +class TestCredentials(BaseTestCase): + """ This class tests for the google api credentials + func : + - test_response_credentails + """ + + def test_response_credentails(self): + """ This function tests for type of response + of the response to see if its a googleapi object + """ + response = Credentials.set_api_credentials(self) + self.assertEquals(str(type(response)), "") # noqa: E501 diff --git a/tests/test_calendars/test_events.py b/tests/test_calendars/test_events.py new file mode 100644 index 000000000..0e8265e63 --- /dev/null +++ b/tests/test_calendars/test_events.py @@ -0,0 +1,25 @@ +"""This module deals with testing Calendar Integration with focus on + googleapi credentials +""" +from helpers.calendar.events import RoomSchedules +from tests.base import BaseTestCase + +import sys +import os +sys.path.append(os.getcwd()) + + +class TestEvents(BaseTestCase): + """ This class tests for the google api credentials + func : + - test_response_events + """ + def test_response_event(self): + """ This function tests for type of response + of the response to see if its a googleapi object + RoomSchedule function + """ + calendarId = 'andela.com_3137313531373531393135@resource.calendar.google.com' # noqa: E501 + response = RoomSchedules.get_room_schedules(self, calendarId, 7) + assert type(response) is list + self.assertNotEquals(response, []) diff --git a/tests/test_rooms/test_query_rooms.py b/tests/test_rooms/test_query_rooms.py index 4758d2dbe..ce8682c51 100644 --- a/tests/test_rooms/test_query_rooms.py +++ b/tests/test_rooms/test_query_rooms.py @@ -5,8 +5,9 @@ query_rooms_response, room_query_by_id, room_query_by_id_response, - room_query_by_nonexistant_id, - room_query_by_nonexistant_id_response + room_with_non_existant_id, + room_query_with_non_existant_id_response + ) @@ -19,6 +20,6 @@ def test_query_room_with_id(self): query = self.client.execute(room_query_by_id) self.assertEquals(query, room_query_by_id_response) - def test_query_room_with_nonexistant_id(self): - query = self.client.execute(room_query_by_nonexistant_id) - self.assertEquals(query, room_query_by_nonexistant_id_response) + def test_query_room_with_non_existant_id(self): + query = self.client.execute(room_with_non_existant_id) + self.assertEquals(query, room_query_with_non_existant_id_response) diff --git a/tests/test_rooms/test_room_model.py b/tests/test_rooms/test_room_model.py index 47ee8b220..fc7d06e7f 100644 --- a/tests/test_rooms/test_room_model.py +++ b/tests/test_rooms/test_room_model.py @@ -42,6 +42,7 @@ def test_if_data_can_be_saved(self): object_count = Room.query.count() room = Room(name='Jinja', room_type='meeting', capacity=5, floor_id=1, + calendar_id='andela.com_3835468272423230343935@resource.calendar.google.com', # noqa: E501 image_url="https://www.officelovin.com/wp-content/uploads/2016/10/andela-office-main-1.jpg") # noqa: E501 room.save() diff --git a/tests/test_rooms/test_room_schedule.py b/tests/test_rooms/test_room_schedule.py new file mode 100644 index 000000000..a28c2e94e --- /dev/null +++ b/tests/test_rooms/test_room_schedule.py @@ -0,0 +1,39 @@ +"""This module containes test for the RoomSchedule Query +""" +from tests.base import BaseTestCase + +from fixtures.room.room_fixtures import ( + room_schedule_query, + room_schedule_query_with_non_existant_calendar_id, + room_schedule_of_non_existant_calendar_id_response + ) + + +class QueryRoomSchedule(BaseTestCase): + """This class deals with tests relating to querying room schedules + and the google api integration + funcs : + - test_room_schedule + - test_room_schedule_with_non_existant_calendar_id + """ + def test_room_schedule(self): + """ + This function tests the return types of the data received + from RoomSchedule query + - if it is a dictionary + - if data is obtained + """ + query = self.client.execute(room_schedule_query) + assert type(query) is dict + self.assertNotEquals(query, {}) + + def test_room_schedule_with_non_existant_calendar_id(self): + """This function tests whether an error is raised if the calendarId is + non existant. + """ + query = self.client.execute( + room_schedule_query_with_non_existant_calendar_id) + self.assertNotEquals( + query, + room_schedule_of_non_existant_calendar_id_response + )