Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
e9f6488
removing package lock from root
monicakochofar Sep 28, 2023
d030374
adding jest to app
monicakochofar Sep 28, 2023
e2bf230
eslint ignores jest files
monicakochofar Sep 28, 2023
848be20
adding config files to eslint ignore
monicakochofar Sep 28, 2023
e919b99
adding coverage report
monicakochofar Sep 29, 2023
047e4e1
shallow mounting component
monicakochofar Sep 29, 2023
da4ff78
updating readme and importing vuetify and vue feather to jest tests
monicakochofar Sep 29, 2023
7584954
updating readme
monicakochofar Sep 29, 2023
c7463f9
adding point address field test
monicakochofar Sep 29, 2023
3d7eb32
updating readme
monicakochofar Sep 29, 2023
43c4311
updating readme
monicakochofar Sep 29, 2023
048f49a
updating readme
monicakochofar Sep 29, 2023
1850ea4
updating readme
monicakochofar Sep 29, 2023
5ff2064
updating readme
monicakochofar Sep 29, 2023
ab09dbd
updating import statement
monicakochofar Sep 30, 2023
8d99092
Merge pull request #98 from button-inc/mk-jest-vue
monicakochofar Oct 3, 2023
e89ec08
adding polyline encoder and updating geocoding helper filename
monicakochofar Oct 3, 2023
682b1e4
google optimize routes api now returns a an encoded polyline, adding …
monicakochofar Oct 3, 2023
57103cd
renaming variables and adding route overlay on open street maps
monicakochofar Oct 3, 2023
178e22c
adding leaflet map object reference
monicakochofar Oct 3, 2023
4052db9
updating readme
monicakochofar Oct 3, 2023
d9732e0
cleaned
YaokunLin Oct 4, 2023
508b93f
brightrbin fetching non-blocking
YaokunLin Oct 4, 2023
9684373
added a err msg for tekelek
YaokunLin Oct 4, 2023
4ce2ff2
goog_sheet_date_format
YaokunLin Oct 4, 2023
79757c5
adding comments and removing redundant function
monicakochofar Oct 4, 2023
bf71fd3
Merge pull request #99 from button-inc/mk-update-line
monicakochofar Oct 4, 2023
b30b686
adding greyscale icons
monicakochofar Oct 4, 2023
6289c0e
separated functions more cleanly
monicakochofar Oct 4, 2023
fc1f9a5
renaming variable
monicakochofar Oct 4, 2023
e6fa2e2
using map to trigger vue reactive property
monicakochofar Oct 4, 2023
bd374cf
Merge branch 'develop' of https://github.com/button-inc/iot-system-pr…
YaokunLin Oct 4, 2023
138e45d
Merge pull request #100 from button-inc/186123335-google-sheet-clean-up
YaokunLin Oct 4, 2023
a5aabaa
updating sensor param
monicakochofar Oct 4, 2023
824b361
removing grayscale icons used and updating count
monicakochofar Oct 4, 2023
4e5d3df
fixing bug with creating a route with one sensor
monicakochofar Oct 5, 2023
657331d
Merge pull request #101 from button-inc/mk-filtered
monicakochofar Oct 5, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions api/google_routes_services.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,10 @@ def get_optimized_routes_payload(selectedRouteList, originAddress, destinationAd
]

if to_optimize:
GOOG_FIELD_MASK = 'routes.duration,routes.distanceMeters,routes.optimizedIntermediateWaypointIndex'
GOOG_FIELD_MASK = 'routes.duration,routes.distanceMeters,routes.optimizedIntermediateWaypointIndex,routes.polyline'
optimizeWaypointOrder = True
else:
GOOG_FIELD_MASK = 'routes.duration,routes.distanceMeters'
GOOG_FIELD_MASK = 'routes.duration,routes.distanceMeters,routes.polyline'
optimizeWaypointOrder = False

