From 552c07f9980c907e8094cf529eeb76aa2177edd7 Mon Sep 17 00:00:00 2001 From: Mathi05 Date: Thu, 14 May 2020 08:12:55 +0530 Subject: [PATCH 1/3] Calculating angle between Clock handles Exercise - Mathi T --- README.md | 58 +++---------------------------------------- app.yaml | 11 ++++++++ cloudbuild.yaml | 10 ++++++++ main.py | 37 +++++++++++++++++++++++++++ templates/form.html | 37 +++++++++++++++++++++++++++ templates/result.html | 15 +++++++++++ test_angle.py | 20 +++++++++++++++ 7 files changed, 133 insertions(+), 55 deletions(-) create mode 100644 app.yaml create mode 100644 cloudbuild.yaml create mode 100644 main.py create mode 100644 templates/form.html create mode 100644 templates/result.html create mode 100644 test_angle.py diff --git a/README.md b/README.md index 4a77c09..cc496ba 100644 --- a/README.md +++ b/README.md @@ -1,58 +1,6 @@ -# Clock Exercise +Calculating angle between Clock handles Exercise- -We are interested in running code of course, but even more in your development process and understanding of Software Development Lifecycle Management. +Hosted in App Engine for easy intraction UI web based application to get the angle from given inputs. -**Fork this repo, then get to work.** Remember that this is a DevOps team, so make sure your repo reflects that. Spend however much time you feel is reasonable. It doesn’t matter if the project is ‘done’, nothing ever is. **When you’re ready push your changes back to Github and put in a pull request back to the base repo.** +App Engine URL: https://hallowed-key-273608.uc.r.appspot.com/ -This exercise is not meant to take an excessive amount of time. It is an opportunity for you to demonstrate your skills without the stress of an interview. If you start to run out of time, it’s ok to leave an imaginary team member a TODO list that details all the things you didn’t quite have time to do in order for your solution to go to prod. - -If you need clarification, or would like to request additional information, pease reach out to the interviewer by email. - -## Scenario - -You have just joined a DevOps team. This team lives by DevOps principles and you want to let them know you mean business! This particular team is developing a product that is deployed in a Google Cloud Project. - -This sprint, the team has been asked to work on a new feature that depends on being able to calculate the angle between the hands on a clock face. They’ve asked you to write some code to help out with that. This is an IOT project, and they have sensors emitting times at a pretty low frequency (about 10 a minute), and for some reason they need to be processed and stored as angles. - -You may need to make some assupmtions, that's OK, just document what they are and move on. - -The team loves innovation, so you can use whatever languages and technologies you like to complete this. Approach this problem as if your code will go to production. Whilst we don’t expect the code to be perfect, we are not looking for a hacked together script. - -Your solution should offer the rest of the team a way to submit a time and receive an angle in return or store it somewhere. They are little fuzzy on the best way to get this low frequency data to your service, so if you can offer them any hints on that, they’d be really happy. - -## How to proceed - -**Fork this repo, then get to work.** Remember that this is a DevOps team, so make sure your repo reflects that. Spend however much time you feel is reasonable. It doesn’t matter if the project is ‘done’, nothing ever is. **When you’re ready push your changes back to Github and put in a pull request back to the base repo.** - -Be sure to add in instructions for how to deploy your solution, and document things in a way that the rest of the team can pick this up and run with it. Remember you have all the tools in the GCP arsenal at your disposal. - -We are looking for you to demonstrate your abilities in software practices and DevOps, including reusability, portability, reliability, ease of maintenance etc. - -Think about how this will actually be deployed and maintained in the future as you build on it and expand it. You don’t have to implement deployment practices if you don’t have the time or resources, its ok to just document those. - ---- - -## Product Backlog Item (Sprint Story) - -Here is the story that is in the backlog. - -As with all stories, the team may have been optimistic with how much can be done in the time permitted. It's ok to meet some of the acceptance criteria by documenting what you would do in the next sprint! Prioritize your time and make sure you have some technical content to deliver. - -### Description:- - -As a team
-We need a serivce that we can send a time value to and have it return or store an angle value
-So that we can use it in downstream processing - -### Detail:- - -We need to calculate the angle between the hands on a clock face. For example input 03:00 would yield 90 degrees. - -### Acceptance Criteria:- - -1) Code to perform the calculation -1) How will you deploy this solution (in code or as a todo list if time is limited). i.e. how and where will this run? -1) How will you manage any infrastructure needed? -1) Delivered as a feature branch in the repo fork -1) Bonus points for a working deployed solution in GCP that you can demo at the "sprint review" (ie interview) -1) Any DevOps/Cicd components that would support this feature in a production setting diff --git a/app.yaml b/app.yaml new file mode 100644 index 0000000..bab91f2 --- /dev/null +++ b/app.yaml @@ -0,0 +1,11 @@ +runtime: python27 +api_version: 1 +threadsafe: true + +handlers: +- url: /.* + script: main.app + +libraries: +- name: flask + version: 0.12 diff --git a/cloudbuild.yaml b/cloudbuild.yaml new file mode 100644 index 0000000..85d2f1f --- /dev/null +++ b/cloudbuild.yaml @@ -0,0 +1,10 @@ +steps: +- name: "python" + id: Test + entrypoint: /bin/sh + args: + - -c + - 'pip install flask && pip install pytest && python -m pytest' +- name: "gcr.io/cloud-builders/gcloud" + args: ["app", "deploy"] +timeout: "1600s" diff --git a/main.py b/main.py new file mode 100644 index 0000000..d1c990a --- /dev/null +++ b/main.py @@ -0,0 +1,37 @@ +import logging +from flask import Flask, render_template, request +app = Flask(__name__) + +@app.route('/') +def student(): + return render_template('form.html') + +@app.route('/result',methods = ['POST','GET']) +def result(): + if request.method == 'POST': + #get the inputs from the form + h = int(request.form['hour']) + m = int(request.form['minute']) + + if (h < 0 or m < 0 or h > 12 or m > 60): + print('Wrong input') + + if (h == 12): + h = 0 + + if (m == 60): + m = 0 + #calculate the hour and minute + hour_angle = 0.5 * (h * 60 + m) + minute_angle = 6 * m + + #calculate the angle + angle = abs(hour_angle - minute_angle) + + angle = min(360 - angle, angle) + + #print the angle in result page + return render_template("result.html",result = str(angle)) + +if __name__ == '__main__': + app.run(debug = True) \ No newline at end of file diff --git a/templates/form.html b/templates/form.html new file mode 100644 index 0000000..2628b55 --- /dev/null +++ b/templates/form.html @@ -0,0 +1,37 @@ + + + + + + +