return {
Expand Down
132 changes: 83 additions & 49 deletions api/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@
# 📚 Authenticate with Google Sheets service account
sa = gspread.service_account(filename="google_sheets_sa_key.json")

goog_sheet_date_format = "%m/%d/%Y %H:%M:%S"

# 🚀 Initialize FastAPI application
app = FastAPI()

Expand Down Expand Up @@ -83,6 +85,13 @@ class BasicSensor(BaseModel):
material_type: str
asset_tag: str
bin_volume: str
fill_level_last_collected: int | None
fill_level_alert: int | None
temperature_alert: int | None
illegal_dumping_alert: bool | None
contamination_alert: bool | None
last_collected: datetime | None



# 📝 Model to represent a BrighterBinsSensorReading
Expand Down Expand Up @@ -215,7 +224,7 @@ def brighterbins_sensor_to_basic_sensor_with_reading(
return BasicSensor(
id=sensor["id"],
sensor_type=SensorType.SOLID_BIN_LEVEL,
fill_level=sensor["readings"][-1]["fillLevel"]
fill_level=sensor["readings"][-1]["fillLevel"] # get the latest reading
if (sensor["readings"] and len(sensor["readings"]) > 0)
else None,
lat=sensor["lat"],
Expand All @@ -232,6 +241,12 @@ def brighterbins_sensor_to_basic_sensor_with_reading(
material_type=sensor["material_type"],
bin_volume=sensor["bin_volume"],
asset_tag=sensor["asset_tag"],
fill_level_last_collected=sensor["fill_level_last_collected"],
fill_level_alert=sensor["fill_level_alert"],
temperature_alert=sensor["temperature_alert"],
illegal_dumping_alert=sensor["illegal_dumping_alert"],
contamination_alert=sensor["contamination_alert"],
last_collected=sensor["last_collected"],
)


Expand Down Expand Up @@ -347,11 +362,10 @@ def set_tkl_cache():
try:
print("Tekelek sensor data cache: fetching sensor data...")
# get api token
# tekelek_api_token = get_tekelek_token() # TODO: put this back on after trade show
tekelek_api_token = get_tekelek_token()

# Define the base URL
base_url = "https://phoenixapiprod.azurewebsites.net/api/"
date_format = "%m/%d/%Y %H:%M:%S"

# Make a request to get all sensor data from tank records
# tanks_url = base_url + "tanks"
Expand All @@ -367,15 +381,18 @@ def set_tkl_cache():
if records:
# Iterate asset records to get additional information from related API endpoints
for index, record in enumerate(records):
id = index # TODO: after trade show, id = record["Sensor ID"]
id = record["Sensor ID"]
print("fetching Tekelek id", id)
# get the latest sensor fill reading
# TODO: put the api call back on after trade show
# latest_reading_url = base_url + "latestReading/" + str(id)
# reading_response = make_http_request(
# latest_reading_url,
# method="GET",
# headers={"Authorization": "Bearer " + tekelek_api_token},
# )
latest_reading_url = base_url + "latestReading/" + str(id)
reading_response = make_http_request(
latest_reading_url,
method="GET",
headers={"Authorization": "Bearer " + tekelek_api_token},
)

if not (reading_response):
print("failed to get response from id ", id)

sensor = {
"id": id,
Expand All @@ -394,12 +411,12 @@ def set_tkl_cache():
"asset_tag": record["Addiontal Asset Tags"],
"group": record["Group"],
"fill_level_last_collected": record["Fill_level_last_collected"],
"fill_level": record["Fill_level"], # TODO: after trade show: "fill_level": reading_response["PercentFull"] if reading_response else None
"fill_level": reading_response["PercentFull"] if reading_response else None,
"fill_level_alert": record["Fill_level_alert"],
"temperature_alert": record["Temperature_alert"],
"illegal_dumping_alert": True if record["Illegal_dumping_alert"].upper() == "YES" else False,
"contamination_alert": True if record["Contamination_alert"].upper() == "YES" else False,
"last_collected": datetime.strptime(record["Last_collected"], date_format)
"illegal_dumping_alert": record["Illegal_dumping_alert"].upper() == "YES",
"contamination_alert": record["Contamination_alert"].upper() == "YES",
"last_collected": datetime.strptime(record["Last_collected"], goog_sheet_date_format)
}
tkl_cache[id] = sensor
print("Tekelek sensor data cache: fetch complete")
Expand All @@ -412,8 +429,8 @@ def set_tkl_cache():
# The purpose is to maintain a historical record of sensor readings over time.
# This allows the program to access and analyze past readings without repeatedly querying the API for the same data
# event handler continues to run periodically, in the background, due to the @repeat_every decorator
#@app.on_event("startup")
#@repeat_every(seconds=60 * 60) # Every 1 hour
@app.on_event("startup")
@repeat_every(seconds=60 * 60) # Every 1 hour
def update_bb_cache() -> None:
global bb_cache
global last_run_timestamp
Expand All @@ -434,39 +451,56 @@ def update_bb_cache() -> None:

for index, record in enumerate(records):
id = record["Sensor ID"]
response = requests.request(
"POST",
url,
headers={"Authorization": "Bearer " + brighterbins_api_token},
data={
"from": readingsStartTime,
"to": readingsEndTime,
print("fetching BrighterBin id", id)
try:
response = requests.request(
"POST",
url,
headers={"Authorization": "Bearer " + brighterbins_api_token},
data={
"from": readingsStartTime,
"to": readingsEndTime,
"id": id,
},
).json()
# Check if the response is successful
if response.get('success'):
readings = (
[]
if len(response["data"]["series"]) == 0
else response["data"]["series"]
)
else:
print(f"Error fetching data for sensor {id}. Response: {response}")
readings = ([{"fillLevel": -1}])

sensor = {
"id": id,
},
).json()
readings = (
[]
if len(response["data"]["series"]) == 0
else response["data"]["series"]
)
sensor = {
"id": id,
"row_id": index + 2,
"bin_name": record["Asset - Name"],
"address": record["Address"],
"city": record["City"],
"province": record["Province"],
"postal_code": record["Postal code"],
"lat": record["Latitude"],
"long": record["Longitude"],
"bin_volume": record["Bin Volume"],
"bin_type": record["Bin Type"],
"material_type": record["Material/Waste Type"],
"asset_tag": record["Addiontal Asset Tags"],
"group": record["Group"],
"readings": readings,
}
bb_cache[id] = sensor
"row_id": index + 2,
"bin_name": record["Asset - Name"],
"address": record["Address"],
"city": record["City"],
"province": record["Province"],
"postal_code": record["Postal code"],
"lat": record["Latitude"],
"long": record["Longitude"],
"bin_volume": record["Bin Volume"],
"bin_type": record["Bin Type"],
"material_type": record["Material/Waste Type"],
"asset_tag": record["Addiontal Asset Tags"],
"group": record["Group"],
"readings": readings,
"fill_level_last_collected": record["Fill_level_last_collected"],
"fill_level_alert": record["Fill_level_alert"],
"temperature_alert": record["Temperature_alert"],
"illegal_dumping_alert": record["Illegal_dumping_alert"].upper() == "YES",
"contamination_alert": record["Contamination_alert"].upper() == "YES",
"last_collected": datetime.strptime(record["Last_collected"], goog_sheet_date_format)
}
bb_cache[id] = sensor
except Exception as e:
print(f"Error fetching data for sensor {id}. Exception: {e}")

print("Initial fetch complete")
else:
print("Fetching latest data...")
Expand Down
1 change: 1 addition & 0 deletions app_vue/.eslintrc.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,5 @@ module.exports = {
globals: {
process: 'readonly',
},
"ignorePatterns": ["**/*.test.js", "**/*.config.js"],
};
39 changes: 36 additions & 3 deletions app_vue/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,22 +52,55 @@ Ensure you have followed the README file located here: api/README.md
### Google Maps Routes API
- this is used to calculate the optimal order of bins to visit
- note that if it returns empty object {} it is likely that your request is passing incorrect lat/long or location data
- we are also using it to return an encoded polyline

### JEST
- We are using Jest and specifically the `@vue/test-utils` version of it (more info [here](https://test-utils.vuejs.org/))
- Following [Given When Then](https://smartbear.com/blog/test-automation-with-gherkin-scenarios/) naming strategy so that tests are human understandable
- Test file naming is in the form of `camelCase.test.js`
- place test files next to relevant component, using same camelCase name

### Vuetify testing
- when testing a component with Vuetify see the following tutorial: [here](https://vuetifyjs.com/en/getting-started/unit-testing/)
- in some cases you may see an error mentioning `stub value undefined` you can check whether switching from `shallowMount` to `mount` resolves this issue

#### commands
Ensure you are in `app_vue` folder from root

```
cd app_vue/
```

to run all tests:

```
npm test
```

to run a single test:

```
npm test -- <relative test.js path here>
```

#### code coverage
After successfully running `npm test`, you can visit `app_vue/src/test/coverage` file path, and open up the `index.html` file there to view code coverage.

## Folder structure

(Last Updated as of Sep 5,2023)
Note that all of these folders are under app_vue/:
- src/assets -> holds images, stylesheets, fonts
- components/shared -> holds components created for global use (ex. generalized button component)
- components -> holds components pertaining to features that would be placed on our pages
- components -> holds components pertaining to features that would be placed on our pages, as well as *.test.js files
- data -> holds json mock data files
- pages -> holds components loaded once on a per-route basis
- router -> holds index.js which contains our configured routes (default is '/')
- stores -> holds Vuex aka Pinia stores for state management
- layout -> holds layout based components (ie. navbar/header/footer)
- utils -> holds shared javascript functions, and services

## Recommended Coding Strategy
## Coding Strategies

### CSS
- we are using the [BEM](https://getbem.com/) approach where possible
Expand All @@ -76,7 +109,7 @@ Note that all of these folders are under app_vue/:
- we have also added some of the vuetify typography in our own mixins located in /assets/stylesheets/mixins
- you can feel free to use `@include <mixinName>` in your custom class to avoid rewriting repetitive code

### stores
### Vue Pinia stores
- we are using the concept of vue [stores](https://vuex.vuejs.org/guide/) in our app
- we are also using a library called [pinia](https://pinia.vuejs.org/) to make the syntax for this a lot simpler, it is recommended by Vue itself!
- think of it as a simple way to manage holding app state. You know its a good tool to use when you find that too many variables are being passed back and forth to components through events (hot potato style). That is where the store can come in and hold that variable globally for you.
Expand Down
19 changes: 19 additions & 0 deletions app_vue/babel.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/*
* export a configuration object that specifies the Babel presets to be used when running tests in the test environment
*/
module.exports = {
env: {
test: {
presets: [
[
"@babel/preset-env",
{
targets: {
node: "current",
},
},
],
],
},
},
}
27 changes: 27 additions & 0 deletions app_vue/jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
const esModules = ['leaflet', '@vue-leaflet/vue-leaflet', 'vuetify', 'vue-feather'].join('|');
module.exports = {
testEnvironment: 'jsdom',
transform: {
'^.+\\.vue$': '@vue/vue3-jest',
'^.+\\js$': 'babel-jest',
'.+\\.(css|styl|less|sass|scss|png|jpg|ttf|woff|woff2)$': 'jest-transform-stub'
},
transformIgnorePatterns: [`/node_modules/(?!${esModules})`],
testRegex: '(/__tests__/.*|(\\.|/)(test|spec))\\.(js|ts)$',
moduleFileExtensions: ['vue', 'js'],
moduleNameMapper: {
'^@/(.*)$': '<rootDir>/src/$1',
'^.+.(css|styl|less|sass|scss|png|jpg|ttf|woff|woff2)$': 'jest-transform-stub'
},
coveragePathIgnorePatterns: ['/node_modules/', '/tests/'],
coverageReporters: ['text', 'json-summary'],
// Fix in order for vue-test-utils to work with Jest 29
// https://test-utils.vuejs.org/migration/#test-runners-upgrade-notes
testEnvironmentOptions: {
customExportConditions: ['node', 'node-addons']
},
verbose: true,
collectCoverage: true,
coverageDirectory: '<rootDir>/src/test/coverage',
coverageReporters: ['html', 'text']
};
6 changes: 6 additions & 0 deletions app_vue/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 11 additions & 1 deletion app_vue/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore",
"format": "prettier --write src/",
"prepare": "cd .. && husky install app_vue/.husky",
"pre-commit": "lint-staged"
"pre-commit": "lint-staged",
"test": "jest"
},
"dependencies": {
"@vue-leaflet/vue-leaflet": "^0.10.1",
Expand All @@ -23,6 +24,7 @@
"ol-contextmenu": "^5.2.1",
"ol-ext": "^4.0.11",
"pinia": "^2.1.6",
"polyline-encoded": "^0.0.9",
"uuid": "^9.0.0",
"vue": "^3.3.4",
"vue-axios": "^3.5.2",
Expand All @@ -33,15 +35,23 @@
"vuetify": "^3.3.14"
},
"devDependencies": {
"@babel/preset-env": "^7.22.20",
"@pinia/testing": "^0.1.3",
"@rushstack/eslint-patch": "^1.3.2",
"@tsconfig/node18": "^18.2.0",
"@types/node": "^18.17.5",
"@vitejs/plugin-vue": "^4.3.1",
"@vue/eslint-config-prettier": "^8.0.0",
"@vue/eslint-config-typescript": "^11.0.3",
"@vue/test-utils": "^2.4.1",
"@vue/tsconfig": "^0.4.0",
"@vue/vue3-jest": "^29.2.6",
"babel-jest": "^29.7.0",
"eslint": "^8.46.0",
"eslint-plugin-vue": "^9.16.1",
"jest": "^29.7.0",
"jest-environment-jsdom": "^29.7.0",
"jest-transform-stub": "^2.0.0",
"lint-staged": "^14.0.1",
"npm-run-all": "^4.1.5",
"prettier": "^3.0.0",
Expand Down
Loading