Demo - Angle between the hands on a clock face

+
+

Hours

+

Minute

+

+
+ + \ No newline at end of file diff --git a/templates/result.html b/templates/result.html new file mode 100644 index 0000000..5a8637f --- /dev/null +++ b/templates/result.html @@ -0,0 +1,15 @@ + + + + + + + + + + +
The Angle is {{ result }}
+
+ + + \ No newline at end of file diff --git a/test_angle.py b/test_angle.py new file mode 100644 index 0000000..3455fa7 --- /dev/null +++ b/test_angle.py @@ -0,0 +1,20 @@ +import pytest + +from main import app as main_app + + +def test_root_page(): + response = main_app.test_client().get('/') + assert response.status_code == 200 + +def test_result_data_success(): + data = { + 'hour': 3, + 'minute': 0 + } + response = main_app.test_client().post('/result', data=data) + assert '90.0' in str(response.data) + + + + From c090cb7534b87c233ec28ab4e38cb700dd1d1769 Mon Sep 17 00:00:00 2001 From: Mathi05 Date: Fri, 15 May 2020 00:11:29 +0530 Subject: [PATCH 2/3] Storing the angle values in BigQuery --- app.yaml | 11 +---------- cloudbuild.yaml | 3 ++- main.py | 42 +++++++++++++++++++++++++++++++++++++----- requirements.txt | 2 ++ test_angle.py | 2 +- 5 files changed, 43 insertions(+), 17 deletions(-) create mode 100644 requirements.txt diff --git a/app.yaml b/app.yaml index bab91f2..ac4c378 100644 --- a/app.yaml +++ b/app.yaml @@ -1,11 +1,2 @@ -runtime: python27 -api_version: 1 -threadsafe: true +runtime: python37 -handlers: -- url: /.* - script: main.app - -libraries: -- name: flask - version: 0.12 diff --git a/cloudbuild.yaml b/cloudbuild.yaml index 85d2f1f..9604ffb 100644 --- a/cloudbuild.yaml +++ b/cloudbuild.yaml @@ -4,7 +4,8 @@ steps: entrypoint: /bin/sh args: - -c - - 'pip install flask && pip install pytest && python -m pytest' + - 'pip install --upgrade google-api-python-client && pip install google-cloud-bigquery && pip install flask && pip install pytest && python -m pytest' + - name: "gcr.io/cloud-builders/gcloud" args: ["app", "deploy"] timeout: "1600s" diff --git a/main.py b/main.py index d1c990a..6dfece6 100644 --- a/main.py +++ b/main.py @@ -1,12 +1,21 @@ +import os import logging from flask import Flask, render_template, request +from google.cloud import bigquery +from google.cloud.bigquery.client import Client +import datetime + +os.environ['GOOGLE_APPLICATION_CREDENTIALS'] = 'keyfile.json' + +#credentials = app_engine.Credentials() + app = Flask(__name__) @app.route('/') -def student(): - return render_template('form.html') +def home(): + return render_template('form.html') -@app.route('/result',methods = ['POST','GET']) +@app.route('/result',methods = ['POST']) def result(): if request.method == 'POST': #get the inputs from the form @@ -28,10 +37,33 @@ def result(): #calculate the angle angle = abs(hour_angle - minute_angle) - angle = min(360 - angle, angle) + angle = min(360 - angle, angle) + + + client = bigquery.Client() + # Prepares a reference to the dataset + dataset_ref = client.dataset('clocks') + + table_ref = dataset_ref.table('angles_details') + table = client.get_table(table_ref) # API call + + rows_to_insert = [ + {u'hours': h, + u'minutes': m, + u'angle': angle, + u'updated_on': datetime.datetime.now() + } + ] + client.insert_rows(table, rows_to_insert) # API request #print the angle in result page return render_template("result.html",result = str(angle)) if __name__ == '__main__': - app.run(debug = True) \ No newline at end of file + app.run(debug = True) + +@app.errorhandler(500) +def server_error(e): + # Log the error and stacktrace. + logging.exception('An error occurred during a request.') + return 'An internal error occurred.', 500 diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..f0885c9 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +google-api-python-client +google-cloud \ No newline at end of file diff --git a/test_angle.py b/test_angle.py index 3455fa7..b3bc470 100644 --- a/test_angle.py +++ b/test_angle.py @@ -15,6 +15,6 @@ def test_result_data_success(): response = main_app.test_client().post('/result', data=data) assert '90.0' in str(response.data) - + From d240dacf0914ea49ec6a6dceaa5a4ab4c8aff772 Mon Sep 17 00:00:00 2001 From: Mathi05 Date: Fri, 15 May 2020 00:25:24 +0530 Subject: [PATCH 3/3] updating script --- main.py | 31 +++++++++++-------------------- 1 file changed, 11 insertions(+), 20 deletions(-) diff --git a/main.py b/main.py index 6dfece6..0f7c0d1 100644 --- a/main.py +++ b/main.py @@ -1,5 +1,4 @@ import os -import logging from flask import Flask, render_template, request from google.cloud import bigquery from google.cloud.bigquery.client import Client @@ -7,15 +6,13 @@ os.environ['GOOGLE_APPLICATION_CREDENTIALS'] = 'keyfile.json' -#credentials = app_engine.Credentials() - app = Flask(__name__) @app.route('/') -def home(): - return render_template('form.html') +def student(): + return render_template('form.html') -@app.route('/result',methods = ['POST']) +@app.route('/result',methods = ['POST','GET']) def result(): if request.method == 'POST': #get the inputs from the form @@ -24,7 +21,7 @@ def result(): if (h < 0 or m < 0 or h > 12 or m > 60): print('Wrong input') - + if (h == 12): h = 0 @@ -37,16 +34,16 @@ def result(): #calculate the angle angle = abs(hour_angle - minute_angle) - angle = min(360 - angle, angle) + + angle = min(360 - angle, angle) - + # bigquery insert client = bigquery.Client() - # Prepares a reference to the dataset dataset_ref = client.dataset('clocks') - table_ref = dataset_ref.table('angles_details') table = client.get_table(table_ref) # API call + #insert data rows_to_insert = [ {u'hours': h, u'minutes': m, @@ -54,16 +51,10 @@ def result(): u'updated_on': datetime.datetime.now() } ] - client.insert_rows(table, rows_to_insert) # API request - + client.insert_rows(table, rows_to_insert) # API request + #print the angle in result page return render_template("result.html",result = str(angle)) if __name__ == '__main__': - app.run(debug = True) - -@app.errorhandler(500) -def server_error(e): - # Log the error and stacktrace. - logging.exception('An error occurred during a request.') - return 'An internal error occurred.', 500 + app.run(debug = True) \ No newline at end of file