diff --git a/.travis.yml b/.travis.yml
index 90126c24..b696f51e 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -2,6 +2,7 @@ dist: trusty
addons:
chrome: stable
apt:
+ update: true
sources:
- google-chrome
packages:
@@ -16,10 +17,10 @@ matrix:
- "2.7"
before_install:
# Install bazel.
- - wget https://github.com/bazelbuild/bazel/releases/download/0.11.1/bazel_0.11.1-linux-x86_64.deb
- - echo f3df344b16a40d4233a7606cce38869d4df3bb35296ac2f4e18838566ae3cb48 bazel_0.11.1-linux-x86_64.deb | sha256sum -c
- - sudo dpkg -i bazel_0.11.1-linux-x86_64.deb
- - rm bazel_0.11.1-linux-x86_64.deb
+ - wget https://github.com/bazelbuild/bazel/releases/download/0.26.1/bazel_0.26.1-linux-x86_64.deb
+ - echo c0b2b676ca7cc071a98f969aefb4a9d4b7db1858b7340d9db6f8076179e776cd bazel_0.26.1-linux-x86_64.deb | sha256sum -c
+ - sudo dpkg -i bazel_0.26.1-linux-x86_64.deb
+ - rm bazel_0.26.1-linux-x86_64.deb
script:
- ./backend_tests.sh $GROUP $TOTAL_GROUPS
env:
diff --git a/README.md b/README.md
index bd8f6c9b..e0b0d848 100644
--- a/README.md
+++ b/README.md
@@ -24,20 +24,52 @@ The program is comprised of three parts:
* A Google App Engine (GAE) application
* A Chrome App that runs on each Chrome OS device
+## Important notice about Chrome Apps!
+**Note:** [Chrome Apps are being phased out in favor of extensions and progressive web apps](https://blog.chromium.org/2020/01/moving-forward-from-chrome-apps.html)
+
+**If you haven't yet deployed Grab and Go to the Chrome Web Store**
+
+Enterprise/EDU customers can continue to deploy Chrome Apps to the Chrome
+Web Store using the guidance we've provided (specifically unlisted/private
+hosting in the Chrome Web Store) in the documentation for the forseeable future.
+
+**If you have deployed Grab and Go to the Chrome Web Store**
+
+You should be in good shape for now! Existing Enterprise/EDU Chrome Apps (Grab
+and Go included) are not in-scope until June 2022 according to the announcement
+linked above.
+
## Current release: [Alpha (v0.7.1a)](https://github.com/google/loaner/tree/Alpha-(0.7.1))
+**Note: If you are doing a new deployment please deploy from master as we work
+on cutting a new release. For current deployments, please hold off on upgrading
+until we can test the next numbered release.**
+
Please note that the current release of this application is in ALPHA.
We will be actively contributing to the project. Please keep an eye out for
future updates and features!
-To clone this release run the following command:
+
+**Note:** To build this project you must install Bazel 0.26. Currently
+Bazel 0.27 or later is unsupported.
+
+To use the **latest code (also known as master)**, run the following
+command:
```
-git clone -b Alpha-\(0.7\) https://github.com/google/loaner.git
+git clone https://github.com/google/loaner.git
cd loaner
```
-* To discuss this project send an email to loaner@googlegroups.com.
+To use release number **0.7.1**, run the following command:
+
+```
+git clone -b Alpha-\(0.7.1\) https://github.com/google/loaner.git
+cd loaner
+```
+
+* To discuss this project send an email to loaner@googlegroups.com. Please note
+ that this group is public (anyone can view/post).
* Read more about releases in our [release notes](docs/release_notes.md).
* Please file bugs using the GitHub issue tracker.
@@ -66,12 +98,11 @@ cd loaner
To deploy and configure the Grab n Go (GnG) Loaner project, follow the steps
below.
-1. [Setup the Web
- Application](docs/setup_guide.md)
-1. [Deploy the Grab n Go Chrome
- App](docs/deploy_chrome_app.md)
-1. [Configure your G Suite
- Environment](docs/gsuite_config.md)
++ [Part 1: Create necessary accounts and computer environments](docs/gngsetup_part1.md)
++ [Part 2: Set up the GnG web app](docs/gngsetup_part2.md)
++ [Part 3: Deploy the Grab n Go Chrome app](docs/gngsetup_part3.md)
++ [Part 4: Configure the G Suite Environment](docs/gngsetup_part4.md)
+
#### Reference Documentation
diff --git a/WORKSPACE b/WORKSPACE
index 44a9cc50..25e4fe5d 100644
--- a/WORKSPACE
+++ b/WORKSPACE
@@ -154,25 +154,57 @@ http_archive(
],
)
+http_archive(
+ name = "futures_archive",
+ build_file = "//third_party:futures.BUILD",
+ sha256 = "9ec02aa7d674acb8618afb127e27fde7fc68994c0437ad759fa094a574adb265",
+ strip_prefix = "futures-3.2.0",
+ urls = [
+ "https://files.pythonhosted.org/packages/1f/9e/7b2ff7e965fc654592269f2906ade1c7d705f1bf25b7d469fa153f7d19eb/futures-3.2.0.tar.gz",
+ ],
+)
+
+bind(
+ name = "futures",
+ actual = "@futures_archive//:futures",
+)
+
+# NOTE: workaround for pkg_resources import issue with gcloud_bigquery.
+http_archive(
+ name = "setup_tools_archive",
+ build_file = "//third_party:setup_tools.BUILD",
+ sha256 = "47881d54ede4da9c15273bac65f9340f8929d4f0213193fa7894be384f2dcfa6",
+ strip_prefix = "setuptools-40.2.0",
+ urls = [
+ "http://mirror.bazel.build/pypi.python.org/packages/source/s/six/setuptools-40.2.0.zip",
+ "https://pypi.python.org/packages/source/s/setuptools/setuptools-40.2.0.zip",
+ ],
+)
+
http_archive(
name = "gcloud_bigquery_archive",
build_file = "//third_party:gcloud_bigquery.BUILD",
- sha256 = "6e8cc6914701bbfd8845cc0e0b19c5e2123649fc6ddc49aa945d83629499f4ec",
- strip_prefix = "google-cloud-bigquery-0.25.0",
+ sha256 = "aed2b1d4db1e21d891522d6d6bb14476e6ba58c681cbb68eeb42c168a4e3fda9",
+ strip_prefix = "google-cloud-bigquery-1.1.0",
urls = [
- "https://mirror.bazel.build/pypi.python.org/packages/4a/f1/05631b0a29b1f763794404195d161edb24d7463029c987e0a32fc521e2a6/google-cloud-bigquery-0.25.0.tar.gz",
- "https://pypi.python.org/packages/4a/f1/05631b0a29b1f763794404195d161edb24d7463029c987e0a32fc521e2a6/google-cloud-bigquery-0.25.0.tar.gz",
+ "https://mirror.bazel.build/files.pythonhosted.org/packages/24/f8/54a929bc544d4744ef02cee1c9b97c9498d835445608bf2d099268ed8f1c/google-cloud-bigquery-1.1.0.tar.gz",
+ "https://files.pythonhosted.org/packages/24/f8/54a929bc544d4744ef02cee1c9b97c9498d835445608bf2d099268ed8f1c/google-cloud-bigquery-1.1.0.tar.gz",
],
)
+bind(
+ name = "gcloud_bigquery",
+ actual = "@gcloud_bigquery_archive//:gcloud_bigquery",
+)
+
http_archive(
name = "gcloud_core_archive",
build_file = "//third_party:gcloud_core.BUILD",
- sha256 = "1249ee44c445f820eaf99d37904b37961347019dcd3637dbad1f3173260245f2",
- strip_prefix = "google-cloud-core-0.25.0",
+ sha256 = "89e8140a288acec20c5e56159461d3afa4073570c9758c05d4e6cb7f2f8cc440",
+ strip_prefix = "google-cloud-core-0.28.1",
urls = [
- "https://mirror.bazel.build/pypi.python.org/packages/58/d0/c3a30eca2a0073d5ac00254a1a9d259929a899deee6e3dfe4e45264f5187/google-cloud-core-0.25.0.tar.gz",
- "https://pypi.python.org/packages/58/d0/c3a30eca2a0073d5ac00254a1a9d259929a899deee6e3dfe4e45264f5187/google-cloud-core-0.25.0.tar.gz",
+ "https://mirror.bazel.build/files.pythonhosted.org/packages/22/f0/a062f4d877420e765f451af99045326e44f9b026088d621ca40011f14c66/google-cloud-core-0.28.1.tar.gz",
+ "https://files.pythonhosted.org/packages/22/f0/a062f4d877420e765f451af99045326e44f9b026088d621ca40011f14c66/google-cloud-core-0.28.1.tar.gz",
],
)
@@ -278,23 +310,30 @@ http_archive(
http_archive(
name = "io_bazel_rules_appengine",
- sha256 = "3cc3963d883c06d953181c28ce8c32ad4720779fca22a36891fc54ffb41c32d0",
- strip_prefix = "rules_appengine-edee76dd6892c1af75ad4166c1d3f709d240daf5",
- url = "https://github.com/bazelbuild/rules_appengine/archive/edee76dd6892c1af75ad4166c1d3f709d240daf5.tar.gz",
+ sha256 = "b5b3c964e7dba92ab2a80857519ef3a8c599c4fc3e84094ea112ec34cfe4b2e2",
+ url = "https://github.com/bazelbuild/rules_appengine/archive/0.0.9.tar.gz",
+ strip_prefix = "rules_appengine-0.0.9",
)
+load(
+ "@io_bazel_rules_appengine//appengine:sdk.bzl",
+ "appengine_repositories",
+)
+
+appengine_repositories()
+
+
load(
"@io_bazel_rules_appengine//appengine:py_appengine.bzl",
"py_appengine_repositories"
)
-
py_appengine_repositories()
http_archive(
name = "io_bazel_rules_python",
- sha256 = "8b32d2dbb0b0dca02e0410da81499eef8ff051dad167d6931a92579e3b2a1d48",
- strip_prefix = "rules_python-8b5d0683a7d878b28fffe464779c8a53659fc645",
- url = "https://github.com/bazelbuild/rules_python/archive/8b5d0683a7d878b28fffe464779c8a53659fc645.tar.gz",
+ sha256 = "9a3d71e348da504a9c4c5e8abd4cb822f7afb32c613dc6ee8b8535333a81a938",
+ strip_prefix = "rules_python-fdbb17a4118a1728d19e638a5291b4c4266ea5b8",
+ url = "https://github.com/bazelbuild/rules_python/archive/fdbb17a4118a1728d19e638a5291b4c4266ea5b8.tar.gz",
)
load("@io_bazel_rules_python//python:pip.bzl", "pip_repositories", "pip_import")
@@ -469,23 +508,13 @@ http_archive(
],
)
-http_archive(
- name = "setup_tools_archive",
- build_file = "//third_party:setup_tools.BUILD",
- sha256 = "6501fc32f505ec5b3ed36ec65ba48f1b975f52cf2ea101c7b73a08583fd12f75",
- strip_prefix = "setuptools-38.4.0",
- urls = [
- "https://pypi.python.org/packages/41/5f/6da80400340fd48ba4ae1c673be4dc3821ac06cd9821ea60f9c7d32a009f/setuptools-38.4.0.zip",
- ],
-)
-
http_archive(
name = "six_archive",
build_file = "//third_party:six.BUILD",
- sha256 = "70e8a77beed4562e7f14fe23a786b54f6296e34344c23bc42f07b15018ff98e9",
- strip_prefix = "six-1.11.0",
+ sha256 = "236bdbdce46e6e6a3d61a337c0f8b763ca1e8717c03b369e87a7ec7ce1319c0a",
+ strip_prefix = "six-1.14.0",
urls = [
- "https://pypi.python.org/packages/16/d8/bc6316cf98419719bd59c91742194c111b6f2e85abac88e496adefaf7afe/six-1.11.0.tar.gz",
+ "https://files.pythonhosted.org/packages/21/9f/b251f7f8a76dec1d6651be194dfba8fb8d7781d10ab3987190de8391d08e/six-1.14.0.tar.gz",
],
)
diff --git a/docs/README.md b/docs/README.md
index 0062677b..a3f6b565 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -1,15 +1,12 @@
# Grab n Go Loaners
-To deploy and configure the Grab n Go (GnG) Loaner project, follow the steps
-below.
+To deploy and configure the Grab n Go (GnG) Loaner project:
-1. [Setup the Web
- Application](setup_guide.md)
-1. [Deploy the Grab n Go Chrome
- App](deploy_chrome_app.md)
-1. [Configure your G Suite
- Environment](gsuite_config.md)
++ [Part 1: Create necessary accounts and computer environments](gngsetup_part1.md)
++ [Part 2: Set up the GnG web app](gngsetup_part2.md)
++ [Part 3: Deploy the Grab n Go Chrome app](gngsetup_part3.md)
++ [Part 4: Configure the G Suite Environment](gngsetup_part4.md)
#### Reference Documentation
diff --git a/docs/customizations.md b/docs/customizations.md
new file mode 100644
index 00000000..06056797
--- /dev/null
+++ b/docs/customizations.md
@@ -0,0 +1,140 @@
+### (Optional) Customize GnG Settings
+
+*Default Configurations* are those options you can configure when GnG is
+running. The default values for these options are defined in
+`loaner/web_app/config_defaults.yaml`. After first launch, GnG stores these
+values in [Cloud Datastore](https://cloud.google.com/datastore/). You can change
+settings without deploying a new version of GnG:
+
++ **allow_guest_mode**: Allow users to use guest mode on loaner devices.
++ **loan_duration**: The number of days to assign a device.
++ **maximum_loan_duration**: The maximum number of days a loaner can be
+ loaned.
++ **loan_duration_email**: Send a duration email to the user.
++ **reminder_email_throttling**: Do not send emails to a user when a reminder
+ appears in the loaner's Chrome app.
++ **reminder_delay**: Number of hours after which GnG will send a reminder
+ email for a device identified as needing a reminder.
++ **shelf_audit**: Enable shelf audit.
++ **shelf_audit_email**: Whether email should be sent for audits.
++ **shelf_audit_email_to**: List of email addresses to receive a notification.
++ **shelf_audit_interval**: The number of hours to allow a shelf to remain
+ unaudited. Can be overwritten via the audit_interval_override property for a
+ shelf.
++ **responsible_for_audit**: Group that is responsible for performing an audit
+ on a shelf.
++ **support_contact**: The name of the support contact.
++ **org_unit_prefix**: The organizational unit to be the root for the GnG
+ child organizational units.
++ **audit_interval**: The shelf audit threshold in hours.
++ **sync_roles_query_size**: The number of users for whom to query and
+ synchronize roles.
++ **anonymous_surveys**: Record surveys anonymously (or not).
++ **use_asset_tags**: To require asset tags when enrolling new devices, set as
+ True. Otherwise, set as False to only require serial numbers.
++ **img_banner_**: The banner is a custom image used in the reminder emails
+ sent to users. Use the URL of an image you have stored in your GCP Storage.
++ **img_button_**: The button images is a custom image used for reminder
+ emails sent to users. Use the URL of an image you have stored in your GCP
+ Storage.
++ **timeout_guest_mode**: Specify that a deferred task should be created to
+ time out guest mode.
++ **guest_mode_timeout_in_hours**: The number of hours to allow guest mode to
+ be in use.
++ **unenroll_ou**: The organizational unit into which to move devices as they
+ leave the GnG program. This value defaults to the root organizational unit.
++ **return_grace_period**: The grace period (in minutes) between a user
+ marking a device as pending return and when we reopen the existing loan.
+
+### (Optional) Customize Images for Button and Banner in Emails
+
+You can upload custom banner and button images to
+[Google Cloud Storage](https://cloud.google.com/storage/) to use in the emails
+sent by the GnG.
+
+To do this, upload your custom images to Google Cloud Storage via the console by
+following
+[these instructions](https://cloud.google.com/storage/docs/cloud-console).
+
+Name your bucket and object something descriptive, e.g.
+`https://storage.cloud.google.com/[BUCKET_NAME]/[OBJECT_NAME]`.
+
+The recommended banner image size is 1280 x 460 and the recommended button size
+is 840 x 140. Make sure the `Public Link` checkbox is checked for both of the
+images you upload to Cloud Storage.
+
+Next, click on the image names in the console to open the images and copy their
+URLs. Take these URLs and populate them as values for the variables
+`img_banner_primary` and `img_button_manage` in the `config_defaults.yaml` file.
+
+### (Optional) Customize Events and Email Templates in the GnG Datastore
+
+This YAML file contains the event settings and email templates that the
+bootstrap process imports into Cloud Datastore after first launch:
+
+`loaner/web_app/backend/lib/bootstrap.yaml`
+
+#### Core Events
+
+Core events (in the `core_events` section) are events that GnG raises at runtime
+when a particular event occurs. For example, the assignment of a new device or
+the enrollment of a new shelf. The calls to raise events are hard-coded and the
+event names in the configuration YAML file must correspond to actions defined in
+the `loaner/web_app/backend/actions` directory.
+
+Specifically, each event can be configured in the datastore to call zero or more
+actions and these actions are defined by the modules contained in the
+`loaner/web_app/backend/actions` directory. Each of these actions will be run as
+an
+[App Engine Task](https://cloud.google.com/appengine/docs/standard/python/taskqueue/),
+which allows them to run asynchronously and not block the processing of GnG.
+
+While GnG contains several pre-coded actions, you can also add your own. For
+example, you can add an action as a module in the
+`loaner/web_app/backend/actions` directory to interact with your organization's
+ticketing or inventory system. If you do this, please be sure to add or remove
+the actions in the applicable events section in the YAML file.
+
+When bootstrapping is complete, this YAML will have been imported and converted
+into Cloud Datastore entities — you'll need to make further changes to those
+entities.
+
+#### Custom Events
+
+Custom events (in the `custom_events` section) are events that GnG raises as
+part of a regular cron job. These events define criteria on the Device and Shelf
+entities in the Cloud Datastore. GnG queries the Datastore using the defined
+criteria and raises Action tasks, just as it does for Core events.
+
+The difference is that GnG uses the query to determine which entities require
+these events. For example, you can specify that Shelf entities with an audit
+date of more than three days ago should trigger an email to a management team
+and run the corresponding actions that are defined for that event.
+
+The custom events system can access the same set of actions as core events.
+
+#### Reminder Events
+
+Reminder events (in the `reminder_events` section) define criteria for device
+entities that trigger reminders for a user. For example, that their device is
+due tomorrow or is overdue. These events are numbered starting with 0. You can
+customize the events as need be.
+
+**Note**: If you customize any event, be sure to change the neighboring events,
+too. Reminder events must not overlap with each other. If so, reminders may
+provide conflicting information to borrowers.
+
+The reminder events system can access the same set of actions as core and custom
+events.
+
+#### Shelf Audit Event
+
+Shelf audit events (in the `shelf_audit_events` section) are events that are
+triggered by the shelf audit cron job. GnG runs a single Shelf audit event by
+default, but you can add custom events as well.
+
+#### Email Templates
+
+The `templates` section contains a base email template for reminders, and
+higher-level templates that extend that base template for specific reminders.
+You can customize the templates.
diff --git a/docs/gng_apis.md b/docs/gng_apis.md
index 64b37497..adacd770 100644
--- a/docs/gng_apis.md
+++ b/docs/gng_apis.md
@@ -1,13 +1,12 @@
-# Grab n Go API
-
-
+
+# Grab n Go API
## Getting Started with the GnG API
This documentation explains how to get started with the GnG API.
-## API Authentication
+### API Authentication
The GnG API is authenticated based on user roles and permissions. Roles are
managed by Google groups that are synced with a Cron job.
@@ -25,12 +24,11 @@ There are two roles built into the app by default:
experience. This role has all permissions by default and thus
the ability to perform all of the actions within the application.
-Additional roles can be created by using the Roles API. Each Role can be
-given zero or more permissions and associated with a group to automatically
-add users to the given role. Some example roles you may want to create are
-a technician role that can audit shelves and other inventory-related tasks or
-a helpdesk role that can assist users with their loans.
-
+Additional roles can be created by using the Roles API. Each role can be given
+zero or more permissions and associated with a group to automatically add users
+to the given role. Some example roles you may want to create are a technician
+role that can audit shelves and other inventory-related tasks or a helpdesk role
+that can assist users with their loans.
### Authentication Decorator
@@ -85,8 +83,8 @@ also be synced to groups so you don't need to manually update them.
1. Go to the root of the source code and search for a file named
`constants.py`.
-1. Use your favorite editor to open the file and add the superadmin group
- that you created earlier. For example:
+1. Use your favorite editor to open the file and add the superadmin group that
+ you created earlier. For example:
```python
# superadmins_group: str, The name of the Google Group that governs who is
@@ -96,43 +94,82 @@ also be synced to groups so you don't need to manually update them.
## API List
+
+
### Bootstrap_api
The entry point for the Bootstrap methods.
#### Methods
-##### get_status
-
-Gets general bootstrap status, and task status if not yet completed:
-
-Requests | Attributes
-:------------------------ | :---------
-message_types.VoidMessage | None
-
-| Returns | Attributes |
-| :---------------------------------- | :------------------------------------- |
-| GetStatusResponse: Bootstrap status | enabled: bool, indicates if the |
-| response ProtoRPC | bootstrap is enabled. |
-| | started: bool, indicated if the |
-| | bootstrap has been started. |
-| | completed: bool, indicated if the |
-| | bootstrap is completed. |
-| | tasks: BootstrapTask, A list of all of |
-| | the tasks to be displayed. |
-
-##### run
-
-Runs request for the Bootstrap API:
-
-| Requests | Attributes |
-| :---------------------------- | :---------------------------------------- |
-| RunRequest: Bootstrap request | requested_tasks: BootstrapTask, A list of |
-| ProtoRPC message | the requested tasks. |
-
-Returns | Attributes
-:------------------------ | :---------
-message_types.VoidMessage | None
+`get_status` Gets general bootstrap status, and task status if not yet
+completed:
+
+
+
+
+
Requests
+
Attributes
+
+
+
message_types.VoidMessage
+
None
+
+
+
+
+
+
+
+
Returns
+
Attributes
+
+
+
GetStatusResponse: Bootstrap status response ProtoRPC
+
enabled: bool, indicates if the bootstrap is enabled.
+
+
+
started: bool, indicated if the bootstrap has been started.
+
+
+
completed: bool, indicated if the bootstrap is completed.
+
+
+
tasks: BootstrapTask, A list of all of the tasks to be displayed.
+
+
+
+
+
+`run` Runs request for the Bootstrap API:
+
+
+
+
+
Requests
+
Attributes
+
+
+
RunRequest: Bootstrap request ProtoRPC message
+
requested_tasks: BootstrapTask, A list of the requested tasks.
+
+
+
+
+
+
+
+
Returns
+
Attributes
+
+
+
message_types.VoidMessage
+
None
+
+
+
+
+
### Chrome_api
@@ -140,21 +177,38 @@ The entry point for the GnG Loaners Chrome App.
#### Methods
-##### heartbeat
-
-Heartbeat check-in for Chrome devices:
-
-| Requests | Attributes |
-| :---------------------------------- | :-------------------------------- |
-| HeartbeatRequest: Heartbeat Request | device_id: str, The unique Chrome |
-| ProtoRPC message. | device ID of the Chrome device. |
-
-| Returns | Attributes |
-| :--------------------------- | :-------------------------------------------- |
-| HeartbeatResponse: Heartbeat | is_enrolled: bool, Determine if the device is |
-| Response ProtoRPC message. | enrolled. |
-| | start_assignment: bool, Determine if |
-| | assignment workflow should be started. |
+`heartbeat`Heartbeat check-in for Chrome devices:
+
+
is_enrolled: bool, Determine if the device is enrolled.
+
+
+
+
start_assignment: bool, Determine if assignment workflow should be started.
+
+
+
+
### Configuration_api
@@ -162,68 +216,126 @@ Lists the given setting's value.
#### Methods
-##### get
-
-Lists the given setting's value:
-
-| Requests | Attributes |
-| :---------------------------------- | :------------------------------------- |
-| GetConfigurationRequest request for | setting: str, The name of the setting |
-| ProtoRPC message. | being requested. |
-| | configuration_type: ConfigurationType, |
-| | The type of configuration to request |
-| | for. |
-
-| Returns | Attributes |
-| :--------------------------------- | :-------------------------------------- |
-| ConfigurationResponse response for | setting: str, The name of the setting |
-| ProtoRPC message. | being returned. |
-| | string_value: str, The string value of |
-| | the setting. |
-| | integer_value: int, The integer value |
-| | of the setting. |
-| | boolean_value: bool, The boolean value |
-| | of the setting. |
-| | list_value: list, The list value of the |
-| | setting. |
-
-##### list
-
-Get a list of all configuration values.
-
-Requests | Attributes
-:------------------------ | :---------
-message_types.VoidMessage | None
-
-| Returns | Attributes |
-| :---------------------------------- | :------------------------------------ |
-| ListConfigurationsResponse response | settings: ConfigurationResponse, The |
-| for ProtoRPC message. | setting and corresponding value being |
-| | returned. |
-
-##### update
-
-Updates a given settings value.
-
-| Requests | Attributes |
-| :--------------------------------- | :-------------------------------------- |
-| UpdateConfigurationRequest request | setting: str, The name of the setting |
-| for ProtoRPC message. | being requested. |
-| | configuration_type: ConfigurationType, |
-| | The type of configuration to request |
-| | for. |
-| | string_value: str, The string value of |
-| | the setting being updated. |
-| | integer_value: int, The integer value |
-| | of the setting being updated. |
-| | boolean_value: bool, The boolean value |
-| | of the setting being updated. |
-| | list_value: list, The list value of the |
-| | setting being updated. |
-
-Returns | Attributes
-:------------------------ | :---------
-message_types.VoidMessage | None
+`get` Lists the given setting's value:
+
+
+
+
+
Requests
+
Attributes
+
+
+
GetConfigurationRequest request for ProtoRPC message.
+
setting: str, The name of the setting being requested.
+
+
+
+
configuration_type: ConfigurationType, the type of configuration to request for.
+
+
+
+
+
+
+
Returns
+
Attributes
+
+
+
ConfigurationResponse response for ProtoRPC message.
+
setting: str, The name of the setting being returned.
+
+
+
+
string_value: str, The string value of the setting.
+
+
+
integer_value: int, The integer value of the setting.
+
+
+
boolean_value: bool, The boolean value of the setting.
+
+
+
list_value: list, The list value of the setting.
+
+
+
+
+
+`list` Get a list of all configuration values.
+
+
+
+
+
Requests
+
Attributes
+
+
+
message_types.VoidMessage
+
None
+
+
+
+
+
+
+
+
Returns
+
Attributes
+
+
+
ListConfigurationsResponse response for ProtoRPC message.
+
settings: ConfigurationResponse, The setting and corresponding value being returned.
+
+
+
+
+
+
+`update` Updates a given settings value.
+
+
+
+
+
Requests
+
Attributes
+
+
+
UpdateConfigurationRequest request for ProtoRPC message.
+
setting: str, The name of the setting being requested.
+
+
+
+
configuration_type: ConfigurationType, The type of configuration to request for.
+
+
+
string_value: str, The string value of the setting being updated.
+
+
+
integer_value: int, The integer value ff the setting being updated.
+
+
+
boolean_value: bool, The boolean value of the setting being updated.
+
+
+
list_value: list, The list value of the setting being updated.
+
+
+
+
+
+
+
+
Returns
+
Attributes
+
+
+
message_types.VoidMessage
+
None
+
+
+
+
+
### Datastore_api
@@ -231,18 +343,35 @@ The entry point for the Datastore methods.
#### Methods
-##### import
-
-Datastore import request for the Datastore API.
-
-| Requests | Attributes |
-| :---------------------------- | :------------------------------------ |
-| Datastore YAML Import Request | yaml: str, The name of the YAML being |
-| ProtoRPC message. | imported. |
-
-Returns | Attributes
-:------------------------ | :---------
-message_types.VoidMessage | None
+`import` Datastore import request for the Datastore API.
+
+
+
+
+
Requests
+
Attributes
+
+
+
Datastore YAML Import Request ProtoRPC message.
+
yaml: str, The name of the YAML being imported.
+
+
+
+
+
+
+
+
Returns
+
Attributes
+
+
+
message_types.VoidMessage.
+
None
+
+
+
+
+
### Device_api
@@ -250,292 +379,572 @@ API endpoint that handles requests related to Devices.
#### Methods
-##### auditable
-
-If a device is able to be audited for shelf audits. Returns an error if the
-device cannot be moved to the shelf for any reason.
-
-| Requests | Attributes |
-| :-------------------------------- | :--------------------------------------- |
-| General Device request ProtoRPC | asset_tag: str, The asset tag of the |
-| message with several identifiers. | Chrome device. |
-| Only one identifier needs to be | chrome_device_id: str, The Chrome device |
-| provided. | id of the Chrome device. |
-| | serial_number: str, The serial number of |
-| | the Chrome device. |
-| | urlkey: str, The URL-safe key of a |
-| | device. |
-| | unknown_identifier: str, Either an asset |
-| | tag or serial number of the device. |
-
-Returns | Attributes
-:------------------------ | :---------
-message_types.VoidMessage | None
-
-##### enable_guest_mode
-
-Enables Guest Mode for a given device.
-
-| Requests | Attributes |
-| :-------------------------------- | :--------------------------------------- |
-| General Device request ProtoRPC | asset_tag: str, The asset tag of the |
-| message with several identifiers. | Chrome device. |
-| Only one identifier needs to be | chrome_device_id: str, The Chrome device |
-| provided. | id of the Chrome device. |
-| | serial_number: str, The serial number of |
-| | the Chrome device. |
-| | urlkey: str, The URL-safe key of a |
-| | device. |
-| | unknown_identifier: str, Either an asset |
-| | tag or serial number of the device. |
-
-Returns | Attributes
-:------------------------ | :---------
-message_types.VoidMessage | None
-
-##### enroll
-
-Enrolls a device in the program
-
-| Requests | Attributes |
-| :-------------------------------- | :--------------------------------------- |
-| General Device request ProtoRPC | asset_tag: str, The asset tag of the |
-| message with several identifiers. | Chrome device. |
-| Only one identifier needs to be | chrome_device_id: str, The Chrome device |
-| provided. | id of the Chrome device. |
-| | serial_number: str, The serial number of |
-| | the Chrome device. |
-| | urlkey: str, The URL-safe key of a |
-| | device. |
-| | unknown_identifier: str, Either an asset |
-| | tag or serial number of the device. |
-
-Returns | Attributes
-:------------------------ | :---------
-message_types.VoidMessage | None
-
-##### extend_loan
-
-Extend the current loan for a given Chrome device.
-
-| Requests | Attributes |
-| :------------------------------ | :---------------------------------------- |
-| Loan extension request ProtoRPC | device: DeviceRequest, A device to be |
-| message. | fetched. |
-| | extend_date: datetime, The date to extend |
-| | the loan for. |
-
-Returns | Attributes
-:------------------------ | :---------
-message_types.VoidMessage | None
-
-##### get
-
-Gets a device using any identifier in device_message.DeviceRequest.
-
-| Requests | Attributes |
-| :-------------------------------- | :--------------------------------------- |
-| General Device request ProtoRPC | asset_tag: str, The asset tag of the |
-| message with several identifiers. | Chrome device. |
-| Only one identifier needs to be | chrome_device_id: str, The Chrome device |
-| provided. | id of the Chrome device. |
-| | serial_number: str, The serial number of |
-| | the Chrome device. |
-| | urlkey: str, The URL-safe key of a |
-| | device. |
-| | unknown_identifier: str, Either an asset |
-| | tag or serial number of the device. |
-
-Returns | Attributes
-:------------------------ | :---------
-message_types.VoidMessage | None
-
-##### list
-
-Lists all devices based on any device attribute.
-
-| Requests | Attributes |
-| :----------------------- | :------------------------------------------------ |
-| Device ProtoRPC message. | serial_number: str, The serial number of the |
-| | Chrome device. |
-| | asset_tag: str, The asset tag of the Chrome |
-| | device. |
-| | enrolled: bool, Indicates the enrollment status |
-| | of the device. |
-| | device_model: int, Identifies the model name of |
-| | the device. |
-| | due_date: datetime, The date that device is due |
-| | for return. |
-| | last_know_healthy: datetime, The date to indicate |
-| | the last known healthy status. |
-| | shelf: shelf_messages.Shelf, The shelf the device |
-| | is placed on. |
-| | assigned_user: str, The email of the user who is |
-| | assigned to the device. |
-| | assignment_date: datetime, The date the device |
-| | was assigned to a user. |
-| | current_ou: str, The current organizational unit |
-| | the device belongs to. |
-| | ou_change_date: datetime, The date the |
-| | organizational unit was changed. |
-| | locked: bool, Indicates whether or not the device |
-| | is locked. |
-| | lost: bool, Indicates whether or not the device |
-| | is lost. |
-| | mark_pending_return_date: datetime, The date a |
-| | user marked device returned. |
-| | chrome_device_id: str, A unique device ID. |
-| | last_heartbeat: datetime, The date of the last |
-| | time the device checked in. |
-| | damaged: bool, Indicates the if the device is |
-| | damaged. |
-| | damaged_reason: str, A string denoting the reason |
-| | for being reported as damaged. |
-| | last_reminder: Reminder, Level, time, and count |
-| | of the last reminder the device had. |
-| | next_reminder: Reminder, Level, time, and count |
-| | of the next reminder. |
-| | page_size: int, The number of results to query |
-| | for and display. |
-| | page_number: int, the page index to offset the |
-| | results |
-| | max_extend_date: datetime, Indicates maximum |
-| | extend date a device can have. |
-| | guest_enabled: bool, Indicates if guest mode has |
-| | been already enabled. |
-| | guest_permitted: bool, Indicates if guest mode has|
-| | been allowed. |
-| | give_name: str, The given name of the user. |
-| | query: shared_message.SearchRequest, a message |
-| | containing query options to conduct a search on an|
-| | index. |
-
-| Returns | Attributes |
-| :---------------------------- | :------------------------------------------ |
-| List device response ProtoRPC | devices: Device, A device to display. |
-| message. | |
-| | total_results: int, the total number of |
-| | results for a query. |
-| | total_pages: int, the total number of pages |
-| | needed to display all of the results. |
-
-##### mark_damaged
-
-Mark that a device is damaged.
-
-| Requests | Attributes |
-| :------------------------------- | :------------------------------------ |
-| Damaged device ProtoRPC message. | device: DeviceRequest, A device to be |
-| | fetched. |
-| | damaged_reason: str, The reason the |
-| | device is being reported as damaged. |
-
-Returns | Attributes
-:------------------------ | :---------
-message_types.VoidMessage | None
-
-##### mark_lost
-
-Mark that a device is lost.
-
-| Requests | Attributes |
-| :-------------------------------- | :--------------------------------------- |
-| General Device request ProtoRPC | asset_tag: str, The asset tag of the |
-| message with several identifiers. | Chrome device. |
-| | chrome_device_id: str, The Chrome device |
-| | id of the Chrome device. |
-| | serial_number: str, The serial number of |
-| | the Chrome device. |
-| | urlkey: str, The URL-safe key of a |
-| | device. |
-| | unknown_identifier: str, Either an asset |
-| | tag or serial number of the device. |
-
-Returns | Attributes
-:------------------------ | :---------
-message_types.VoidMessage | None
-
-##### mark_pending_return
-
-Mark that a device is pending return.
-
-| Requests | Attributes |
-| :-------------------------------- | :--------------------------------------- |
-| General Device request ProtoRPC | asset_tag: str, The asset tag of the |
-| message with several identifiers. | Chrome device. |
-| Only one identifier needs to be | chrome_device_id: str, The Chrome device |
-| provided. | id of the Chrome device. |
-| | serial_number: str, The serial number of |
-| | the Chrome device. |
-| | urlkey: str, The URL-safe key of a |
-| | device. |
-| | unknown_identifier: str, Either an asset |
-| | tag or serial number of the device. |
-
-Returns | Attributes
-:------------------------ | :---------
-message_types.VoidMessage | None
-
-##### resume_loan
-
-Manually resume a loan that was paused because the device was marked
-pending_return.
-
-| Requests | Attributes |
-| :-------------------------------- | :--------------------------------------- |
-| General Device request ProtoRPC | asset_tag: str, The asset tag of the |
-| message with several identifiers. | Chrome device. |
-| Only one identifier needs to be | chrome_device_id: str, The Chrome device |
-| provided. | id of the Chrome device. |
-| | serial_number: str, The serial number of |
-| | the Chrome device. |
-| | urlkey: str, The URL-safe key of a |
-| | device. |
-| | unknown_identifier: str, Either an asset |
-| | tag or serial number of the device. |
-
-Returns | Attributes
-:------------------------ | :---------
-message_types.VoidMessage | None
-
-##### unenroll
-
-Unenrolls a device from the program.
-
-| Requests | Attributes |
-| :-------------------------------- | :--------------------------------------- |
-| General Device request ProtoRPC | asset_tag: str, The asset tag of the |
-| message with several identifiers. | Chrome device. |
-| Only one identifier needs to be | chrome_device_id: str, The Chrome device |
-| provided. | id of the Chrome device. |
-| | serial_number: str, The serial number of |
-| | the Chrome device. |
-| | urlkey: str, The URL-safe key of a |
-| | device. |
-| | unknown_identifier: str, Either an asset |
-| | tag or serial number of the device. |
-
-Returns | Attributes
-:------------------------ | :---------
-message_types.VoidMessage | None
-
-##### user_devices
-
-Lists the devices assigned to the currently logged in user.
-
-Requests | Attributes
-:------------------------ | :---------
-message_types.VoidMessage | None
-
-| Returns | Attributes |
-| :---------------------------- | :------------------------------------------ |
-| List device response ProtoRPC | devices: Device, A device to display. |
-| message. | |
-| | additional_results: bool, If there are more |
-| | results to be displayed. |
-| | page_token: str, A page token that will |
-| | allow be used to query for additional |
-| | results. |
+`auditable` If a device is able to be audited for shelf audits. Returns an error
+if the device cannot be moved to the shelf for any reason.
+
+
+
+
+
Requests
+
Attributes
+
+
+
General Device request ProtoRPC message with several identifiers. Only one identifier needs to be provided.
+
asset_tag: str, The asset tag of the Chrome device.
+
+
+
+
chrome_device_id: str, The Chrome device id of the Chrome device.
+
+
+
serial_number: str, The serial number of the Chrome device.
+
+
+
urlkey: str, The URL-safe key of a device.
+
+
+
unknown_identifier: str, Either an asset tag or serial number of the device.
+
+
+
+
+
+
+
Returns
+
Attributes
+
+
+
message_types.VoidMessage
+
None
+
+
+
+
+
+
+`enable_guest_mode` Enables Guest Mode for a given device.
+
+
+
+
+
Requests
+
Attributes
+
+
+
RunRequest: Bootstrap request ProtoRPC message
+
requested_tasks: BootstrapTask, A list of the requested tasks.
+
+
+
+
+
+
+
+
Requests
+
Attributes
+
+
+
General Device request ProtoRPC message with several identifiers. Only one identifier needs to be provided.
+
asset_tag: str, The asset tag of the Chrome device.
+
+
+
+
chrome_device_id: str, The Chrome device id of the Chrome device.
+
+
+
serial_number: str, The serial number of the Chrome device.
+
+
+
urlkey: str, The URL-safe key of a device.
+
+
+
unknown_identifier: str, Either an asset tag or serial number of the device.
+
+
+
+
+
+
+
Returns
+
Attributes
+
+
+
message_types.VoidMessage
+
None
+
+
+
+
+
+
+`enroll` Enrolls a device in the program
+
+
+
+
+
Requests
+
Attributes
+
+
+
General Device request ProtoRPC message with several identifiers. Only one identifier needs to be provided.
+
asset_tag: str, The asset tag of the Chrome device.
+
+
+
+
chrome_device_id: str, The Chrome device id of the Chrome device.
+
+
+
serial_number: str, The serial number of the Chrome device.
+
+
+
urlkey: str, The URL-safe key of a device.
+
+
+
unknown_identifier: str, Either an asset tag or serial number of the device.
+
+
+
+
+
+
+
Returns
+
Attributes
+
+
+
message_types.VoidMessage
+
None
+
+
+
+
+
+
+`extend_loan` Extend the current loan for a given Chrome device.
+
+
+
+
+
Requests
+
Attributes
+
+
+
Loan extension request ProtoRPC message.
+
device: DeviceRequest, A device to be fetched.
+
+
+
+
extend_date: datetime, The date to extend the loan for.
+
+
+
+
+
+
+
Returns
+
Attributes
+
+
+
message_types.VoidMessage
+
None
+
+
+
+
+
+
+`get` Gets a device using any identifier in device_message.DeviceRequest.
+
+
+
+
+
Requests
+
Attributes
+
+
+
General Device request ProtoRPC message with several identifiers. Only one identifier needs to be provided.
+
asset_tag: str, The asset tag of the Chrome device.
+
+
+
+
chrome_device_id: str, The Chrome device id of the Chrome device.
+
+
+
serial_number: str, The serial number of the Chrome device.
+
+
+
urlkey: str, The URL-safe key of a device.
+
+
+
unknown_identifier: str, Either an asset tag or serial number of the device.
+
+
+
+
+
+
+
Returns
+
Attributes
+
+
+
message_types.VoidMessage
+
None
+
+
+
+
+
+
+`list` Lists all devices based on any device attribute.
+
+
+
+
+
Requests
+
Attributes
+
+
+
Device ProtoRPC message.
+
serial_number: str, The serial number of the Chrome device.
+
+
+
+
asset_tag: str, The asset tag of the Chrome device.
+
+
enrolled: bool, Indicates the enrollment status of the device.
+
+
+
+
device_model: int, Identifies the model name of the device.
+
+
+
due_date: datetime, The date that device is due for return.
+
+
+
last_know_healthy: datetime, The date to indicate the last known healthy status.
+
+
+
shelf: shelf_messages. Shelf, The shelf the device is placed on.
+
+
+
assigned_user: str, The email of the user who is assigned to the device.
+
+
+
assignment_date: datetime, The date the device was assigned to a user.
+
+
+
current_ou: str, The current organizational unit the device belongs to.
+
+
+
ou_change_date: datetime, The date the organizational unit was changed.
+
+
+
locked: bool, Indicates whether or not the device is locked.
+
+
+
lost: bool, Indicates whether or not the device is lost.
+
+
+
mark_pending_return_date: datetime, The date a user marked device returned.
+
+
+
chrome_device_id: str, A unique device ID.
+
+
+
last_heartbeat: datetime, The date of the last time the device checked in.
+
+
+
damaged: bool, Indicates the if the device is damaged.
+
+
+
damaged_reason: str, A string denoting the reason for being reported as damaged.
+
+
+
last_reminder: Reminder, Level, time, and count of the last reminder the device had.
+
+
+
next_reminder: Reminder, Level, time, and count of the next reminder.
+
+
+
page_size: int, The number of results to query for and display.
+
+
+
page_token: str, A page token to query next page results.
+
+
+
max_extend_date: datetime, Indicates maximum extend date a device can have.
+
+
+
guest_enabled: bool, Indicates if guest mode has been already enabled.
+
+
+
guest_permitted: bool, Indicates if guest mode has been allowed.
+
+
+
give_name: str, The given name of the user.
+
+
+
query: shared_message.SearchRequest, a message containing query options to conduct a search on an index.
+
+
+
+
+
+
+
Returns
+
Attributes
+
+
+
List device response ProtoRPC message.
+
devices: Device, A device to display.
+
+
+
+
has_additional_results: bool, If there are more results to be displayed.
+
+
+
page_token: str, A page token that will allow be used to query for additional results.
+
+
+
+
+
+`mark_damaged` Mark that a device is damaged.
+
+
+
+
+
Returns
+
Attributes
+
+
+
Damaged device ProtoRPC message.
+
device: DeviceRequest, A device to be fetched.
+
+
+
+
damaged_reason: str, The reason the device is being reported as damaged.
+
+
+
+
+
+
+
Returns
+
Attributes
+
+
+
message_types.VoidMessage
+
None
+
+
+
+
+
+
+`mark_lost` Mark that a device is lost.
+
+
+
+
+
Requests
+
Attributes
+
+
+
General Device request ProtoRPC message with several identifiers. Only one identifier needs to be provided.
+
asset_tag: str, The asset tag of the Chrome device.
+
+
+
+
chrome_device_id: str, The Chrome device id of the Chrome device.
+
+
+
serial_number: str, The serial number of the Chrome device.
+
+
+
urlkey: str, The URL-safe key of a device.
+
+
+
unknown_identifier: str, Either an asset tag or serial number of the device.
+
+
+
+
+
+
+
Returns
+
Attributes
+
+
+
message_types.VoidMessage
+
None
+
+
+
+
+
+
+`mark_pending_return` Mark that a device is pending return.
+
+
+
+
+
Requests
+
Attributes
+
+
+
General Device request ProtoRPC message with several identifiers. Only one identifier needs to be provided.
+
asset_tag: str, The asset tag of the Chrome device.
+
+
+
+
chrome_device_id: str, The Chrome device id of the Chrome device.
+
+
+
serial_number: str, The serial number of the Chrome device.
+
+
+
urlkey: str, The URL-safe key of a device.
+
+
+
unknown_identifier: str, Either an asset tag or serial number of the device.
+
+
+
+
+
+
+
Returns
+
Attributes
+
+
+
message_types.VoidMessage
+
None
+
+
+
+
+
+
+`resume_loan` Manually resume a loan that was paused because the device was
+marked pending_return.
+
+
+
+
+
Requests
+
Attributes
+
+
+
General Device request ProtoRPC message with several identifiers. Only one identifier needs to be provided.
+
asset_tag: str, The asset tag of the Chrome device.
+
+
+
+
chrome_device_id: str, The Chrome device id of the Chrome device.
+
+
+
serial_number: str, The serial number of the Chrome device.
+
+
+
urlkey: str, The URL-safe key of a device.
+
+
+
unknown_identifier: str, Either an asset tag or serial number of the device.
+
+
+
+
+
+
+
Returns
+
Attributes
+
+
+
message_types.VoidMessage
+
None
+
+
+
+
+
+
+`unenroll` Unenrolls a device from the program.
+
+
+
+
+
Requests
+
Attributes
+
+
+
General Device request ProtoRPC message with several identifiers. Only one identifier needs to be provided.
+
asset_tag: str, The asset tag of the Chrome device.
+
+
+
+
chrome_device_id: str, The Chrome device id of the Chrome device.
+
+
+
serial_number: str, The serial number of the Chrome device.
+
+
+
urlkey: str, The URL-safe key of a device.
+
+
+
unknown_identifier: str, Either an asset tag or serial number of the device.
+
+
+
+
+
+
+
Returns
+
Attributes
+
+
+
message_types.VoidMessage
+
None
+
+
+
+
+
+
+`user_devices` Lists the devices assigned to the currently logged in user.
+
+
+
+
+
Returns
+
Attributes
+
+
+
message_types.VoidMessage
+
None
+
+
+
+
+
+
+
+
Returns
+
Attributes
+
+
+
List device response ProtoRPC message.
+
devices: Device, A device to display.
+
+
+
+
has_additional_results: bool, If there are more results to be displayed.
+
+
+
page_token: str, A page token that will allow be used to query for additional results.
+
+
+
+
### Roles_api
@@ -543,54 +952,115 @@ API endpoint that handles requests related to user roles.
#### Methods
-##### create
-
-Create a new role.
-
-| Requests | Attributes
-| :---------------------------- | :---------
-| user_messages.Role | name: str, the name of the role.
-| | permissions: list of str, zero or more
-| | permissions to add to the role.
-| | associated_group: str, optional group to
-| | associate to the role for automatic sync.
-
-| Returns | Attributes |
-| :----------------------------- | :------------------------------------------ |
-| message_types.VoidMessage | None |
-
-##### get
-
-Get a specific role by name.
-
-| Requests | Attributes
-| :---------------------------- | :---------
-| user_messages.GetRoleRequest | name: str, the name of the role.
-
-| Returns | Attributes
-| :---------------------------- | :---------
-| user_messages.Role | name: str, the name of the role.
-| | permissions: list of str, zero or more
-| | permissions associated with the role.
-| | associated_group: str, optional group
-| | associated to the role for automatic sync.
-
-##### update
-
-Updates a role's permissions or associated group. Role names cannot be changed
-once set.
-
-| Requests | Attributes
-| :---------------------------- | :---------
-| user_messages.Role | name: str, the name of the role.
-| | permissions: list of str, zero or more
-| | permissions to add to the role.
-| | associated_group: str, optional group to
-| | associate to the role for automatic sync.
-
-| Returns | Attributes |
-| :----------------------------- | :------------------------------------------ |
-| message_types.VoidMessage | None |
+`create` Create a new role.
+
+
+
+
+
Requests
+
Attributes
+
+
+
user_messages.Role
+
name: str, the name of the role.
+
+
+
+
permissions: list of str, zero or more permissions to add to the role.
+
+
+
associated_group: str, optional group to associate to the role for automatic sync.
+
+
+
+
+
+
+
Returns
+
Attributes
+
+
+
message_types.VoidMessage
+
None
+
+
+
+
+
+
+`get` Get a specific role by name.
+
+
+
+
+
Requests
+
Attributes
+
+
+
user_messages.GetRoleRequest
+
name: str, the name of the role.
+
+
+
+
+
+
+
+
Returns
+
Attributes
+
+
+
user_messages.Role
+
name: str, the name of the role.
+
+
+
+
permissions: list of str, zero or more permissions associated with the role.
+
+
+
associated_group: str, optional group associated to the role for automatic sync.
+
+
+
+
+
+
+`update` Updates a role's permissions or associated group. Role names cannot be
+changed once set.
+
+
+
+
+
Requests
+
Attributes
+
+
+
user_messages.Role
+
name: str, the name of the role.
+
+
+
+
permissions: list of str, zero or more permissions to add to the role.
+
+
+
associated_group: str, optional group to associate to the role for automatic sync.
+
+
+
+
+
+
+
Returns
+
Attributes
+
+
+
message_types.VoidMessage
+
None
+
+
+
+
+
### Search_api
@@ -598,31 +1068,65 @@ API endpoint that handles requests related to search.
#### Methods
-##### clear
-
-Clear the index for a given model (Device or Shelf).
-
-| Requests | Attributes
-| :---------------------------- | :---------
-| search_messages.SearchMessage | model: enum, the model to clear the index of
-| | (Device or Shelf).
-
-| Returns | Attributes |
-| :----------------------------- | :------------------------------------------ |
-| message_types.VoidMessage | None |
-
-##### reindex
-
-Reindex the entities for a given model (Device or Shelf).
-
-| Requests | Attributes
-| :---------------------------- | :---------
-| search_messages.SearchMessage | model: enum, the model to reindex (Device or
-| | Shelf).
-
-| Returns | Attributes |
-| :----------------------------- | :------------------------------------------ |
-| message_types.VoidMessage | None |
+`clear` Clear the index for a given model (Device or Shelf).
+
+
+
+
+
Requests
+
Attributes
+
+
+
search_messages.SearchMessage
+
model: enum, the model to clear the index of (Device or Shelf).
+
+
+
+
+
+
+
+
Returns
+
Attributes
+
+
+
message_types.VoidMessage
+
None
+
+
+
+
+
+
+`reindex` Reindex the entities for a given model (Device or Shelf).
+
+
+
+
+
Requests
+
Attributes
+
+
+
search_messages.SearchMessage
+
model: enum, the model to clear the index of (Device or Shelf).
+
+
+
+
+
+
+
+
Returns
+
Attributes
+
+
+
message_types.VoidMessage
+
None
+
+
+
+
+
### Shelf_api
@@ -630,176 +1134,325 @@ The entry point for the Shelf methods.
#### Methods
-##### audit
-
-Performs an audit on a shelf based on location.
-
-| Requests | Attributes |
-| :---------------------------------- | :------------------------------------- |
-| ShelfAuditRequest ProtoRPC message. | shelf_request: ShelfRequest, A message |
-| | containing the unique identifiers to |
-| | be used when retrieving a shelf. |
-| | device_identifiers: list, A list of |
-| | device serial numbers to perform a |
-| | device audit on. |
-
-Returns | Attributes
-:------------------------ | :---------
-message_types.VoidMessage | None
-
-##### disable
-
-Disable a shelf by its location.
-
-| Requests | Attributes |
-| :--------------------------- | :---------------------------------------- |
-| ShelfRequest | location: str, The location of the shelf. |
-| | urlsafe_key: str, The urlsafe representation |
-| | of a ndb.Key. |
-
-Returns | Attributes
-:------------------------ | :---------
-message_types.VoidMessage | None
-
-##### enroll
-
-Enroll request for the Shelf API.
-
-| Requests | Attributes |
-| :----------------------------------- | :------------------------------------ |
-| EnrollShelfRequest ProtoRPC message. | friendly_name: str, The friendly name |
-| | of the shelf. |
-| | location: str, The location of the |
-| | shelf. |
-| | latitude: float, A geographical point |
-| | represented by floating-point. |
-| | longitude: float, A geographical |
-| | point represented by floating-point. |
-| | altitude: float, Indicates the floor. |
-| | capacity: int, The amount of devices |
-| | a shelf can hold. |
-| | audit_notification_enabled: bool, |
-| | Indicates if an audit is enabled for |
-| | the shelf. |
-| | responsible_for_audit: str, The party |
-| | responsible for audits. |
-
-Returns | Attributes
-:------------------------ | :---------
-message_types.VoidMessage | None
-
-##### get
-
-Get a shelf based on location.
-
-| Requests | Attributes |
-| :--------------------------- | :---------------------------------------- |
-| ShelfRequest | location: str, The location of the shelf. |
-| | urlsafe_key: str, The urlsafe representation |
-| | of a ndb.Key. |
-
-| Returns | Attributes |
-| :---------------------- | :------------------------------------------------- |
-| Shelf ProtoRPC message. | enabled: bool, Indicates if the shelf is enabled |
-| | or not. |
-| | friendly_name: str, The friendly name of the |
-| | shelf. |
-| | location: str, The location of the shelf. |
-| | latitude: float, A geographical point represented |
-| | by floating-point. |
-| | longitude: float, A geographical point represented |
-| | by floating-point. |
-| | altitude: float, Indicates the floor. |
-| | capacity: int, The amount of devices a shelf can |
-| | hold. |
-| | audit_notification_enabled: bool, Indicates if an |
-| | audit is enabled for the shelf. |
-| | audit_requested: bool, Indicates if an audit has |
-| | been requested. |
-| | responsible_for_audit: str, The party responsible |
-| | for audits. |
-| | last_audit_time: datetime, Indicates the last |
-| | audit time. |
-| | last_audit_by: str, Indicates the last user to |
-| | audit the shelf. |
-| | page_token: str, A page token to query next page |
-| | results. |
-| | page_size: int, The number of results to query for |
-| | and display. |
-| | shelf_request: ShelfRequest, A message containing |
-| | the unique identifiers to be used when retrieving a|
-| | shelf. |
-
-##### list
-
-List enabled or all shelves based on any shelf attribute.
-
-| Requests | Attributes |
-| :---------------------- | :------------------------------------------------- |
-| Shelf ProtoRPC message. | enabled: bool, Indicates if the shelf is enabled |
-| | or not. |
-| | friendly_name: str, The friendly name of the |
-| | shelf. |
-| | location: str, The location of the shelf. |
-| | latitude: float, A geographical point represented |
-| | by floating-point. |
-| | longitude: float, A geographical point represented |
-| | by floating-point. |
-| | altitude: float, Indicates the floor. |
-| | capacity: int, The amount of devices a shelf can |
-| | hold. |
-| | audit_notification_enabled: bool, Indicates if an |
-| | audit is enabled for the shelf. |
-| | audit_requested: bool, Indicates if an audit has |
-| | been requested. |
-| | responsible_for_audit: str, The party responsible |
-| | for audits. |
-| | last_audit_time: datetime, Indicates the last |
-| | audit time. |
-| | last_audit_by: str, Indicates the last user to |
-| | audit the shelf. |
-| | page_size: int, The number of results to query for |
-| | and display. |
-| | page_number: int, the page index to offset the |
-| | results |
-| | shelf_request: ShelfRequest, A message containing |
-| | the unique identifier to be used to retrieve the |
-| | shelf. |
-| | query: shared_message.SearchRequest, a message |
-| | containing query options to conduct a search on an |
-| | index. |
-
-| Returns | Attributes |
-| :--------------------------- | :-------------------------------------------- |
-| List Shelf Response ProtoRPC | shelves: Shelf, The list of shelves being |
-| message. | returned. |
-| | total_results: int, the total number of |
-| | results for a query. |
-| | total_pages: int, the total number of pages |
-| | needed to display all of the results. |
-
-##### update
-
-Get a shelf using location to update its properties.
-
-| Requests | Attributes |
-| :----------------------------------- | :------------------------------------ |
-| UpdateShelfRequest ProtoRPC message. | shelf_request: ShelfRequest, A message|
-| | containing the unique identifiers to |
-| | be used when retrieving a shelf. |
-| | friendly_name: str, The friendly name |
-| | of the shelf. |
-| | location: str, The location of the |
-| | shelf. |
-| | latitude: float, A geographical point |
-| | represented by floating-point. |
-| | longitude: float, A geographical |
-| | point represented by floating-point. |
-| | altitude: float, Indicates the floor. |
-
-Returns | Attributes
-:------------------------ | :---------
-message_types.VoidMessage | None
+`audit` Performs an audit on a shelf based on location.
+
+
+
+
+
Requests
+
Attributes
+
+
+
ShelfAuditRequest ProtoRPC message.
+
shelf_request: ShelfRequest, A message containing the unique identifiers to be used when retrieving a shelf.
+
+
+
+
device_identifiers: list, A list of device serial numbers to perform a device audit on.
+
+
+
+
+
+
+
Returns
+
Attributes
+
+
+
message_types.VoidMessage
+
None
+
+
+
+
+
+
+`disable` Disable a shelf by its location.
+
+
+
+
+
Requests
+
Attributes
+
+
+
ShelfRequest
+
location: str, The location of the shelf.
+
+
+
+
urlsafe_key: str, The urlsafe representation of a ndb.Key.
+
+
+
+
+
+
+
Returns
+
Attributes
+
+
+
message_types.VoidMessage
+
None
+
+
+
+
+
+
+`enroll` Enroll request for the Shelf API.
+
+
+
+
+
Requests
+
Attributes
+
+
+
EnrollShelfRequest ProtoRPC message.
+
friendly_name: str, The friendly name of the shelf.
+
+
+
+
location: str, The location of the shelf.
+
+
+
latitude: float, A geographical point represented by floating-point.
+
+
+
latitude: float, A geographical point represented by floating-point.
+
+
+
longitude: float, A geographical point represented by floating-point.
+
+
+
altitude: float, Indicates the floor.
+
+
+
capacity: int, The amount of devices a shelf can hold.
+
+
+
audit_notification_enabled: bool, Indicates if an audit is enabled for the shelf.
+
+
+
responsible_for_audit: str, The party responsible for audits.
+
+
+
+
+
+
+
Returns
+
Attributes
+
+
+
message_types.VoidMessage
+
None
+
+
+
+
+
+
+`get` Get a shelf based on location.
+
+
+
+
+
Requests
+
Attributes
+
+
+
ShelfRequest
+
location: str, The location of the shelf.
+
+
+
+
urlsafe_key: str, The urlsafe representation of a ndb.Key.
+
+
+
+
+
+
+
Returns
+
Attributes
+
+
+
Shelf ProtoRPC message
+
enabled: bool, Indicates if the shelf is enabled or not.
+
+
+
friendly_name: str, The friendly name of the shelf.
+
+
+
location: str, The location of the shelf.
+
+
+
latitude: float, A geographical point represented by floating-point.
+
+
+
longitude: float, A geographical point represented by floating-point.
+
+
+
altitude: float, Indicates the floor.
+
+
+
capacity: int, The amount of devices a shelf can hold.
+
+
+
audit_notification_enabled: bool, Indicates if an audit is enabled for the shelf.
+
+
+
audit_requested: bool, Indicates if an audit has been requested.
+
+
+
responsible_for_audit: str, The party responsible for audits.
+
+
+
last_audit_time: datetime, Indicates the last audit time.
+
+
+
last_audit_by: str, Indicates the last user to audit the shelf.
+
+
+
page_token: str, A page token to query next page results.
+
+
+
page_size: int, The number of results to query for and display.
+
+
+
shelf_request: ShelfRequest, A message containing the unique identifiers to be used when retrieving a shelf.
+
+
+
+
+
+`list` List enabled or all shelves based on any shelf attribute.
+
+
+
+
+
Returns
+
Attributes
+
+
+
Shelf ProtoRPC message
+
enabled: bool, Indicates if the shelf is enabled or not.
+
+
+
friendly_name: str, The friendly name of the shelf.
+
+
+
location: str, The location of the shelf.
+
+
+
latitude: float, A geographical point represented by floating-point.
+
+
+
longitude: float, A geographical point represented by floating-point.
+
+
+
altitude: float, Indicates the floor.
+
+
+
capacity: int, The amount of devices a shelf can hold.
+
+
+
audit_notification_enabled: bool, Indicates if an audit is enabled for the shelf.
+
+
+
audit_requested: bool, Indicates if an audit has been requested.
+
+
+
responsible_for_audit: str, The party responsible for audits.
+
+
+
last_audit_time: datetime, Indicates the last audit time.
+
+
+
last_audit_by: str, Indicates the last user to audit the shelf.
+
+
+
page_token: str, A page token to query next page results.
+
+
+
page_size: int, The number of results to query for and display.
+
+
+
shelf_request: ShelfRequest, A message containing the unique identifiers to be used when retrieving a shelf.
+
+
+
query: shared_message.SearchRequest, a message containing query options to conduct a search on an index.
+
+
+
+
+
+
+
Returns
+
Attributes
+
+
+
List Shelf Response ProtoRPC message.
+
shelves: Shelf, The list of shelves being returned.
+
+
+
+
has_additional_results: bool, If there are more results to be displayed.
+
+
+
page_token: str, A page token that will allow be used to query for additional results.
+
+
+
+
+
+`update` Get a shelf using location to update its properties.
+
+
+
+
+
Requests
+
Attributes
+
+
+
UpdateShelfRequest ProtoRPC message.
+
shelf_request: ShelfRequest, A message containing the unique identifiers to be used when retrieving a shelf.
+
+
+
+
friendly_name: str, The friendly name of the shelf.
+
+
+
location: str, The location of the shelf.
+
+
+
latitude: float, A geographical point represented by floating-point.
+
+
+
longitude: float, A geographical point represented by floating-point.
+
+
+
altitude: float, Indicates the floor.
+
+
+
+
+
+
+
+
Returns
+
Attributes
+
+
+
message_types.VoidMessage
+
None
+
+
+
+
+
### Survey_api
@@ -807,126 +1460,225 @@ The entry point for the Survey methods.
#### Methods
-##### create
-
-Create a new survey and insert instance into datastore.
-
-| Requests | Attributes |
-| :----------------------------------- | :------------------------------------ |
-| Survey ProtoRPC Message to | survey_type: survey_model.SurveyType, |
-| encapsulate the survey_model.Survey. | The type of survey this is. |
-| | question: str, The text displayed as |
-| | the question for this survey. |
-| | enabled: bool, Whether or not this |
-| | survey should be enabled. |
-| | rand_weight: int, The weight to be |
-| | applied to this survey when using the |
-| | get method survey with random. |
-| | answers: List of Answer, The list of |
-| | answers possible for this survey. |
-| | survey_urlsafe_key: str, The |
-| | ndb.Key.urlsafe() for the survey. |
-
-Returns | Attributes
-:------------------------ | :---------
-message_types.VoidMessage | None
-
-##### list
-
-List surveys.
-
-| Requests | Attributes |
-| :---------------------------------- | :------------------------------------ |
-| ListSurveyRequest ProtoRPC Message. | survey_type: survey_model.SurveyType, |
-| | The type of survey to list. |
-| | enabled: bool, True for only |
-| | enabled surveys, False to view |
-| | disabled surveys. |
-| | page_size: int, The size of the |
-| | page to return. |
-| | page_token: str, The urlsafe |
-| | representation of the page token. |
-
-| Returns | Attributes |
-| :--------------------------- | :------------------------------------------- |
-| SurveyList ProtoRPC Message. | surveys: List of Survey, The list of surveys |
-| | to return. |
-| | page_token: str, The urlsafe |
-| | representation of the page token. |
-| | more: bool, Whether or not there are more |
-| | results to be queried. |
-
-##### patch
-
-Patch a given survey.
-
-| Requests | Attributes |
-| :----------------------------------- | :------------------------------------ |
-| PatchSurveyRequest ProtoRPC Message. | survey_urlsafe_key: str, The |
-| | ndb.Key.urlsafe() for the survey. |
-| | answers: List of Answer, The list of |
-| | answers possible for this survey. |
-| | answer_keys_to_remove: List of str, |
-| | The list of answer_urlsafe_key to |
-| | remove from this survey. |
-| | survey_type: survey_model.SurveyType, |
-| | The type of survey this is. |
-| | question: str, The text displayed as |
-| | the question for this survey. |
-| | enabled: bool, Whether or not this |
-| | survey should be enabled. |
-| | rand_weight: int, The weight to be |
-| | applied to this survey when using the |
-| | get method survey with random. |
-
-Returns | Attributes
-:------------------------ | :---------
-message_types.VoidMessage | None
-
-##### request
-
-Request a survey by type and present that survey to a Chrome App user.
-
-| Requests | Attributes |
-| :------------------------------ | :---------------------------------------- |
-| SurveyRequest ProtoRPC Message. | survey_type: survey_model.SurveyType, The |
-| | type of survey being requested. |
-
-| Returns | Attributes |
-| :----------------------------------- | :------------------------------------ |
-| Survey ProtoRPC Message to | survey_type: survey_model.SurveyType, |
-| encapsulate the survey_model.Survey. | The type of survey this is. |
-| | question: str, The text displayed as |
-| | the question for this survey. |
-| | enabled: bool, Whether or not this |
-| | survey should be enabled. |
-| | rand_weight: int, The weight to be |
-| | applied to this survey when using the |
-| | get method survey with random. |
-| | answers: List of Answer, The list of |
-| | answers possible for this survey. |
-| | survey_urlsafe_key: str, The |
-| | ndb.Key.urlsafe() for the survey. |
-
-##### submit
-
-Submit a response to a survey acquired via a request.
-
-| Requests | Attributes |
-| :--------------------------------- | :-------------------------------------- |
-| SurveySubmission ProtoRPC Message. | survey_urlsafe_key: str, The urlsafe |
-| | ndb.Key for a survey_model.Survey |
-| | instance. |
-| | answer_urlsafe_key: str, The urlsafe |
-| | ndb.Key for a survey_model.Answer |
-| | instance. |
-| | more_info: str, the extra info |
-| | optionally provided for the given |
-| | Survey and Answer. |
-
-Returns | Attributes
-:------------------------ | :---------
-message_types.VoidMessage | None
+`create` Create a new survey and insert instance into datastore.
+
+
+
+
+
Requests
+
Attributes
+
+
+
Survey ProtoRPC Message to encapsulate the survey_model.Survey.
+
survey_type: survey_model.SurveyType, The type of survey this is.
+
+
+
question: str, The text displayed as the question for this survey.
+
+
+
+
enabled: bool, Whether or not this survey should be enabled.
+
+
+
rand_weight: int, The weight to be applied to this survey when using the get method survey with random.
+
+
+
answers: List of Answer, The list of answers possible for this survey.
+
+
+
survey_urlsafe_key: str, The ndb.Key.urlsafe() for the survey.
+
+
+
+
+
+
+
Returns
+
Attributes
+
+
+
message_types.VoidMessage
+
None
+
+
+
+
+
+
+`list` List surveys.
+
+
+
+
+
Requests
+
Attributes
+
+
+
ListSurveyRequest ProtoRPC Message.
+
survey_type: survey_model.SurveyType, The type of survey to list.
+
+
+
+
enabled: bool, True for only enabled surveys, False to view disabled surveys.
+
+
+
page_size: int, The size of the page to return.
+
+
+
page_token: str, The urlsafe representation of the page token.
+
+
+
+
+
+
+
Returns
+
Attributes
+
+
+
SurveyList ProtoRPC Message.
+
surveys: List of Survey, The list of surveys to return.
+
+
+
+
page_token: str, The urlsafe representation of the page token.
+
+
+
more: bool, Whether or not there are more results to be queried.
+
+
+
+
+
+`patch` Patch a given survey.
+
+
+
+
+
Requests
+
Attributes
+
+
+
PatchSurveyRequest ProtoRPC Message.
+
survey_urlsafe_key: str, The ndb.Key.urlsafe() for the survey.
+
+
+
+
answers: List of Answer, The list of answers possible for this survey.
+
+
+
answer_keys_to_remove: List of str, The list of answer_urlsafe_key to remove from this survey.
+
+
+
survey_type: survey_model.SurveyType, The type of survey this is.
+
+
+
question: str, The text displayed as the question for this survey.
+
+
+
enabled: bool, Whether or not this survey should be enabled.
+
+
+
rand_weight: int, The weight to be applied to this survey when using the get method survey with random.
+
+
+
+
+
+
+
+
Returns
+
Attributes
+
+
+
message_types.VoidMessage
+
None
+
+
+
+
+
+
+`request`Request a survey by type and present that survey to a Chrome App user.
+
+
+
+
+
Requests
+
Attributes
+
+
+
ListSurveyRequest ProtoRPC Message.
+
survey_type: survey_model.SurveyType, The type of survey to list.
+
+
+
+
+
+
+
+
Returns
+
Attributes
+
+
+
Survey ProtoRPC Message to encapsulate the survey_model.Survey
+
survey_type: survey_model.SurveyType, The type of survey this is.
+
+
+
+
question: str, The text displayed as the question for this survey.
+
+
+
enabled: bool, Whether or not this survey should be enabled.
+
+
+
rand_weight: int, The weight to be applied to this survey when using the get method survey with random.
+
+
+
answers: List of Answer, The list of answers possible for this survey.
+
+
+
survey_urlsafe_key: str, The ndb.Key.urlsafe() for the survey.
+
+
+
+
+
+`submit` Submit a response to a survey acquired via a request.
+
+
+
+
+
Requests
+
Attributes
+
+
+
SurveySubmission ProtoRPC Message.
+
survey_urlsafe_key: str, The urlsafe ndb.Key for a survey_model.Survey instance.
+
+
+
+
answer_urlsafe_key: str, The urlsafe ndb.Key for a survey_model.Answer instance.
+
+
+
more_info: str, the extra info optionally provided for the given Survey and Answer.
+
+
+
+
+
+
+
Returns
+
Attributes
+
+
+
message_types.VoidMessage
+
None
+
+
+
+
+
### Tag_api
@@ -934,51 +1686,178 @@ API endpoint that handles requests related to tags.
#### Methods
-##### create
-
-Create a new tag.
-
-| Requests | Attributes
-| :---------------------------- | :---------
-| tag_messages.CreateTagRequest | tag: tag_messages.Tag, the attributes of a Tag.
-
-Returns | Attributes
-:------------------------ | :---------
-message_types.VoidMessage | None
-
-##### destroy
-
-Destroy a tag.
-
-| Requests | Attributes
-| :----------------------------- | :---------
-| tag_messages.TagRequest | urlsafe_key: str, the urlsafe representation
-| | of the ndb.Key for the tag being requested.
-
-Returns | Attributes
-:------------------------ | :---------
-message_types.VoidMessage | None
-
-##### get
-
-Destroy a tag.
-
-| Requests | Attributes
-| :----------------------------- | :---------
-| tag_messages.TagRequest | urlsafe_key: str, the urlsafe representation
-| | of the ndb.Key for the tag being requested.
-
-Returns | Attributes
-:---------------- | :---------
-tag_messages.Tag | name: str, the unique name of the tag.
- | hidden: bool, whether the tag is hidden in the frontend,
- | defaults to False.
- | color: str, the color of the tag, one of the material
- | design palette.
- | protect: bool, whether the tag is protected from user
- | manipulation; this field will only be included in response
- | messages.
- | description: str, the description for the tag.
+`create` Create a new tag.
+
+
+
+
+
Requests
+
Attributes
+
+
+
tag_messages.CreateTagRequest
+
tag: tag_messages.Tag, the attributes of a Tag.
+
+
+
+
+
+
+
+
Returns
+
Attributes
+
+
+
message_types.VoidMessage
+
None
+
+
+
+
+`destroy` Destroy a tag.
+
+
+
+
+
Requests
+
Attributes
+
+
+
tag_messages.TagRequest
+
urlsafe_key: str, the urlsafe representation of the ndb.Key for the tag being requested.
+
+
+
+
+
+
+
+
Returns
+
Attributes
+
+
+
message_types.VoidMessage
+
None
+
+
+
+
+`get` Get a tag.
+
+
+
+
+
Requests
+
Attributes
+
+
+
tag_messages.TagRequest
+
urlsafe_key: str, the urlsafe representation of the ndb.Key for the tag being requested.
+
+
+
+
+
+
+
+
Returns
+
Attributes
+
+
+
tag_messages.Tag
+
name: str, the unique name of the tag.
+
+
+
+
hidden: bool, whether the tag is hidden in the frontend, defaults to False.
+
+
+
color: str, the color of the tag, one of the material design palette.
+
+
+
protect: bool, whether the tag is protected from user manipulation; this field will only be included in response messages.
+
+
+
description: str, the description for the tag.
+
+
+
+
+
+`update` Updates a tag.
+
+
+
+
+
Requests
+
Attributes
+
+
+
tag_messages.UpdateTagRequest
+
tag: tag_messages.Tag, the attributes of a Tag.
+
+
+
+
+
+
+
+
Returns
+
Attributes
+
+
+
message_types.VoidMessage
+
None
+
+
+
+
+
+
+`list` Lists tags.
+
+
+
+
+
Requests
+
Attributes
+
+
+
tag_messages.ListTagRequest
+
page_size: int, the number of results to return.
+
+
+
+
cursor: str, the base64-encoded cursor string specifying where to start the query.
+
+
+
page_index: int, the page index to offset the results from.
+
+
+
include_hidden_tags: bool, whether to include hidden tags in the results, defaults to False.
+
+
+
+
+
+
+
Returns
+
Attributes
+
+
+
tag_messages.ListTagResponse
+
tags: tag_messages.Tag (repeated), the list of tags being returned.
+
+
+
+
cursor: str, the base64-encoded denoting the position of the last result retrieved. additional results to be retrieved.
+
+
+
+
+
+
+
### User_api
@@ -986,19 +1865,39 @@ API endpoint that handles requests related to users.
#### Methods
-##### get
-
-Get a user object using the logged in user's credential.
-
-| Requests | Attributes
-| :------------------------ | :---------
-| message_types.VoidMessage | None
-
-| Returns | Attributes |
-| :----------------------------- | :------------------------------------------ |
-| UserResponse response for | email: str, The user email to be displayed. |
-| ProtoRPC message. | roles: list of str, The roles of the user to|
-| | be displayed. |
-| | permissions: list of str, The permissions |
-| | the user has. |
-| | superadmin: bool, if the user is superadmin.|
+`get` Get a user object using the logged in user's credential.
+
+
+
+
+
Returns
+
Attributes
+
+
+
message_types.VoidMessage
+
None
+
+
+
+
+
+
+
+
Returns
+
Attributes
+
+
+
UserResponse response for ProtoRPC message.
+
email: str, The user email to be displayed.
+
+
+
+
roles: list of str, The roles of the user to be displayed.
+
+
+
permissions: list of str, The permissions the user has.
+
+
+
superadmin: bool, if the user is superadmin.
+
+
diff --git a/docs/gngsetup_part1.md b/docs/gngsetup_part1.md
new file mode 100644
index 00000000..a0627ba3
--- /dev/null
+++ b/docs/gngsetup_part1.md
@@ -0,0 +1,131 @@
+# Grab n Go Setup Part 1: Create necessary accounts and computer environments
+
+
+As you go through this guide, you may find that you already have some of these
+prequisites in place, like a G Suite account for your company. If this is the
+case, you can skip to the next relevant step.
+
+
+
+## Step 1: Get G Suite and Chrome for Enterprise
+
++ [Get a G Suite account for your company](https://gsuite.google.com/intl/en_in/setup-hub/)
+
+ Logging into a loaner Chromebook requires a Google G Suite account, standard
+ Gmail accounts won't work.
+
++ [Get Chrome for Enterprise](https://cloud.google.com/chrome-enterprise/)
+
+## Step 2: Set up an App Engine Project in Google Cloud
+
+GnG runs on Google App Engine, an automatically scaling, sandboxed computing
+environment that runs on Google Cloud.
+
+1. [Create a Google Cloud Platform Project](https://cloud.google.com/resource-manager/docs/creating-managing-projects).
+
+ Name the project something you will remember, such as *loaner*.
+
+1. [Create a billing account](https://cloud.google.com/billing/docs/how-to/manage-billing-account)
+ and
+ [enable billing for the project](https://cloud.google.com/billing/docs/how-to/modify-project)
+ that you created.
+
+1. [Create an OAuth2 Client ID within your App Engine Project](https://cloud.google.com/endpoints/docs/frameworks/python/creating-client-ids#Creating_OAuth_20_client_IDs)
+ (make sure to select the **Web Client** instructons tab).
+
+ For secure authentication, the GnG application uses OAuth2. When you create
+ the OAuth2 Client ID, for:
+
+ + Authorized JavaScript Origins URL, use either:
+
+ + [Your own custom domain](https://cloud.google.com/appengine/docs/standard/python/mapping-custom-domains)
+ OR
+
+ + Your GCP project ID (found in the project dropdown) followed by
+ appspot.com
+
+ For example, if your GCP project ID is "example-123456" then the
+ default URL will be https://example-123456.appspot.com
+
+ + Application type: Select **Public**
+
+1. [Create a service account its credentials on your G Suite Domain](https://developers.google.com/admin-sdk/directory/v1/guides/delegation)
+ (You can leave **Role** blank).
+
+ This is required in order to access the G Suite APIs to move devices to and
+ from organizational units, maintain permissions based on Google Groups, etc.
+
+ **When you get the JSON file containing the client secrets for the service
+ account,** save it somewhere that you'll be able to find and don't share it
+ as it allows access to your G Suite domain user data.
+
+1. [Delegate domain-wide authority to the service account you created](https://developers.google.com/admin-sdk/directory/v1/guides/delegation).
+
+ In the **One or More API Scopes** field, copy and paste the following list
+ of scopes required by GNG:
+
+ `https://www.googleapis.com/auth/admin.directory.device.chromeos,
+ https://www.googleapis.com/auth/admin.directory.group.member.readonly,
+ https://www.googleapis.com/auth/admin.directory.orgunit,
+ https://www.googleapis.com/auth/admin.directory.user.readonly`
+
+1. [**Enable** the Admin SDK API through Google Cloud Console](https://console.developers.google.com/apis/api/admin.googleapis.com/overview).
+
+ GnG requires the Directory API to manage devices in your G Suite Domain. To
+ access the Directory API you need to enable the Admin SDK API.
+
+1. **Optional:** Follow these instructions (Step 2) again to create Google
+ Cloup App Engine projects for DEV and QA instances. It's useful to have
+ separate development and QA apps for testing. For steps 2.2 and 2.4, use the
+ same accounts that you set up for Prod.
+
+## Step 3: Set up a G Suite role account
+
+In order to give the GnG app domain privileges, you must set up a G Suite role
+account for the app to use. This account won't require an additional G Suite
+license and will act only as a proxy for the application.
+
+1. Visit [Google Admin](https://admin.google.com/)
+ + **Name** it something memorable like *loaner-role@example.com*
+ + Set the **password** to something highly complex (a human should never
+ log into this account)
+ + It is highly recommended that you also **use 2FA** on this account to
+ reduce risk
+2. Give the account the following Admin roles:
+ + Directory Admin
+ + Services Admin
+ + User Management Admin
+
+**Note:** It's recommended that you put this account in an
+[Organizational Unit](https://en.wikipedia.org/wiki/Organizational_unit_\(computing\))
+that has all G Suite and additional services disabled.
+
+## Step 4: Create a superadmins permission group
+
+In order to set up the GnG application, you'll need to create a superadmin
+group, which will have all permissions by default.
+
+1. [Create a Google Group for superadmins](https://support.google.com/groups/answer/2464926?hl=en).
+1. Add yourself to the superadmin group. This is required for you to be able to
+ set up the GnG application.
+1. If you have people in your organization that need to manage GnG loaner
+ devices and shelves, add them to the superadmin group.
+
+ Remember the name of this group, as you'll need this later on in the setup.
+
+Additional roles can be created by calling the role API with a custom set of
+permissions, depending on what access you'd like to give. You can provide
+different Google Groups to manage the users in these roles and they will sync
+automatically. You can also manually add users to roles if you do not provide a
+group. Just
+[add the appropriate users to each group](https://support.google.com/groups/answer/2465464?hl=en&ref_topic=2458761).
+
+## Step 5: Enterprise enroll your Chromebooks
+
+You must
+[enterprise enroll](https://support.google.com/chrome/a/answer/1360534?hl=en)
+each of your Chromebook loaners.
+
+## Next up:
+
+### [GnG Setup Part 2: Set up the GnG web app](gngsetup_part2.md)
diff --git a/docs/gngsetup_part2.md b/docs/gngsetup_part2.md
new file mode 100644
index 00000000..4296d40d
--- /dev/null
+++ b/docs/gngsetup_part2.md
@@ -0,0 +1,259 @@
+# Grab n Go Setup Part 2: Set up the GnG web app
+
+
+
+
+## About
+
+The Grab n Go (GnG) web app makes it easy to manage a fleet of loaner Chromebook
+devices. Using GnG, users can self-checkout a loaner Chromebook and begin using
+it right away, thereby decreasing the workload on IT support while keeping users
+productive.
+
+## Step 1: Set up a development computer
+
+This computer will be the device that you'll modify the code, and build and
+upload GnG from.
+
+**Note:** This deployment has only been tested on Linux and macOS.
+
+**Warning:** You must install Bazel 0.26 or earlier as this configuration is
+incompatible with Bazel 0.27 and later.
+
+Install the following software:
+
++ [Git](https://git-scm.com/downloads)
++ [Bazel 0.26.1](https://github.com/bazelbuild/bazel/releases/tag/0.26.1)
++ [Google Cloud SDK](https://cloud.google.com/sdk/)
++ [NPM](https://www.npmjs.com/get-npm)
+
+## Step 2: Clone the GnG loaner source code
+
+Clone the GnG loaner source code by running the command for the current release,
+found in the
+[Current Release section of the README](README.md).
+
+**Note:** This setup guide assumes that your working directory is the root of
+the Git repository.
+
+## Step 3: Customize the App Deployment Script
+
+1. In `loaner/deployments/deploy.sh`, find `PROD="prod=loaner-prod"` and
+ replace `loaner-prod` with the Google Cloud Project ID you created.
+1. If you created multiple projects for QA and DEV, replace the `loaner-dev`
+ and `loner-qa` with the relevant Google Cloud Project IDs.
+
+## Step 4: Customize the BUILD Rule for deployment
+
+1. Find the client secret file for the service account you created earlier and
+ rename it `client-secret.json`
+1. Move `client-secret.json` into `loaner/web_app`
+
+ If you are using Cloud Shell or a remote computer, you can copy and paste
+ the contents of the file.
+
+1. In `loaner/web_app/BUILD`, update following section of code:
+
+```
+ loaner_appengine_library(
+ name = "loaner",
+ data = ["client-secret.json"], # Add this line.
+ deps = [
+ ":chrome_api",
+ ":endpoints_api",
+ ":main",
+ "//loaner/web_app/backend",
+ ],
+ )
+```
+
+## Step 5: Customize the App Constants
+
+Constants are variables you typically define once. For a constant to take
+effect, you must deploy a new version of the app. Constants can’t be configured
+in a running app. Instead, they must be set manually in
+`loaner/web_app/constants.py` and `loaner/shared/config.ts`.
+
+1. In `loaner/web_app/constants.py`, configure the following constants:
+
+ + `APP_DOMAINS`: Use the Google domain that you run G Suite with Chrome
+ Enterprise. You can add a list of other domains that you would like to
+ have access to this deployment of Grab n Go, but they must be listed
+ after the Google domain that you run G Suite with Chrome Enterprise.
+
+ **If you'd like to run this program on more than one domain**, see the
+ "Multi-domain Support" section at the bottom of this doc.
+
+ + `ON_PROD`: Replace the string `prod-app-engine-project` with the Google
+ Cloud Project ID the production version of GnG will run in.
+
+ + `ADMIN_EMAIL`: Use the email address of the G Suite role account you set
+ up.
+
+ + `SEND_EMAIL_AS`: Use the email address within the G Suite Domain that
+ you want GnG app email notifications to be sent from.
+
+ + `SUPERADMINS_GROUP`: Use the Google Groups email address that contains
+ at least one Superadmin in charge of configuring the app.
+
+ + `WEB_CLIENT_ID`: Use the OAuth2 Client ID you created previously for the
+ production version of GnG. In your Cloud Project, this can be found in
+ **APIs and Services > Credentials**.
+
+ + `SECRETS_FILE`: Set this equal to `loaner/web_app/client-secret.json`
+
+ The remaining ON_QA and ON_DEV are only required if you choose to use
+ multiple versions to test deployments before promoting them to the
+ production version.
+
+ + **Optional:** `CUSTOMER_ID`: Use the unique ID for your organization's G
+ Suite account, which GnG uses to access Google's Directory API. If this
+ is not configured the app will use the helper string `my_customer` which
+ will default to the G Suite domain the app is running in.
+
+1. In `loaner/shared/config.ts`, configure the following:
+
+ + `Export const PROD`: Replace `'prod-app-engine-project'` with the Google
+ Cloud Project ID the production version of GnG will run in.
+
+ + `WEB_CLIENT_IDS`: Use the OAuth2 Client ID you created previously. In
+ your Cloud Project, this can be found in **APIs and Services >
+ Credentials**. If you are deploying a single instance of the
+ application, fill in the PROD value with the Client ID.
+
+ + `STANDARD_ENDPOINTS`: If you're using a custom Domain, replace the URL
+ with your domain. Other you can leave this as is. If you are deploying a
+ single instance of the application, use that value for all fields.
+ Otherwise, specify your separate prod, qa and dev endpoint URLs.
+
+## Step 6: Build and Deploy
+
+1. Go to the `loaner/` directory and launch the GnG deployment script:
+
+ ```
+ cd loaner
+ bash deployments/deploy.sh web prod
+ ```
+
+ **Note**: If you are running `deploy.sh` on Linux, you may need to install
+ `node-sass` using `npm` manually using the following command:
+
+ ```
+ npm install --unsafe-perm node-sass
+ ```
+
+ This command builds the GnG web application GnG and deploys it to prod using
+ gcloud.
+
+ The `deploy.sh` script also includes other options:
+
+ ```
+ bash deployments/deploy.sh (web|chrome) (local|dev|qa|prod)
+ ```
+
+1. App Engine's SDK provides an app named `dev_appserver.py` that you can use
+ to test the app on your local development machine. To do so, build the app
+ manually and then use `dev_appserver.py` from the output directory like so:
+
+ ```
+ bash loaner/deployments/deploy.sh web local
+ cd ../bazel-bin/loaner/web_app/runfiles.runfiles/gng/
+ dev_appserver.py app.yaml
+ ```
+
+## Step 7: Confirm that GnG is Running
+
+In the Cloud Console under _App Engine > Versions_ the GnG code that you just
+built and pushed should appear.
+
+To display all four services, click the _Service_ drop-down menu:
+
++ **`default`** is the main service, which interacts with the web frontend
++ **`action-system`** runs the cron jobs that spawn Custom and Reminder events
+ and process the resulting Action tasks
++ **`chrome`** is the service that handles heartbeats from the Chrome app
++ **`endpoints`** handles API requests via Cloud endpoints for all API clients
+ except Chrome app heartbeats
+
+## Step 8: Bootstrapping
+
+The first time you visit the GnG Web app you will be prompted to bootstrap the
+application. You can only do this if you're a technical administrator, so make
+sure you've added your account to the correct group `technical-admins` group you
+defined previously. Bootstrapping the app will set up the default configurations
+and initialize the connection to [BigQuery](https://cloud.google.com/bigquery/)
+and the [Directory API](https://developers.google.com/admin-sdk/directory/).
+After the app has been successfully bootstrapped, make sure to edit the
+`constants.py` file and set `bootstrap_enabled` to `False` so that you don't
+accidentally overwrite your configuration.
+
+**Note**: The bootstrap process may take a few minutes to complete.
+
+#### Create an Authorized Email Sender
+
+You need to configure an authorized email sender that GnG emails will be sent
+from, e.g. loaner@example.com. To do that, add an
+[Email API Authorized Senders](https://console.cloud.google.com/appengine/settings)
+in the GCP Console.
+
+#### Deploy the Chrome App
+
+After bootstrapping is complete, you will need to set up the GnG Chrome App.
+This app helps configure the Chromebooks you will be using as loaners and
+provides the bulk of the user-facing experience. Continue on to
+[deploying the chrome app](gngsetup_part3.md).
+
+#### Multi-domain Support (Optional).
+
+WARNING: This functionality is considered unstable. Use with caution and report
+any bugs using GitHub's issue tracker.
+
+If you want to support more than one managed domain on loaner devices please
+follow the steps below. Please note, the domains you want to support must be
+part of the same G Suite account and added to admin.google.com via Account >
+Domains > Add/Remove Domains. Different domains managed by different G Suite
+accounts and public Gmail addresses are not supported.
+
++ The domains you want to support must be added to the App Engine project from
+ console.cloud.google.com via App Engine > Settings > Custom Domains.
++ In App Engine > Settings > Application settings "Referrers" must be set to
+ Google Accounts API. WARNING: Setting this allows any Google managed account
+ to try and sign into the app. Make sure you have the latest version of the
+ code deployed or you could be exposing the app publicly.
++ In the application's code in web_app/constants.py the variable APP_DOMAINS
+ should be a list of all the domains you plan on supporting.
++ Go to admin.google.com and in Devices > Chrome Management > Device Settings
+ find the Grab n Go parent OU and set Sign-in Restriction to the list of
+ domains you're supporting. Optionally, you may also want to switch off the
+ Autocomplete Domain option as it may cause some confusion (it's not very
+ intuitive that you can override the sign-in screen by typing your full email
+ address).
+
+#### Datastore backups (Optional, but Recommended).
+
+A cron is used to schedule an automatic export of the Google Cloud Datastore
+entities for backup purposes. The export is done directly to a Google Cloud
+Storage bucket.
+
+Requirements:
+
++ [Create a Cloud Storage bucket for your project](https://cloud.google.com/storage/docs/creating-buckets).
+ All exports and imports rely on Cloud Storage. You must use the same
+ location for your Cloud Storage bucket and Cloud Datastore. For example, if
+ you chose your Project location to be US, make sure that same location is
+ chosen when creating the bucket.
++ [Configure access permissions](https://cloud.google.com/datastore/docs/schedule-export#setting_up_scheduled_exports)
+ for the default service account and the Cloud Storage bucket created above.
++ Enter the name of the bucket in the configuration page of the application.
++ Toggle datastore backups to on in the configuration page of the application.
+
+NOTE: Please review the
+[Object Lifecycle Management](https://cloud.google.com/storage/docs/lifecycle)
+feature of Cloud Storage buckets in order to get familiar with retention
+policies. For example, policies can be set on GCS buckets such that objects can
+be deleted after a specified interval. This is to avoid additional costs
+associated with Cloud Storage.
+
+## Next up:
+
+### [Grab n Go Setup Part 3: Deploy the Grab n Go Chrome app](gngsetup_part3.md)
diff --git a/docs/deploy_chrome_app.md b/docs/gngsetup_part3.md
similarity index 82%
rename from docs/deploy_chrome_app.md
rename to docs/gngsetup_part3.md
index a7a5bd5a..2480dab0 100644
--- a/docs/deploy_chrome_app.md
+++ b/docs/gngsetup_part3.md
@@ -1,4 +1,4 @@
-# Deploy the Grab n Go Chrome App
+# Grab n Go Setup Part 3: Deploy the Grab n Go Chrome app
@@ -77,10 +77,10 @@ initialize it. Below are the steps you will need to complete.
1. Zip the `dist` folder and save it somewhere you can easily access. It must
be a zip file (other archive formats are not recognized).
-1. Go to the [Chrome Web Store Developer
- Dashboard](https://chrome.google.com/webstore/developer/dashboard). You may
- see a warning that you have to pay an access fee ($5 at the time of this
- writing), but you can safely disregard this message.
+1. Go to the
+ [Chrome Web Store Developer Dashboard](https://chrome.google.com/webstore/developer/dashboard).
+ You may see a warning that you have to pay an access fee ($5 at the time of
+ this writing), but you can safely disregard this message.
1. Click **Add New Item**.
@@ -116,9 +116,9 @@ initialize it. Below are the steps you will need to complete.
## Step 3: Keying the Chrome App
-1. Return to the [Chrome Web Store Developer
- Dashboard](https://chrome.google.com/webstore/developer/dashboard) and click
- **More Info on your application**.
+1. Return to the
+ [Chrome Web Store Developer Dashboard](https://chrome.google.com/webstore/developer/dashboard)
+ and click **More Info on your application**.
1. Copy the contents below the "BEGIN PUBLIC KEY" and above the "END PUBLIC
KEY" comments.
@@ -211,24 +211,26 @@ To define the OAuth client:
`False` will prevent unexpected bootstraps in the future (a bootstrap will
cause data loss).
-**NOTE:** You will need to migrate all of your traffic in the [App
-Engine>Versions](https://console.cloud.google.com/appengine/versions) menu to
-the newest version every time you re-deploy the app. You can do so by selecting
-the new version's checkbox, and clicking **Migrate Traffic** for each service
-(i.e. default, action system, chrome, endpoints). Once you are certain the new
-version is working properly, you can delete the old one(s) to save resources.
+**NOTE:** You will need to migrate all of your traffic in the
+[App Engine>Versions](https://console.cloud.google.com/appengine/versions) menu
+to the newest version every time you re-deploy the app. You can do so by
+selecting the new version's checkbox, and clicking **Migrate Traffic** for each
+service (i.e. default, action system, chrome, endpoints). Once you are certain
+the new version is working properly, you can delete the old one(s) to save
+resources.
Your Chrome App can now use OAuth to communicate with the API.
## Step 5: Whitelist the API to Bypass OAuth Prompts
-To avoid prompting users to grant access to their email addresses, whitelist
-the API client and the scopes. It is important to do this for your domain
-because if the Chrome App cannot collect email addresses automatically, it will
-not be able to assign devices.
+To avoid prompting users to grant access to their email addresses, whitelist the
+API client and the scopes. It is important to do this for your domain because if
+the Chrome App cannot collect email addresses automatically, it will not be able
+to assign devices.
-1. Open the [Manage API client
- access](https://admin.google.com/ManageOauthClients) menu in Google Admin.
+1. Open the
+ [Manage API client access](https://admin.google.com/ManageOauthClients) menu
+ in Google Admin.
1. Paste the Client ID you generated in the previous section as the Client
Name.
1. For scope, use:
@@ -245,8 +247,8 @@ We'll now update the API URL to allow the Chrome App to communicate with the
backend. Additionally, we will set the troubleshooting information for the
Chrome App.
-**View of the default troubleshooting page for the Chrome App:** 
+**View of the default troubleshooting page for the Chrome App:**
+
Edit the configuration file:
@@ -278,15 +280,15 @@ Edit the configuration file:
The Chrome App's management view allows for FAQ to be displayed in the FAQ tab.
The Chrome App will use the standard markdown format to display these FAQs.
-**View of the default FAQ page for the Chrome App:** 
+**View of the default FAQ page for the Chrome App:**
+
To add content to the FAQ section:
1. You can edit the contents of our provided `chrome_app/src/app/assets/faq.md`
- file using [the markdown
- format](https://guides.github.com/features/mastering-markdown/) to what you
- want to see in your FAQ tab in the Chrome App.
+ file using
+ [the markdown format](https://guides.github.com/features/mastering-markdown/)
+ to what you want to see in your FAQ tab in the Chrome App.
* If you want some examples of how the FAQ works on the Chrome App, you
can visit the included `chrome_app/src/app/assets/faq.md` file and look
@@ -315,20 +317,19 @@ To deploy the Chrome App:
Chrome Web Store Developer Dashboard. For example, if the current Chrome Web
Store version is 0.0.1, the new version would be 0.0.2.
-1. When the app is finished building, open the app on the [Web Store Developer
- Dashboard](https://chrome.google.com/webstore/developer/dashboard) and click
- **Edit**, then click **Upload Updated Package** to upload the zip folder
- (loaner_chrome_app.zip) that was just created in the root of the workspace.
- Once the file has uploaded, click **Publish changes** all the way at the
- bottom right in the edit page. Whenever you deploy a Chrome App, you need to
- promote it to the current version on the Chrome Web Store Developer
+1. When the app is finished building, open the app on the
+ [Web Store Developer Dashboard](https://chrome.google.com/webstore/developer/dashboard)
+ and click **Edit**, then click **Upload Updated Package** to upload the zip
+ folder (loaner_chrome_app.zip) that was just created in the root of the
+ workspace. Once the file has uploaded, click **Publish changes** all the way
+ at the bottom right in the edit page. Whenever you deploy a Chrome App, you
+ need to promote it to the current version on the Chrome Web Store Developer
Dashboard.
**NOTE**: When publishing Chrome Apps, allow at least 30 minutes for the new
version to become available. In addition, it can take up to 24 hours for the
Chrome App to be updated on your Chrome OS fleet.
-## Step 9: Deploy Chrome App
+## Next up:
-In order to deploy the Chrome App to your Chrome OS fleet, continue to follow
-the instructions at: [Configure the G Suite Environment](gsuite_config.md).
+### [GnG Setup Part 4: Configure the G Suite Environment](gngsetup_part4.md)
diff --git a/docs/gngsetup_part4.md b/docs/gngsetup_part4.md
new file mode 100644
index 00000000..70810d6f
--- /dev/null
+++ b/docs/gngsetup_part4.md
@@ -0,0 +1,244 @@
+# Grab n Go Setup Part 4: Configure the G Suite Environment
+
+
+
+
+## Set Up G Suite
+
+### Create a G Suite Account
+
+To manage, configure, and use Chrome OS devices in an enterprise setting, you
+must have a [G Suite] account. To set up and configure a G Suite account, see
+[G Suite Sign-Up Help].
+
+### [Configure the Organizational Unit](https://support.google.com/a/answer/182537?hl=en)
+
+When setting up GnG, it's highly recommended that you separate device management
+into discrete organizational units (OU) from the root organizational unit (the
+figure below displays the Google organizational unit configuration). Doing so
+enables you to:
+
+* Manage your GnG fleet separately from all the other Chrome OS devices
+ managed by your enterprise.
+
+* Enable or disable guest mode on devices in the loaner program. Disabled
+ devices are moved to a separate OU.
+
+
+
+NOTE: Creating either a device or user OU is fine as the OUs will be able to be
+used for both user and device management.
+
+## GnG Organizational Unit Settings
+
+Lets go ahead and modif some of the setting in our newly created Grab n Go OU.
+
+1. Go to the [Admin Console](http://admin.google.com).
+
+1. Open Device Management.
+
+1. Under Device Settings, click Chrome Management.
+
+### User Settings
+
+Setting | Description
+----------------------------------- | --------------------------------------
+**Enrollment Controls** |
+Enrollment Permissions (default) | Allow users in this organization to
+ | enroll new or deprovisioned devices.
+ | Should the device be wiped, this
+ | policy automatically forces the device
+ | to re-enroll to the domain. For
+ | convenience and a better user
+ | experience, all domain users have the
+ | ability to enroll the device. This
+ | saves time since users don't need
+ | technical assistance.
+**Apps and Extensions** |
+Force-installed Apps and Extensions | Use this mechanism to install a loaner
+ | companion app. This Chrome OS app is
+ | pushed to all users in the domain, but
+ | the app only reports those devices
+ | already enrolled in the GnG loaner OU.
+
+### Device Settings
+
+Setting | Description
+--------------------------- | --------------------------------------
+**Enrollment & Access** |
+Forces Re-enrollment | To prevent device theft, set this
+ | configuration to *Force device to*
+ | *re-enroll into this domain after*
+ | *wiping*. Use a key combination to
+ | easily wipe a Chrome OS device. When a
+ | device is wiped, force the user to
+ | re-enroll the device to the domain so
+ | that you can reset any custom
+ | policies. If not, a user could wipe
+ | the device and use it at will.
+Verified Access | Ensures that all executed code comes
+ | from the Chromium OS source tree
+ | rather than from an attacker,
+ | corruption, or other untrusted source.
+ | Setting this to *Enabled for*
+ | *Enterprise Extensions* and *Enabled*
+ | *for Content Protections* ensures that
+ | Chrome OS devices verify their
+ | identity to content providers using a
+ | unique key provided by the device’s
+ | TPM. If either are disabled, Chrome OS
+ | extensions can't interact with the
+ | device's TPM.
+Verified Mode | Require verified mode boot for
+ | Verified Access. For device
+ | verification to succeed, a device must
+ | be running in verified boot mode.
+ | Devices in dev mode always fail the
+ | Verified Access check. Having a fleet
+ | of Chrome OS devices in dev mode is
+ | insecure and highly unstable. Disabled
+ | device return instruction
+**Sign-in Settings** |
+Guest Mode | Google does not allow guest mode for
+ | loaner Chromebooks. If guest mode is
+ | enabled, a user could pick up a loaner
+ | device and use it without being signed
+ | in. We rely on login information to
+ | retroactively assign Chromebooks. If
+ | devices are manually assigned at the
+ | time of distribution, then this risk
+ | is not present. Note: The GnG program
+ | uses a Guest mode OU that has Guest
+ | Mode enabled. When a GnG customer opts
+ | into guest mode, we automatically move
+ | them to the Guest OU for a fixed
+ | duration of time. This opt-in happens
+ | after we have recorded their sign-in
+ | and use of the device.
+Sign-in Restriction | Restrict the sign-in to domain users
+ | only. For example, @example.com. Doing
+ | so allows only domain users to log in
+ | to the device (no @gmail.com login).
+Autocomplete Domain | Use the domain name, set Sign-in
+ | Restriction, for autocomplete. (This
+ | must be the domain name for the
+ | account).
+Sign-in Screen | Set to *Never show user names and*
+ | *photos*. This setting is important if
+ | you chose to not erase user data upon
+ | logout. If not set this way, the
+ | usernames of previous users and photos
+ | will be present. Some users may be
+ | unsettled by the thought that their
+ | data remains on the loaner device
+ | after they've used it. It's important
+ | to note that all user data is
+ | encrypted by default on Chrome
+ | devices. So while it may be
+ | uncomfortable for some users that
+ | previous user data is on the device-
+ | it poses virtually no security risk.
+User Data | Set to *Erase all local user data*.
+ | Related to the note above, since this
+ | will be a loaner device, you don't
+ | want to leave the data of other users
+ | behind, even if that data is
+ | encrypted. At times, Google found that
+ | this data wipe prevented reporting
+ | check-ins. If a loaner was used for a
+ | short duration, usage data could be
+ | wiped before there was a chance to
+ | autonomously account for it.
+Sign-In Language | Set to company primary language. This
+ | is to prevent a user from changing
+ | language and then the user that
+ | follows not knowing the language.
+**Device Update Settings** |
+Auto Update Settings | Set to Allow auto-updates. For
+ | security reasons, perform all updates
+ | on the device.
+Auto Reboot After Updates | Set to Allow auto-reboots. Chrome OS
+ | requires a reboot to apply the latest
+ | downloaded update. Auto-reboot helps
+ | you to install the updates without
+ | human intervention. When Allow
+ | auto-reboots is selected, after a
+ | successful auto update, the Chrome
+ | device will reboot when the user next
+ | signs out.
+Release Channel | Set to Move to Stable Channel. For
+ | optimal stability, all devices must be
+ | set to Move to Stable Channel.
+**Kiosk Settings** |
+Public Session Kiosk | Set to Do not allow Public Session
+ | Kiosk. Public Session Kiosks do not
+ | use an account for logging in to the
+ | device. Without having an account that
+ | is logged into the device, the device
+ | can't be assigned to a user.
+**User & Device Reporting** |
+Device Reporting | Enable device state reporting and
+ | enable tracking recent device users.
+ | Google uses this data to record who is
+ | using a device and when. This data is
+ | also facilitates communication with
+ | GnG customers.
+**Power & Shutdown** |
+Power Management | Set to Allow device to sleep/shut down
+ | when idle on the sign in screen. When
+ | the device is not in use, allowing it
+ | to sleep/shutdown maximizes battery
+ | life.
+
+## Device Enterprise Enrollment
+
+All Chrome OS devices must be enterprise enrolled so that your organization can
+enforce policies on each device. A manual enrollment process is required for
+every device.
+
+## [How to Enterprise Enroll a Device](https://support.google.com/chrome/a/answer/1360534?hl=en)
+
+## Force install Chrome App by OU
+
+Set up the application to be pushed to all Chromebooks in your domain.
+
+If a device is not already [enterprise enrolled], the application will disable
+itself and become dormant.
+
+To create an installation policy:
+
+1. Go to the [Admin Console](http://admin.google.com).
+
+1. Open Device Management.
+
+1. Under Device Settings, click Chrome Management.
+
+1. Click App Management.
+
+1. Under Filters, find the label named Type and change the type to Domain Apps.
+
+ The names of all domain apps available to your domain are listed.
+
+1. Find the loaner application that you deployed previously and click on the
+ name.
+
+1. Click User Settings.
+
+1. Find the OU that your users belong to (most likely the parent OU, for
+ example, example.com) and click on it.
+
+1. For that OU, you can now configure how the application is to be deployed.
+ The following configuration is recommended:
+
+ * Allow Installation: Disabled (to prevent users from manually installing
+ the application)
+
+ * Force Installation: Enabled (everyone can install the application — this
+ option is required for the application to open upon log-in)
+
+ * Pin to Taskbar: Enabled (pins the application to the bottom taskbar)
+
+1. Click Save.
+
+[G Suite]: https://gsuite.google.com/
+[G Suite Sign-Up Help]: https://docs.google.com/document/d/1qUpgVzCttLiZJ-s5nhXEfuFdHBGNWjPvNRK40pcU9m0/edit#heading=h.5xt9ofon499z
diff --git a/docs/gsuite_config.md b/docs/gsuite_config.md
deleted file mode 100644
index fdf19f7e..00000000
--- a/docs/gsuite_config.md
+++ /dev/null
@@ -1,244 +0,0 @@
-# Configure the G Suite Environment
-
-
-
-
-## Set Up G Suite
-
-### Create a G Suite Account
-
-To manage, configure, and use Chrome OS devices in an enterprise setting, you
-must have a [G Suite] account. To set up and configure a G Suite account, see [G
-Suite Sign-Up Help].
-
-### [Configure the Organizational Unit](https://support.google.com/a/answer/182537?hl=en)
-
-When setting up GnG, it's highly recommended that you separate device management
-into discrete organizational units (OU) from the root organizational unit (the
-figure below displays the Google organizational unit configuration). Doing so
-enables you to:
-
-* Manage your GnG fleet separately from all the other Chrome OS devices
- managed by your enterprise.
-
-* Enable or disable guest mode on devices in the loaner program. Disabled
- devices are moved to a separate OU.
-
-{width="450"}
-
-NOTE: Creating either a device or user OU is fine as the OUs will be able to be
-used for both user and device management.
-
-## GnG Organizational Unit Settings
-
-Lets go ahead and modif some of the setting in our newly created Grab n Go OU.
-
-1. Go to the [Admin Console](http://admin.google.com).
-
-1. Open Device Management.
-
-1. Under Device Settings, click Chrome Management.
-
-### User Settings
-
-| Setting | Description |
-| ----------------------------------- | -------------------------------------- |
-| **Enrollment Controls** | |
-| Enrollment Permissions (default) | Allow users in this organization to |
-| | enroll new or deprovisioned devices. |
-| | Should the device be wiped, this |
-| | policy automatically forces the device |
-| | to re-enroll to the domain. For |
-| | convenience and a better user |
-| | experience, all domain users have the |
-| | ability to enroll the device. This |
-| | saves time since users don't need |
-| | technical assistance. |
-| **Apps and Extensions** | |
-| Force-installed Apps and Extensions | Use this mechanism to install a loaner |
-| | companion app. This Chrome OS app is |
-| | pushed to all users in the domain, but |
-| | the app only reports those devices |
-| | already enrolled in the GnG loaner OU. |
-
-### Device Settings
-
-| Setting | Description |
-| ----------------------------------- | -------------------------------------- |
-| **Enrollment & Access** | |
-| Forces Re-enrollment | To prevent device theft, set this |
-| | configuration to *Force device to* |
-| | *re-enroll into this domain after* |
-| | *wiping*. Use a key combination to |
-| | easily wipe a Chrome OS device. When a |
-| | device is wiped, force the user to |
-| | re-enroll the device to the domain so |
-| | that you can reset any custom |
-| | policies. If not, a user could wipe |
-| | the device and use it at will. |
-| Verified Access | Ensures that all executed code comes |
-| | from the Chromium OS source tree |
-| | rather than from an attacker, |
-| | corruption, or other untrusted source. |
-| | Setting this to *Enabled for* |
-| | *Enterprise Extensions* and *Enabled* |
-| | *for Content Protections* ensures that |
-| | Chrome OS devices verify their |
-| | identity to content providers using a |
-| | unique key provided by the device’s |
-| | TPM. If either are disabled, Chrome OS |
-| | extensions can't interact with the |
-| | device's TPM. |
-| Verified Mode | Require verified mode boot for |
-| | Verified Access. For device |
-| | verification to succeed, a device must |
-| | be running in verified boot mode. |
-| | Devices in dev mode always fail the |
-| | Verified Access check. Having a fleet |
-| | of Chrome OS devices in dev mode is |
-| | insecure and highly unstable. Disabled |
-| | device return instruction |
-| **Sign-in Settings** | |
-| Guest Mode | Google does not allow guest mode for |
-| | loaner Chromebooks. If guest mode is |
-| | enabled, a user could pick up a loaner |
-| | device and use it without being signed |
-| | in. We rely on login information to |
-| | retroactively assign Chromebooks. If |
-| | devices are manually assigned at the |
-| | time of distribution, then this risk |
-| | is not present. Note: The GnG program |
-| | uses a Guest mode OU that has Guest |
-| | Mode enabled. When a GnG customer opts |
-| | into guest mode, we automatically move |
-| | them to the Guest OU for a fixed |
-| | duration of time. This opt-in happens |
-| | after we have recorded their sign-in |
-| | and use of the device. |
-| Sign-in Restriction | Restrict the sign-in to domain users |
-| | only. For example, @example.com. Doing |
-| | so allows only domain users to log in |
-| | to the device (no @gmail.com login). |
-| Autocomplete Domain | Use the domain name, set Sign-in |
-| | Restriction, for autocomplete. (This |
-| | must be the domain name for the |
-| | account). |
-| Sign-in Screen | Set to *Never show user names and* |
-| | *photos*. This setting is important if |
-| | you chose to not erase user data upon |
-| | logout. If not set this way, the |
-| | usernames of previous users and photos |
-| | will be present. Some users may be |
-| | unsettled by the thought that their |
-| | data remains on the loaner device |
-| | after they've used it. It's important |
-| | to note that all user data is |
-| | encrypted by default on Chrome |
-| | devices. So while it may be |
-| | uncomfortable for some users that |
-| | previous user data is on the device- |
-| | it poses virtually no security risk. |
-| User Data | Set to *Erase all local user data*. |
-| | Related to the note above, since this |
-| | will be a loaner device, you don't |
-| | want to leave the data of other users |
-| | behind, even if that data is |
-| | encrypted. At times, Google found that |
-| | this data wipe prevented reporting |
-| | check-ins. If a loaner was used for a |
-| | short duration, usage data could be |
-| | wiped before there was a chance to |
-| | autonomously account for it. |
-| Sign-In Language | Set to company primary language. This |
-| | is to prevent a user from changing |
-| | language and then the user that |
-| | follows not knowing the language. |
-| **Device Update Settings** | |
-| Auto Update Settings | Set to Allow auto-updates. For |
-| | security reasons, perform all updates |
-| | on the device. |
-| Auto Reboot After Updates | Set to Allow auto-reboots. Chrome OS |
-| | requires a reboot to apply the latest |
-| | downloaded update. Auto-reboot helps |
-| | you to install the updates without |
-| | human intervention. When Allow |
-| | auto-reboots is selected, after a |
-| | successful auto update, the Chrome |
-| | device will reboot when the user next |
-| | signs out. |
-| Release Channel | Set to Move to Stable Channel. For |
-| | optimal stability, all devices must be |
-| | set to Move to Stable Channel. |
-| **Kiosk Settings** | |
-| Public Session Kiosk | Set to Do not allow Public Session |
-| | Kiosk. Public Session Kiosks do not |
-| | use an account for logging in to the |
-| | device. Without having an account that |
-| | is logged into the device, the device |
-| | can't be assigned to a user. |
-| **User & Device Reporting** | |
-| Device Reporting | Enable device state reporting and |
-| | enable tracking recent device users. |
-| | Google uses this data to record who is |
-| | using a device and when. This data is |
-| | also facilitates communication with |
-| | GnG customers. |
-| **Power & Shutdown** | |
-| Power Management | Set to Allow device to sleep/shut down |
-| | when idle on the sign in screen. When |
-| | the device is not in use, allowing it |
-| | to sleep/shutdown maximizes battery |
-| | life. |
-
-## Device Enterprise Enrollment
-
-All Chrome OS devices must be enterprise enrolled so that your organization can
-enforce policies on each device. A manual enrollment process is required for
-every device.
-
-## [How to Enterprise Enroll a Device](https://support.google.com/chrome/a/answer/1360534?hl=en)
-
-## Force install Chrome App by OU
-
-Set up the application to be pushed to all Chromebooks in your domain.
-
-If a device is not already [enterprise enrolled], the application will disable
-itself and become dormant.
-
-To create an installation policy:
-
-1. Go to the [Admin Console](http://admin.google.com).
-
-1. Open Device Management.
-
-1. Under Device Settings, click Chrome Management.
-
-1. Click App Management.
-
-1. Under Filters, find the label named Type and change the type to Domain Apps.
-
- The names of all domain apps available to your domain are listed.
-
-1. Find the loaner application that you deployed previously and click on the
- name.
-
-1. Click User Settings.
-
-1. Find the OU that your users belong to (most likely the parent OU, for
- example, example.com) and click on it.
-
-1. For that OU, you can now configure how the application is to be deployed.
- The following configuration is recommended:
-
- * Allow Installation: Disabled (to prevent users from manually installing
- the application)
-
- * Force Installation: Enabled (everyone can install the application — this
- option is required for the application to open upon log-in)
-
- * Pin to Taskbar: Enabled (pins the application to the bottom taskbar)
-
-1. Click Save.
-
-[G Suite]: https://gsuite.google.com/
-[G Suite Sign-Up Help]: https://docs.google.com/document/d/1qUpgVzCttLiZJ-s5nhXEfuFdHBGNWjPvNRK40pcU9m0/edit#heading=h.5xt9ofon499z
diff --git a/docs/release_notes.md b/docs/release_notes.md
index d2405498..ce4864cc 100644
--- a/docs/release_notes.md
+++ b/docs/release_notes.md
@@ -4,106 +4,121 @@
## Notes on Master branch
-If you are planning on deploying this program for use on your domain we
-recommend pulling from one of the branched releases as opposed to the master
-branch. While we try to keep the master branch working we consider it
-"unstable" and don't recommend using it unless you want to develop for the
-project.
-## [Alpha 0.7.1](https://github.com/google/loaner/tree/Alpha-(0.7.1))
+**If you are doing a new deployment please deploy from master as we work
+on cutting a new release. For current deployments, please hold off on upgrading
+until we can test the next numbered release.**
+
+## [Alpha 0.7.1](https://github.com/google/loaner/tree/Alpha-\(0.7.1\))
+
Released 2018-12-19
#### Features added
-* No major functionality has been added, but this release includes many
- bug fixes and stability improvements.
-* This release includes the framework of our new deployment system that we will
- continue to build on in 2019. While there is no added functionality yet it
- will soon be the easiest way to automatically configure, deploy, and update
- your GnG experience.
+
+* No major functionality has been added, but this release includes many bug
+ fixes and stability improvements.
+* This release includes the framework of our new deployment system that we
+ will continue to build on in 2019. While there is no added functionality yet
+ it will soon be the easiest way to automatically configure, deploy, and
+ update your GnG experience.
#### Known issues
-* You must manually [create the GSuite Chrome organizational units](gsuite_config.md)
- as the app cannot yet create them.
-* You may need to run 'npm install' to update to the latest NPM packages.
-* There may be additional incompatibilities with older versions of this app. If
- you experience any problems please use GitHub's issue tracker.
-## [Alpha 0.7](https://github.com/google/loaner/tree/Alpha-(0.7))
+* You must manually
+ [create the GSuite Chrome organizational units](gngsetup_part1.md)
+ as the app cannot yet create them.
+* You may need to run 'npm install' to update to the latest NPM packages.
+* There may be additional incompatibilities with older versions of this app.
+ If you experience any problems please use GitHub's issue tracker.
+
+## [Alpha 0.7](https://github.com/google/loaner/tree/Alpha-\(0.7\))
+
Released 2018-09-08
Warning: This is a breaking change. If you are running earlier versions of the
app you will need to take the following steps after upgrading for the app to
continue functioning correctly.
-1. Open the `shared/config.ts` file from within the loaner directory and scroll
- to the CHROME_PUBLIC_KEYS section. In this section, you'll paste the value
- from the key field in the `chrome_app/manifet.json` (the public key of the
- Chrome App) to the respective environment (eg. if this is your prod app,
- paste the public key into the prod's quoted value). This is how the Chrome
- App will determine which API to target.
+1. Open the `shared/config.ts` file from within the loaner directory and scroll
+ to the CHROME_PUBLIC_KEYS section. In this section, you'll paste the value
+ from the key field in the `chrome_app/manifet.json` (the public key of the
+ Chrome App) to the respective environment (eg. if this is your prod app,
+ paste the public key into the prod's quoted value). This is how the Chrome
+ App will determine which API to target.
+
+ NOTE: Make sure the key fits on a single line.
- NOTE: Make sure the key fits on a single line.
-1. Save the file and follow the [Deploy to the Chrome Web Store](deploy_chrome_app.md)
- steps to update the application.
+1. Save the file and follow the
+ [Deploy to the Chrome Web Store](gngsetup_part2.md)
+ steps to update the application.
#### Features added
-* Added configuration view so that configurations can be dynamically updated
- without redeploying the app.
-* Settings are now loaded into datastore by default during bootstrap.
-* Animations and additional assets have been added.
-* Adds limited support for multiple domains as long as they're controlled by the
- same G Suite account. This feature is still considered unstable. See the
- "Multi-domain Support" section of the [Setup Guide](setup_guide.md)
- for more information.
+
+* Added configuration view so that configurations can be dynamically updated
+ without redeploying the app.
+* Settings are now loaded into datastore by default during bootstrap.
+* Animations and additional assets have been added.
+* Adds limited support for multiple domains as long as they're controlled by
+ the same G Suite account. This feature is still considered unstable. See the
+ "Multi-domain Support" section of the
+ [Setup Guide](gngsetup_part2.md)
+ for more information.
#### Known issues
-* You must manually [create the GSuite Chrome organizational units](gsuite_config.md)
- as the app cannot yet create them.
-* There may be additional incompatibilities with older versions of this app. If
- you experience any problems please use GitHub's issue tracker.
-* If you are constantly redirected to the bootstrap screen you may need to go
- into Datastore and select "Config" in the dropdown menu and set
- bootstrap_completed to true.
-
-## [Alpha 0.6a](https://github.com/google/loaner/tree/Alpha-(0.6))
+
+* You must manually
+ [create the GSuite Chrome organizational units](gngsetup_part4.md)
+ as the app cannot yet create them.
+* There may be additional incompatibilities with older versions of this app.
+ If you experience any problems please use GitHub's issue tracker.
+* If you are constantly redirected to the bootstrap screen you may need to go
+ into Datastore and select "Config" in the dropdown menu and set
+ bootstrap_completed to true.
+
+## [Alpha 0.6a](https://github.com/google/loaner/tree/Alpha-\(0.6\))
+
Released 2018-06-08
Warning: This is a breaking change. If you are running earlier versions of the
app you will need to take the following steps after upgrading for the app to
continue functioning correctly.
-1. Open your project in [Cloud Console](http://console.cloud.google.com) and
-navigate to Datastore > Entities.
-1. In the Kind dropdown select User.
-1. Select all User entities and Delete them.
-1. Navigate to App Engine > Memcache.
-1. Click the "Flush Cache" button.
-1. Navigate to App Engine > Task Queues and select the Cron Jobs tab.
-1. Find `/_cron/sync_user_roles` and click "Run now."
+1. Open your project in [Cloud Console](http://console.cloud.google.com) and
+ navigate to Datastore > Entities.
+1. In the Kind dropdown select User.
+1. Select all User entities and Delete them.
+1. Navigate to App Engine > Memcache.
+1. Click the "Flush Cache" button.
+1. Navigate to App Engine > Task Queues and select the Cron Jobs tab.
+1. Find `/_cron/sync_user_roles` and click "Run now."
#### Features added
-* Added Search functionality, you can now search by device, shelf, and user.
-* The permissions/roles system has been refactored. Now instead of three static
- roles there's just one pre-defined role (superadmin) that gets all
- permissions. Additional roles can be defined by superadmins and synced with
- groups. For more information about the new system see the APIs doc.
-* Device and shelf views now correctly paginate.
-* Added support for synchronous actions in addition to async actions.
- Synchronous actions can be attached to many of the same workflows async
- actions can be attached to.
+
+* Added Search functionality, you can now search by device, shelf, and user.
+* The permissions/roles system has been refactored. Now instead of three
+ static roles there's just one pre-defined role (superadmin) that gets all
+ permissions. Additional roles can be defined by superadmins and synced with
+ groups. For more information about the new system see the APIs doc.
+* Device and shelf views now correctly paginate.
+* Added support for synchronous actions in addition to async actions.
+ Synchronous actions can be attached to many of the same workflows async
+ actions can be attached to.
#### Known issues
-* You must manually [create the GSuite Chrome organizational units](gsuite_config.md)
- as the app cannot yet create them.
-* There is no configuration view. Configurations must be changed by calling
- the configuration API manually.
-* There may be additional incompatibilities with older versions of this app. If
- you experience any problems please use GitHub's issue tracker.
+* You must manually
+ [create the GSuite Chrome organizational units](gngsetup_part4.md)
+ as the app cannot yet create them.
+* There is no configuration view. Configurations must be changed by calling
+ the configuration API manually.
+* There may be additional incompatibilities with older versions of this app.
+ If you experience any problems please use GitHub's issue tracker.
+
+## [Alpha 0.5a](https://github.com/google/loaner/tree/Alpha-\(0.5\))
-## [Alpha 0.5a](https://github.com/google/loaner/tree/Alpha-(0.5))
Released 2018-03-30
#### Known issues
-* There is no configuration view. Configurations must be changed by calling
- the configuration API manually.
+
+* There is no configuration view. Configurations must be changed by calling
+ the configuration API manually.
diff --git a/docs/setup_guide.md b/docs/setup_guide.md
deleted file mode 100644
index 3f4b0d60..00000000
--- a/docs/setup_guide.md
+++ /dev/null
@@ -1,547 +0,0 @@
-# Grab n Go Setup
-
-
-
-
-## About
-
-The Grab n Go (GnG) web app makes it easy to manage a fleet of loaner Chromebook
-devices. Using GnG, users can self-checkout a loaner Chromebook and begin using
-it right away, thereby decreasing the workload on IT support while keeping users
-productive.
-
-## Prerequisites
-
-Before you start configuring the GnG web app itself, you need to setup and
-configure a Google Cloud Platform project:
-
-1. **Get [G Suite](https://gsuite.google.com/intl/en_in/setup-hub/) with
- [Chrome for
- Enterprise](https://enterprise.google.com/chrome/chrome-enterprise/)**
-
- To log in to an assigned loaner Chromebook, borrowers must use a Google G
- Suite account (GnG will not work with standard Gmail accounts).
-
-1. **Setup an App Engine project in Google Cloud**
-
- 1. GnG runs on Google App Engine, an automatically scaling, sandboxed
- computing environment that runs on Google Cloud. [Create a Google Cloud
- Platform
- Project](https://cloud.google.com/resource-manager/docs/creating-managing-projects).
- Name the project something you will remember, such as *loaner*.
-
- 1. [Create a billing
- account](https://cloud.google.com/billing/docs/how-to/manage-billing-account)
- and then [enable billing for the
- project](https://cloud.google.com/billing/docs/how-to/modify-project)
- that you created.
-
- 1. For secure authentication, the GnG application uses OAuth2. This
- requires that you [create an OAuth2 Client ID within your App Engine
- Project](https://cloud.google.com/endpoints/docs/frameworks/python/creating-client-ids#Creating_OAuth_20_client_IDs).
- When prompted, make sure to select **Web App**. For the Authorized
- JavaScript Origins URL, your App Engine project URL will be your GCP
- project ID followed by appspot.com. For example, if your GCP project ID
- is "example-123456" then the default URL will be
- https://example-123456.appspot.com. You can also [configure App Engine
- to use your own custom
- domain](https://cloud.google.com/appengine/docs/standard/python/mapping-custom-domains).
-
- **NOTE**: Make sure to add your App Engine project URL to Authorized
- JavaScript Origins. Otherwise, the app will fail to authenticate.
- Changing this setting has a propagation delay, so if you are getting
- origin errors you will need to set this and then wait a few minutes.
-
- 1. Visit the OAuth consent screen tab and ensure that the Application type
- is listed as "Public".
-
- **WARNING:** The Chrome App will be unable to generate any
- OAuth tokens if the Application type isn't listed as Public.
-
- 1. The GnG application requires a service account on your G Suite Domain
- configured with **G Suite Domain-Wide Delegated Authority** in order to
- access the G Suite APIs to move devices to and from organizational
- units, maintain permissions based on Google Groups, etc.
-
- [Create the service account and its
- credentials](https://developers.google.com/admin-sdk/directory/v1/guides/delegation).
-
- **NOTE**: During service account creation you do not need to select a
- Role.
-
- This will produce a newly furnished private key in the form of a JSON
- file containing the client secrets for the service account.
-
- **WARNING:** Do not lose or share this private key file, as it allows
- access to your G Suite domain user data through the service account.
-
- Once you have created a service account and downloaded its JSON-encoded
- private key, you can move on to the next step.
-
- 1. [Delegate domain-wide authority to the service account you
- created](https://developers.google.com/admin-sdk/directory/v1/guides/delegation).
-
- In the **One or More API Scopes** field copy and paste the following
- list of scopes required by GnG:
-
- ```
- https://www.googleapis.com/auth/admin.directory.device.chromeos,
- https://www.googleapis.com/auth/admin.directory.group.member.readonly,
- https://www.googleapis.com/auth/admin.directory.orgunit,
- https://www.googleapis.com/auth/admin.directory.user.readonly
- ```
-
- 1. GnG requires the Directory API to manage devices in your G Suite Domain.
- To access the Directory API you will need to enable the Admin SDK API
- through [Google Cloud
- Console](https://console.developers.google.com/apis/api/admin.googleapis.com/overview)
-
-1. **Set up a G Suite role account**
-
- In order to give the app domain privileges you must also set up a G Suite
- role account for the app to use. This account won't require an additional G
- Suite license, it will act only as a proxy for the application.
-
- 1. Visit [Google Admin](https://admin.google.com) and create a new user.
- Name it something such as loaner-role@example.com. Set the password to
- something highly complex, as a human should never log into this account.
- It is highly recommended that you also use 2FA on this account to reduce
- risk.
-
- 1. Give the account the following Admin roles:
-
- + Directory Admin
- + Services Admin
- + User Management Admin
-
- **Note**: It is recommended that you put this account in an OU that has all
- G Suite and additional services disabled.
-
-1. **[Enterprise
- enroll](https://support.google.com/chrome/a/answer/1360534?hl=en) your
- [Chromebooks](https://www.google.com/chromebook/)**
-
-1. **Set up your permissions groups**
-
- By default users only have permission to view and manage their own loans. To
- give users elevated permissions to manage devices and shelves you must
- assign them roles. User's roles are managed using Google Groups. You must
- provide at least one group for superadmins - users that have all permissions
- by default. Additional roles can be created by calling the role API with
- a custom set of permissions depending on what access you'd like to give. You
- can provide different Google Groups to manage the users in these roles and
- they will sync automatically. You can also manually add users to roles if
- you do not provide a group. You can [add the appropriate users to each
- group.](https://support.google.com/groups/answer/2465464?hl=en&ref_topic=2458761)
-
- Note: Make sure to add yourself in the superadmins group in order to
- get the highest elevated permissions for the application. You will not be
- able to set up the application without those permissions.
-
-1. **Set up a development computer**
-
- You’ll modify the code and build and upload GnG from this device.
-
- + Note: This deployment has only been tested on Linux and macOS.
-
- + Install the following software:
-
- + [Install Git](https://git-scm.com/downloads)
- + [Install
- Bazel](https://docs.bazel.build/versions/master/install.html)
- + [Install the Google Cloud SDK](https://cloud.google.com/sdk/)
- + [Install NPM](https://www.npmjs.com/get-npm)
-
-While the following skills are not explicitly required, you should be
-comfortable referencing the documentation for each of these to troubleshoot
-deployments of GnG:
-
-+ **[Know some Python](https://www.python.org/).** \
- To customize the GnG backend, you’ll use Python 2.7.
-
-+ **[Know some Angular and Typescript](https://angular.io/).** \
- To modify the GnG frontend and Chrome App, you will use Angular with
- Typescript.
-
-+ **[Learn the Basics of Google App
- Engine](https://cloud.google.com/appengine/docs/standard/python/).** \
- Although GnG is mostly set up, it is helpful to know the App Engine
- environment should you want to customize it.
-
-+ **[Learn Git](https://git-scm.com/).** \
- If you have not used Git before, become familiar with this popular version
- control system. You will clone the repository with Git.
-
-## Configuration
-
-Use Git to make a copy of the GnG loaner source code, the command to run for the
-current release can be found on the
-[README](README.md).
-
-**Note**: The rest of this setup guide assumes that your working directory will
-be the root of the Git repository.
-
-### Customize the App Deployment Script
-
-In the `loaner/deployments` directory, edit `deploy.sh` and change the instances
-(`PROD`, `QA` and `DEV`) to the Google Cloud Project ID(s) you've created for
-your app. If you've only created one project, assign the project ID to `PROD`.
-We find it useful to have separate development and qa apps for testing, but
-these are optional.
-
-### Customize the BUILD Rule for Deployment
-
-The source code includes a `WORKSPACE` file to make it a [Bazel
-workspace](https://docs.bazel.build/versions/master/build-ref.html#workspaces).
-
-The client secret file for the service account you created earlier must be moved
-into your local copy of the GnG app inside the `loaner/web_app` directory. If
-you are using Cloud Shell or a remote computer, you can simply copy and paste
-the contents of the file. A friendly name is suggested e.g.
-`client-secret.json`. Once the file has been relocated to this directory, the
-BUILD rule in `loaner/web_app/BUILD` named "loaner" must have a [data
-dependency](https://docs.bazel.build/versions/master/build-ref.html#data) that
-references the `client-secret.json` file.
-
-```
- loaner_appengine_library(
- name = "loaner",
- data = ["client-secret.json"], # Add this line.
- deps = [
- ":chrome_api",
- ":endpoints_api",
- ":main",
- "//loaner/web_app/backend",
- ],
- )
-```
-
-### Customize the App Constants
-
-Constants are variables you typically define once. For a constant to take
-effect, you must deploy a new version of the app. Constants can’t be configured
-in a running app. Instead, they must be set manually in
-`loaner/web_app/constants.py` and `loaner/shared/config.ts`.
-
-Before you deploy GnG, the following constants must be configured:
-
-#### loaner/web_app/constants.py
-
-+ **`APP_DOMAINS`** is a list of domains you would like to have access to this
- deployment of Grab n Go. The primary domain should be listed first, this is
- the Google domain in which you run G Suite with Chrome Enterprise. For
- example, if you arrange G Suite for the domain `mycompany.com` use that
- domain name as the first value in this list constant.
-
- Note: If you'd like to run this program on more than one domain, please see
- the "Multi-domain Support" section at the bottom of this doc.
-
-+ **`ON_PROD`** is the Google Cloud Project ID the production version of GnG
- will run in. You need to replace the string 'prod-app-engine-project' with
- the ID of your project.
-
-+ **`ADMIN_EMAIL`** the email address of the G Suite role account you set up.
- Usually loaner-role@example.com.
-
-+ **`SEND_EMAIL_AS`** is the email address within the G Suite Domain that GnG
- app email notifications will be sent from.
-
-+ **`SUPERADMINS_GROUP`**: The Google Groups email address that contains
- at least one Superadmin in charge of configuring the app.
-
-Within the `if ON_PROD` block are the required constants to be configured on the
-Google Cloud Project you will be using to host the production version of GnG:
-
-+ **`CHROME_CLIENT_ID`** the Chrome App will use this to authenticate to
- the production version of GnG. **Leave this blank for now, you'll generate
- this ID later.**
-
-+ **`WEB_CLIENT_ID`** is the OAuth2 Client ID you created previously that
- the Web App frontend will use to authenticate to the production version of
- GnG.
-
-+ **`SECRETS_FILE`** is the location of the Directory APIs service account
- secret json file relative to the Bazel WORKSPACE. If using the example above
- for the BUILD rule the constant would look like this:
-
- SECRETS_FILE = 'loaner/web_app/client-secret.json'
-
-The remaining ON_QA and ON_DEV are only required if you choose to use multiple
-versions to test deployments before promoting them to the production version.
-
-+ **`CUSTOMER_ID`** is the (optional) unique ID for your organization's G
- Suite account, which GnG uses to access Google's Directory API. If this is
- not configured the app will use the helper string `my_customer` which will
- default to the G Suite domain the app is running in.
-
-+ By default, **`BOOTSTRAP_ENABLED`** is set to `True`. This constant unlocks
- the bootstrap functionality of GnG necessary for the initial deployment.
-
- **WARNING:** Change this constant to `False` *after* you complete the
- initial bootstrap. Setting this constant to `False` will prevent unexpected
- bootstraps in the future (a bootstrap will cause data loss).
-
-#### shared/config.ts
-
-+ **`PROD`** is the Google Cloud Project ID that the production version of GnG
- will operate in. You will need to replace the string
- 'prod-app-engine-project' with the ID of your project. This is the same ID
- used for ON_PROD in loaner/web_app/constants.py.
-
-+ **`WEB_CLIENT_IDS`** is the OAuth2 Client ID you created previously that
- the Web App frontend will use to authenticate to the backend. This is the
- same ID that was used for the WEB_CLIENT_ID in
- loaner/web_app/constants.py. If you are deploying a single instance of the
- application, fill in the PROD value with the Client ID.
-
-+ **`STANDARD_ENDPOINTS`** is the Google Endpoints URL the frontend uses to
- access your backend API. If necessary, update the `prod`, `qa` and `dev`
- values.
-
- * (*optional*) If you are deploying a single instance of the application,
- use that value for all fields. Otherwise, specify your separate prod, qa
- and dev endpoint URLs.
-
-### (Optional) Customize GnG Settings
-
-*Default Configurations* are those options you can configure when GnG is
-running. The default values for these options are defined in
-`loaner/web_app/config_defaults.yaml`. After first launch, GnG stores these
-values in [Cloud Datastore](https://cloud.google.com/datastore/). You can change
-settings without deploying a new version of GnG:
-
-+ **allow_guest_mode**: Allow users to use guest mode on loaner devices.
-+ **loan_duration**: The number of days to assign a device.
-+ **maximum_loan_duration**: The maximum number of days a loaner can be
- loaned.
-+ **loan_duration_email**: Send a duration email to the user.
-+ **reminder_email_throttling**: Do not send emails to a user when a reminder
- appears in the loaner's Chrome app.
-+ **reminder_delay**: Number of hours after which GnG will send a reminder
- email for a device identified as needing a reminder.
-+ **shelf_audit**: Enable shelf audit.
-+ **shelf_audit_email**: Whether email should be sent for audits.
-+ **shelf_audit_email_to**: List of email addresses to receive a notification.
-+ **shelf_audit_interval**: The number of hours to allow a shelf to remain
- unaudited. Can be overwritten via the audit_interval_override property for a
- shelf.
-+ **responsible_for_audit**: Group that is responsible for performing an audit
- on a shelf.
-+ **support_contact**: The name of the support contact.
-+ **org_unit_prefix**: The organizational unit to be the root for the GnG
- child organizational units.
-+ **audit_interval**: The shelf audit threshold in hours.
-+ **sync_roles_query_size**: The number of users for whom to query and
- synchronize roles.
-+ **anonymous_surveys**: Record surveys anonymously (or not).
-+ **use_asset_tags**: To require asset tags when enrolling new devices, set as
- True. Otherwise, set as False to only require serial numbers.
-+ **img_banner_**: The banner is a custom image used in the reminder emails
- sent to users. Use the URL of an image you have stored in your GCP Storage.
-+ **img_button_**: The button images is a custom image used for reminder
- emails sent to users. Use the URL of an image you have stored in your GCP
- Storage.
-+ **timeout_guest_mode**: Specify that a deferred task should be created to
- time out guest mode.
-+ **guest_mode_timeout_in_hours**: The number of hours to allow guest mode to
- be in use.
-+ **unenroll_ou**: The organizational unit into which to move devices as they
- leave the GnG program. This value defaults to the root organizational unit.
-+ **return_grace_period**: The grace period (in minutes) between a user
- marking a device as pending return and when we reopen the existing loan.
-
-### (Optional) Customize Images for Button and Banner in Emails
-
-You can upload custom banner and button images to [Google Cloud
-Storage](https://cloud.google.com/storage/) to use in the emails sent by the
-GnG.
-
-To do this, upload your custom images to Google Cloud Storage via the console by
-following [these
-instructions](https://cloud.google.com/storage/docs/cloud-console).
-
-Name your bucket and object something descriptive, e.g.
-`https://storage.cloud.google.com/[BUCKET_NAME]/[OBJECT_NAME]`.
-
-The recommended banner image size is 1280 x 460 and the recommended button size
-is 840 x 140. Make sure the `Public Link` checkbox is checked for both of the
-images you upload to Cloud Storage.
-
-Next, click on the image names in the console to open the images and copy their
-URLs. Take these URLs and populate them as values for the variables
-`img_banner_primary` and `img_button_manage` in the `config_defaults.yaml` file.
-
-### (Optional) Customize Events and Email Templates in the GnG Datastore
-
-This YAML file contains the event settings and email templates that the
-bootstrap process imports into Cloud Datastore after first launch:
-
-`loaner/web_app/backend/lib/bootstrap.yaml`
-
-#### Core Events
-
-Core events (in the `core_events` section) are events that GnG raises at runtime
-when a particular event occurs. For example, the assignment of a new device or
-the enrollment of a new shelf. The calls to raise events are hard-coded and the
-event names in the configuration YAML file must correspond to actions defined in
-the `loaner/web_app/backend/actions` directory.
-
-Specifically, each event can be configured in the datastore to call zero or more
-actions and these actions are defined by the modules contained in the
-`loaner/web_app/backend/actions` directory. Each of these actions will be run as
-an [App Engine
-Task](https://cloud.google.com/appengine/docs/standard/python/taskqueue/), which
-allows them to run asynchronously and not block the processing of GnG.
-
-While GnG contains several pre-coded actions, you can also add your own. For
-example, you can add an action as a module in the
-`loaner/web_app/backend/actions` directory to interact with your organization's
-ticketing or inventory system. If you do this, please be sure to add or remove
-the actions in the applicable events section in the YAML file.
-
-When bootstrapping is complete, this YAML will have been imported and converted
-into Cloud Datastore entities — you'll need to make further changes to those
-entities.
-
-#### Custom Events
-
-Custom events (in the `custom_events` section) are events that GnG raises as
-part of a regular cron job. These events define criteria on the Device and Shelf
-entities in the Cloud Datastore. GnG queries the Datastore using the defined
-criteria and raises Action tasks, just as it does for Core events.
-
-The difference is that GnG uses the query to determine which entities require
-these events. For example, you can specify that Shelf entities with an audit
-date of more than three days ago should trigger an email to a management team
-and run the corresponding actions that are defined for that event.
-
-The custom events system can access the same set of actions as core events.
-
-#### Reminder Events
-
-Reminder events (in the `reminder_events` section) define criteria for device
-entities that trigger reminders for a user. For example, that their device is
-due tomorrow or is overdue. These events are numbered starting with 0. You can
-customize the events as need be.
-
-**Note**: If you customize any event, be sure to change the neighboring events,
-too. Reminder events must not overlap with each other. If so, reminders may
-provide conflicting information to borrowers.
-
-The reminder events system can access the same set of actions as core and custom
-events.
-
-#### Shelf Audit Event
-
-Shelf audit events (in the `shelf_audit_events` section) are events that are
-triggered by the shelf audit cron job. GnG runs a single Shelf audit event by
-default, but you can add custom events as well.
-
-#### Email Templates
-
-The `templates` section contains a base email template for reminders, and
-higher-level templates that extend that base template for specific reminders.
-You can customize the templates.
-
-## Build and Deploy
-
-1. Go to the `loaner/` directory and launch the GnG deployment script:
-
- ```
- cd loaner
- bash deployments/deploy.sh web prod
- ```
-
- **Note**: If you are running `deploy.sh` on Linux, you may need to install
- `node-sass` using `npm` manually using the following command:
-
- ```
- npm install --unsafe-perm node-sass
- ```
-
- This command builds the GnG web application GnG and deploys it to prod using
- gcloud.
-
- The `deploy.sh` script also includes other options:
-
- ```
- bash deployments/deploy.sh (web|chrome) (local|dev|qa|prod)
- ```
-
-1. App Engine's SDK provides an app named `dev_appserver.py` that you can use
- to test the app on your local development machine. To do so, build the app
- manually and then use `dev_appserver.py` from the output directory like so:
-
- ```
- bash loaner/deployments/deploy.sh web local
- cd ../bazel-bin/loaner/web_app/runfiles.runfiles/gng/
- dev_appserver.py app.yaml
- ```
-
-### Confirm that GnG is Running
-
-In the Cloud Console under _App Engine > Versions_ the GnG code that you just
-built and pushed should appear.
-
-To display all four services, click the _Service_ drop-down menu:
-
-+ **`default`** is the main service, which interacts with the web frontend
-+ **`action-system`** runs the cron jobs that spawn Custom and Reminder events
- and process the resulting Action tasks
-+ **`chrome`** is the service that handles heartbeats from the Chrome app
-+ **`endpoints`** handles API requests via Cloud endpoints for all API clients
- except Chrome app heartbeats
-
-### Bootstrapping
-
-The first time you visit the GnG Web app you will be prompted to bootstrap the
-application. You can only do this if you're a technical administrator, so make
-sure you've added your account to the correct group `technical-admins` group you
-defined previously. Bootstrapping the app will set up the default configurations
-and initialize the connection to [BigQuery](https://cloud.google.com/bigquery/)
-and the [Directory API](https://developers.google.com/admin-sdk/directory/).
-After the app has been successfully bootstrapped, make sure to edit the
-`constants.py` file and set `bootstrap_enabled` to `False` so that you don't
-accidentally overwrite your configuration.
-
-**Note**: The bootstrap process may take a few minutes to complete.
-
-#### Create an Authorized Email Sender
-
-You need to configure an authorized email sender that GnG emails will be sent
-from, e.g. loaner@example.com. To do that, add an [Email API Authorized
-Senders](https://console.cloud.google.com/appengine/settings) in the GCP
-Console.
-
-#### Deploy the Chrome App
-After bootstrapping is complete, you will need to set up the GnG Chrome App.
-This app helps configure the Chromebooks you will be using as loaners and
-provides the bulk of the user-facing experience. Continue on to [deploying the
-chrome app](deploy_chrome_app.md).
-
-#### Multi-domain Support (Optional).
-
-WARNING: This functionality is considered unstable. Use with caution and report
-any bugs using GitHub's issue tracker.
-
-If you want to support more than one managed domain on loaner devices please
-follow the steps below. Please note, the domains you want to support must be
-part of the same G Suite account and added to admin.google.com via
-Account > Domains > Add/Remove Domains. Different domains managed by different
-G Suite accounts and public Gmail addresses are not supported.
-
-+ The domains you want to support must be added to the App Engine project from
- console.cloud.google.com via App Engine > Settings > Custom Domains.
-+ In App Engine > Settings > Application settings "Referrers" must be set to
- Google Accounts API.
- WARNING: Setting this allows any Google managed account to try and sign into
- the app. Make sure you have the latest version of the code deployed or you
- could be exposing the app publicly.
-+ In the application's code in web_app/constants.py the variable APP_DOMAINS
- should be a list of all the domains you plan on supporting.
-+ Go to admin.google.com and in Devices > Chrome Management > Device Settings
- find the Grab n Go parent OU and set Sign-in Restriction to the list of
- domains you're supporting. Optionally, you may also want to switch off the
- Autocomplete Domain option as it may cause some confusion (it's not very
- intuitive that you can override the sign-in screen by typing your full email
- address).
diff --git a/loaner/chrome_app/config/webpack.common.js b/loaner/chrome_app/config/webpack.common.js
index cbf01e4c..8ea8c58c 100644
--- a/loaner/chrome_app/config/webpack.common.js
+++ b/loaner/chrome_app/config/webpack.common.js
@@ -33,11 +33,15 @@ module.exports = {
test: /\.ts$/,
loader: '@ngtools/webpack',
},
- {test: /\.html$/, loader: 'html-loader'}, {
+ {test: /\.html$/, loader: 'html-loader'},
+ {
test: /\.(png|jpe?g|gif|woff|svg|woff2|ttf|eot|ico)$/,
- loader: 'file-loader?name=assets/[name].[hash].[ext]'
+ loader: 'file-loader?name=assets/[name].[hash].[ext]',
+ options: {
+ esModule: false, // Prevents [object Module] output in IMG SRC.
+ },
},
- {test: /\.scss$/, use: ['raw-loader', 'sass-loader']}
+ {test: /\.scss$/, use: ['raw-loader', 'sass-loader']},
]
},
plugins: [
@@ -72,12 +76,19 @@ module.exports = {
{from: './chrome_app/src/app/assets/preload.css', to: './assets/'},
// Chrome App icons
{from: './chrome_app/src/app/assets/icons/', to: './assets/icons/'},
+ // Chrome App shared assets
+ {from: './shared/assets/', to: './shared/assets/'},
// FAQ markdown file
{from: './chrome_app/src/app/assets/faq.md', to: './assets/faq.md'},
// Animations
{
from: './chrome_app/src/app/assets/animations/',
to: './assets/animations/'
+ },
+ // Debug view
+ {from: './chrome_app/src/app/debug/debug.html', to: './'}, {
+ from: './chrome_app/src/app/debug/debug.js',
+ to: './debug_script-bundle.js'
}
]),
]
diff --git a/loaner/chrome_app/config/webpack.prod.js b/loaner/chrome_app/config/webpack.prod.js
index 912195e9..b7b17305 100644
--- a/loaner/chrome_app/config/webpack.prod.js
+++ b/loaner/chrome_app/config/webpack.prod.js
@@ -14,7 +14,7 @@
const webpack = require('webpack');
const webpackMerge = require('webpack-merge');
-const ExtractTextPlugin = require('extract-text-webpack-plugin');
+const TerserPlugin = require('terser-webpack-plugin');
const commonConfig = require('./webpack.common.js');
const helpers = require('./helpers');
@@ -31,18 +31,12 @@ module.exports = webpackMerge(commonConfig, {
plugins: [
new webpack.NoEmitOnErrorsPlugin(),
- new webpack.optimize.UglifyJsPlugin({
- comments: false
- }),
- new webpack.DefinePlugin({
- 'process.env': {
- 'ENV': JSON.stringify(ENV)
- }
- }),
+ new TerserPlugin(),
+ new webpack.DefinePlugin({'process.env': {'ENV': JSON.stringify(ENV)}}),
new webpack.LoaderOptionsPlugin({
htmlLoader: {
- minimize: false // workaround for ng2
+ minimize: false // workaround for ng2
}
- })
+ }),
]
});
diff --git a/loaner/chrome_app/manifest.json b/loaner/chrome_app/manifest.json
index bd385e02..54aff941 100644
--- a/loaner/chrome_app/manifest.json
+++ b/loaner/chrome_app/manifest.json
@@ -8,6 +8,7 @@
"alarms",
"enterprise.deviceAttributes",
"identity",
+ "idle",
"notifications",
"storage",
"webview",
@@ -28,5 +29,13 @@
"scopes": [
"https://www.googleapis.com/auth/userinfo.email"
]
+ },
+ "commands": {
+ "open-debug": {
+ "suggested_key": {
+ "default": "Ctrl+Shift+9"
+ },
+ "description": "Opens the debug view."
+ }
}
}
diff --git a/loaner/chrome_app/src/app/assets/icons/gng128.png b/loaner/chrome_app/src/app/assets/icons/gng128.png
index 37b4b075..665ae439 100755
Binary files a/loaner/chrome_app/src/app/assets/icons/gng128.png and b/loaner/chrome_app/src/app/assets/icons/gng128.png differ
diff --git a/loaner/chrome_app/src/app/assets/icons/gng16.png b/loaner/chrome_app/src/app/assets/icons/gng16.png
index 22f3f6ce..22f03461 100755
Binary files a/loaner/chrome_app/src/app/assets/icons/gng16.png and b/loaner/chrome_app/src/app/assets/icons/gng16.png differ
diff --git a/loaner/chrome_app/src/app/assets/icons/gng48.png b/loaner/chrome_app/src/app/assets/icons/gng48.png
index 08c1ed6c..861ab51d 100755
Binary files a/loaner/chrome_app/src/app/assets/icons/gng48.png and b/loaner/chrome_app/src/app/assets/icons/gng48.png differ
diff --git a/loaner/chrome_app/src/app/assets/icons/gnglogo.png b/loaner/chrome_app/src/app/assets/icons/gnglogo.png
index f79ea777..e7d5d449 100755
Binary files a/loaner/chrome_app/src/app/assets/icons/gnglogo.png and b/loaner/chrome_app/src/app/assets/icons/gnglogo.png differ
diff --git a/loaner/chrome_app/src/app/background/background.ts b/loaner/chrome_app/src/app/background/background.ts
index b523f50f..5f0a61e0 100644
--- a/loaner/chrome_app/src/app/background/background.ts
+++ b/loaner/chrome_app/src/app/background/background.ts
@@ -40,6 +40,24 @@ const ENROLLED_AND_NOT_ONBOARDED: LoanerStorage = {
/** Represent the name of the local storage key name. */
const LOANER_STATUS_NAME = 'loanerStatus';
+/** Notification identifiers. */
+const UNHANDLED_ERROR = 'unhandledError';
+const NOT_ENROLLED = 'notEnrolled';
+const NO_INTERNET_MANAGEMENT = 'noInternet-management';
+const NOT_ONBOARDED = 'notOnboarded';
+const PLEASE_WAIT = 'pleaseWait';
+
+/**
+ * Checks if the ID provided is approved for the debug listener.
+ * @param id string that represents the name of the notification.
+ */
+function debugApproved(id: string) {
+ return (
+ id === UNHANDLED_ERROR || id === NOT_ENROLLED ||
+ id === NO_INTERNET_MANAGEMENT || id === NOT_ONBOARDED ||
+ id === PLEASE_WAIT);
+}
+
/**
* On launch of the application check the source and open the management
* application. This also does a check to see if an internet connection is
@@ -56,7 +74,7 @@ chrome.app.runtime.onLaunched.addListener((launchData) => {
const offlineNotification = 'You have no internet connection so the ' +
PROGRAM_NAME + ' app is unavailable.';
createNotification(
- 'noInternet-management', offlineNotification, 'You\'re offline');
+ NO_INTERNET_MANAGEMENT, offlineNotification, 'You\'re offline');
}
}
});
@@ -75,7 +93,9 @@ function prepareToOnboardUser() {
// First attempt occurs regardless of sign in.
onboardUser();
// Second attempt only occurs if the sign in changes.
- chrome.identity.onSignInChanged.addListener(() => onboardUser());
+ chrome.identity.onSignInChanged.addListener(() => {
+ onboardUser();
+ });
}
/**
@@ -99,14 +119,14 @@ function launchManage() {
'the ' + PROGRAM_NAME + ' program. Please contact your ' +
'administrator.';
createNotification(
- 'notEnrolled', notEnrolledNotification,
+ NOT_ENROLLED, notEnrolledNotification,
'This device is not enrolled');
console.error('Enrollment status: ', status.enrolled);
} else if (!status.onboardingComplete) {
const notOnboardedNotification = 'Please try again after ' +
'completing the onboarding process.';
createNotification(
- 'notOnboarded', notOnboardedNotification,
+ NOT_ONBOARDED, notOnboardedNotification,
'Please complete the onboarding process first');
console.error(
'Onboarding complete status: ', status.onboardingComplete);
@@ -124,7 +144,7 @@ function manageValueUpdater(): Observable {
// Adds a warning and notification that the values are being updated.
console.warn('Attempting to update the local storage values.');
const waitNotification = 'We are checking this devices current status.';
- createNotification('pleaseWait', waitNotification, 'Please wait');
+ createNotification(PLEASE_WAIT, waitNotification, 'Please wait');
return new Observable(observer => {
Heartbeat.sendHeartbeat().subscribe(
deviceInfo => {
@@ -148,7 +168,7 @@ function manageValueUpdater(): Observable {
const unhandledNotification =
'Oh no! We were unable to retrieve the devices current state.';
createNotification(
- 'unhandledError', unhandledNotification, 'Something happened');
+ UNHANDLED_ERROR, unhandledNotification, 'Something happened');
console.error(error);
});
});
@@ -169,6 +189,7 @@ function createNotification(
requireInteraction: true,
title: `${title}`,
type: 'basic',
+ buttons: [{title: 'Debug'}]
};
chrome.notifications.create(notificationID, options);
@@ -258,6 +279,9 @@ chrome.runtime.onMessage.addListener(
case 'offboarding':
launchOffboardingFlow();
break;
+ case 'debug':
+ createDebugView();
+ break;
default:
// Do nothing
break;
@@ -281,7 +305,7 @@ chrome.runtime.onMessage.addListener(
*/
function checkLoanerStatus(): Observable {
const storage = new Storage();
- return storage.local.getLoanerStorage(LOANER_STATUS_NAME)
+ return storage.local.get(LOANER_STATUS_NAME)
.pipe(
take(1),
switchMap(status => status ? of(status) : manageValueUpdater()));
@@ -420,3 +444,25 @@ function keepTrying(alarm: chrome.alarms.Alarm) {
});
}
}
+
+/**
+ * Add a listener to allow for notifications to generate a debug view for
+ * given views.
+ */
+chrome.notifications.onButtonClicked.addListener((id, buttonIndex) => {
+ if (buttonIndex === 0 && debugApproved(id)) createDebugView();
+});
+
+/**
+ * Adds a listener to allow for the defined hotkey set to generate a debug view.
+ */
+chrome.commands.onCommand.addListener(command => {
+ if (command === 'open-debug') createDebugView();
+});
+
+/** Generates the debug view for debugging the Chrome App and its state. */
+function createDebugView() {
+ chrome.app.window.create(
+ 'debug.html', {id: 'debug', minWidth: 800, minHeight: 650});
+}
+
diff --git a/loaner/chrome_app/src/app/background/heartbeat.ts b/loaner/chrome_app/src/app/background/heartbeat.ts
index ce2489e9..81396f69 100644
--- a/loaner/chrome_app/src/app/background/heartbeat.ts
+++ b/loaner/chrome_app/src/app/background/heartbeat.ts
@@ -74,13 +74,27 @@ export function setHeartbeatAlarmListener() {
*/
function createHeartbeatListener(alarm: chrome.alarms.Alarm) {
if (alarm.name === HEARTBEAT.name && navigator.onLine) {
- sendHeartbeat().subscribe();
- if (CONFIG.LOGGING) {
- console.info(`Heartbeat sent`);
- }
+ sendHeartbeatIfUnlocked();
}
}
+/**
+ * Checks if the loaner is active (used in the last 5 minutes) or idle. If it is
+ * locked, it will not send a heartbeat as this will potentially reassign a
+ * previously returned device that the previous user did not log out of.
+ */
+function sendHeartbeatIfUnlocked() {
+ const durationToQuery = 5 * 60; // 5 minutes worth of time for active state.
+ chrome.idle.queryState(durationToQuery, state => {
+ if (state !== 'locked') {
+ sendHeartbeat().subscribe();
+ if (CONFIG.LOGGING) {
+ console.info(`Heartbeat sent`);
+ }
+ }
+ });
+}
+
/** Destroys the heartbeat listener. */
export function removeHeartbeatListener() {
chrome.alarms.onAlarm.removeListener(createHeartbeatListener);
diff --git a/loaner/chrome_app/src/app/debug/debug.html b/loaner/chrome_app/src/app/debug/debug.html
new file mode 100644
index 00000000..6ebef06e
--- /dev/null
+++ b/loaner/chrome_app/src/app/debug/debug.html
@@ -0,0 +1,38 @@
+
+
+
+ Codestin Search App
+
+
+
+ This view is strictly for debugging the Chrome App. Please take a screenshot of this view so
+ that we may troubleshoot further.
+
+
Alarms
+
+
+
Enrollment Status
+
+
+
OAuth Token Status
+
+
+
Device ID
+
+
+
App Version
+
+
+
Public Key
+
+
+
Client ID
+
+
+
Network Connection
+
+
+
+
+
+
diff --git a/loaner/chrome_app/src/app/debug/debug.js b/loaner/chrome_app/src/app/debug/debug.js
new file mode 100644
index 00000000..52b25c55
--- /dev/null
+++ b/loaner/chrome_app/src/app/debug/debug.js
@@ -0,0 +1,87 @@
+// Copyright 2018 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+/**
+ * Waits for the window to load and then runs the update function to populate
+ * debug values.
+ */
+window.onload = () => update();
+
+/** Updates the debug view with all of the necessary debug values. */
+function update() {
+ // Alarms
+ const alarmsElement = document.getElementById('alarms');
+ chrome.alarms.getAll(alarms => {
+ if (alarmsElement) {
+ alarmsElement.textContent = JSON.stringify(alarms);
+ }
+ });
+
+ // Loaner Storage Status
+ const enrollmentElement = document.getElementById('enrollment');
+ chrome.storage.local.get(['loanerStatus'], status => {
+ if (enrollmentElement) {
+ enrollmentElement.textContent = JSON.stringify(status);
+ }
+ });
+
+ // OAuth Token
+ const oauthElement = document.getElementById('oauth');
+ chrome.identity.getAuthToken(token => {
+ if (oauthElement) {
+ oauthElement.textContent =
+ token ? 'Defined' : 'THERE IS NO TOKEN DEFINED.';
+ }
+ });
+
+ // Device ID
+ const deviceElement = document.getElementById('device');
+ if (deviceElement) {
+ if (chrome.enterprise) {
+ chrome.enterprise.deviceAttributes.getDirectoryDeviceId(
+ id => deviceElement.textContent = id);
+ } else {
+ deviceElement.textContent = 'chrome.enterprise API is unavailable';
+ }
+ }
+
+ // App Version
+ const appVersionElement = document.getElementById('version');
+ if (appVersionElement) {
+ appVersionElement.textContent = chrome.runtime.getManifest().version;
+ }
+ // Public Key
+ const keyElement = document.getElementById('key');
+ if (keyElement) {
+ keyElement.textContent = chrome.runtime.getManifest().key;
+ }
+
+ // Client ID
+ const clientIdElement = document.getElementById('client-id');
+ if (clientIdElement) {
+ clientIdElement.textContent = chrome.runtime.getManifest().oauth2.client_id;
+ }
+
+ // Network Connection
+ const networkElement = document.getElementById('network');
+ if (networkElement) {
+ networkElement.textContent = navigator.onLine ?
+ 'There is a network connection.' :
+ 'There is NO network connection. Please connect to a WiFi network.';
+ }
+
+}
+
+/** Updates the content when the refresh button is clicked. */
+document.getElementById('refresh').onclick = () => update();
diff --git a/loaner/chrome_app/src/app/manage/app.ts b/loaner/chrome_app/src/app/manage/app.ts
index 53b6d401..e61ff0d8 100644
--- a/loaner/chrome_app/src/app/manage/app.ts
+++ b/loaner/chrome_app/src/app/manage/app.ts
@@ -13,7 +13,7 @@
// limitations under the License.
import {PlatformLocation} from '@angular/common';
-import {Component, NgModule, ViewChild, ViewEncapsulation} from '@angular/core';
+import {AfterViewInit, Component, NgModule, ViewChild, ViewEncapsulation} from '@angular/core';
import {BrowserModule} from '@angular/platform-browser';
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
import {NavigationEnd, Router, RouterModule, Routes} from '@angular/router';
@@ -21,7 +21,7 @@ import {NavigationEnd, Router, RouterModule, Routes} from '@angular/router';
import {DamagedModule} from '../../../../shared/components/damaged';
import {ExtendModule} from '../../../../shared/components/extend';
import {GuestModeModule} from '../../../../shared/components/guest';
-import {BACKGROUND_LOGO, BACKGROUND_LOGO_ENABLED, ConfigService} from '../../../../shared/config';
+import {BACKGROUND_LOGO, BACKGROUND_LOGO_ENABLED, CHROME_MODE, ConfigService} from '../../../../shared/config';
import {AnalyticsModule, AnalyticsService} from '../shared/analytics';
import {ChromeAppPlatformLocation,} from '../shared/chrome_app_platform_location';
import {HttpModule} from '../shared/http/http_module';
@@ -31,6 +31,7 @@ import {BottomNavModule, NavTab} from './shared/bottom_nav';
import {StatusComponent, StatusModule} from './status';
import {TroubleshootComponent, TroubleshootModule} from './troubleshoot';
+/** Represents the root management component. */
@Component({
encapsulation: ViewEncapsulation.None,
preserveWhitespaces: true,
@@ -38,12 +39,12 @@ import {TroubleshootComponent, TroubleshootModule} from './troubleshoot';
styleUrls: ['./app.scss'],
templateUrl: './app.ng.html',
})
-export class AppRoot {
+export class AppRoot implements AfterViewInit {
backgroundLogo = BACKGROUND_LOGO;
backgroundLogoEnabled = BACKGROUND_LOGO_ENABLED;
// Represents the analytics image in the body.
- @ViewChild('analytics') analyticsImg!: HTMLImageElement|null;
+ @ViewChild('analytics', {static: true}) analyticsImg!: HTMLImageElement|null;
navBarTabs: NavTab[] = [
{
@@ -73,7 +74,7 @@ export class AppRoot {
) {}
ngAfterViewInit() {
- if (this.config.analyticsEnabled) {
+ if (this.config.analyticsEnabled && this.config.isAnalyticsIdValid()) {
this.router.events.subscribe(route => {
if (route instanceof NavigationEnd) {
this.analyticsService
diff --git a/loaner/chrome_app/src/app/manage/faq/faq.ts b/loaner/chrome_app/src/app/manage/faq/faq.ts
index fe337df0..c596b34d 100644
--- a/loaner/chrome_app/src/app/manage/faq/faq.ts
+++ b/loaner/chrome_app/src/app/manage/faq/faq.ts
@@ -26,19 +26,22 @@ import * as marked from 'marked';
templateUrl: './faq.ng.html',
})
export class FaqComponent implements OnInit {
- sanitizedFaqContent!: string|null;
+ sanitizedFaqContent?: string|null;
constructor(
private readonly http: HttpClient,
private readonly sanitizer: DomSanitizer) {}
ngOnInit() {
+ const renderer = new marked.Renderer();
this.getFaq().subscribe((response) => {
+ renderer.link = (href, title, text) =>
+ `${text}`;
+ marked.setOptions({renderer});
this.sanitizedFaqContent =
this.sanitizer.sanitize(SecurityContext.HTML, marked(response));
});
}
-
/** Gets the FAQ from assets/faq.md file. */
getFaq(): Observable {
return this.http.get('./assets/faq.md', {'responseType': 'text'});
diff --git a/loaner/chrome_app/src/app/manage/faq/faq_test.ts b/loaner/chrome_app/src/app/manage/faq/faq_test.ts
index 4e4b7b08..11c23030 100644
--- a/loaner/chrome_app/src/app/manage/faq/faq_test.ts
+++ b/loaner/chrome_app/src/app/manage/faq/faq_test.ts
@@ -22,6 +22,7 @@ import {MaterialModule} from './material_module';
describe('FaqComponent', () => {
let fixture: ComponentFixture;
+ let httpService: HttpClient;
beforeEach(() => {
TestBed
@@ -35,10 +36,10 @@ describe('FaqComponent', () => {
})
.compileComponents();
fixture = TestBed.createComponent(FaqComponent);
+ httpService = TestBed.get(HttpClient);
});
it('should render markdown as HTML', () => {
- const httpService = TestBed.get(HttpClient);
const faqMock = `
# Heading 1
## Heading 2
@@ -59,4 +60,19 @@ You can do the following:
expect(fixture.debugElement.nativeElement.querySelector('li').textContent)
.toContain('This way');
});
+
+ it('should render links with a target of _blank', () => {
+ const faqMock = `[Test](https://google.com)`;
+ spyOn(httpService, 'get').and.returnValue(of(faqMock));
+ fixture.detectChanges();
+
+ expect(fixture.debugElement.nativeElement.querySelector('a').textContent)
+ .toContain('Test');
+ expect(fixture.debugElement.nativeElement.querySelector('a').getAttribute(
+ 'href'))
+ .toContain('https://google.com');
+ expect(fixture.debugElement.nativeElement.querySelector('a').getAttribute(
+ 'target'))
+ .toContain('_blank');
+ });
});
diff --git a/loaner/chrome_app/src/app/manage/faq/material_module.ts b/loaner/chrome_app/src/app/manage/faq/material_module.ts
index 619db599..f439f83d 100644
--- a/loaner/chrome_app/src/app/manage/faq/material_module.ts
+++ b/loaner/chrome_app/src/app/manage/faq/material_module.ts
@@ -13,7 +13,7 @@
// limitations under the License.
import {NgModule} from '@angular/core';
-import {MatCardModule} from '@angular/material';
+import {MatCardModule} from '@angular/material/card';
const MATERIAL_MODULES = [MatCardModule];
diff --git a/loaner/chrome_app/src/app/manage/main.ts b/loaner/chrome_app/src/app/manage/main.ts
index aee45f49..e7748009 100644
--- a/loaner/chrome_app/src/app/manage/main.ts
+++ b/loaner/chrome_app/src/app/manage/main.ts
@@ -12,16 +12,14 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import 'core-js/es6';
-import 'core-js/es6/array';
-import 'core-js/es6/function';
-import 'core-js/es6/map';
-import 'core-js/es6/number';
-import 'core-js/es6/object';
-import 'core-js/es6/reflect';
-import 'core-js/es6/string';
-import 'core-js/es6/symbol';
-import 'core-js/es7/reflect';
+import 'core-js/es/array';
+import 'core-js/es/function';
+import 'core-js/es/map';
+import 'core-js/es/number';
+import 'core-js/es/object';
+import 'core-js/es/string';
+import 'core-js/es/symbol';
+import 'core-js/proposals/reflect-metadata';
import 'zone.js/dist/zone';
import 'rxjs';
diff --git a/loaner/chrome_app/src/app/manage/shared/bottom_nav/bottom_nav_test.ts b/loaner/chrome_app/src/app/manage/shared/bottom_nav/bottom_nav_test.ts
index 2a5dae80..8d6fe8b5 100644
--- a/loaner/chrome_app/src/app/manage/shared/bottom_nav/bottom_nav_test.ts
+++ b/loaner/chrome_app/src/app/manage/shared/bottom_nav/bottom_nav_test.ts
@@ -63,7 +63,7 @@ describe('BottomNavComponent', () => {
`
})
class SimpleBottomNavTestApp {
- @ViewChild(BottomNavComponent) bottomNav!: BottomNavComponent;
+ @ViewChild(BottomNavComponent, {static: true}) bottomNav!: BottomNavComponent;
readonly navTabs = [
{
ariaLabel: 'Troubleshoot your device',
diff --git a/loaner/chrome_app/src/app/manage/shared/bottom_nav/material_module.ts b/loaner/chrome_app/src/app/manage/shared/bottom_nav/material_module.ts
index 9a0b21be..af31e3a0 100644
--- a/loaner/chrome_app/src/app/manage/shared/bottom_nav/material_module.ts
+++ b/loaner/chrome_app/src/app/manage/shared/bottom_nav/material_module.ts
@@ -13,7 +13,8 @@
// limitations under the License.
import {NgModule} from '@angular/core';
-import {MatIconModule, MatRippleModule} from '@angular/material';
+import {MatRippleModule} from '@angular/material/core';
+import {MatIconModule} from '@angular/material/icon';
const MATERIAL_MODULES = [
MatIconModule,
diff --git a/loaner/chrome_app/src/app/manage/status/material_module.ts b/loaner/chrome_app/src/app/manage/status/material_module.ts
index 9c8360c5..da1188a1 100644
--- a/loaner/chrome_app/src/app/manage/status/material_module.ts
+++ b/loaner/chrome_app/src/app/manage/status/material_module.ts
@@ -13,7 +13,11 @@
// limitations under the License.
import {NgModule} from '@angular/core';
-import {MatButtonModule, MatCardModule, MatDialogModule, MatIconModule, MatTooltipModule} from '@angular/material';
+import {MatButtonModule} from '@angular/material/button';
+import {MatCardModule} from '@angular/material/card';
+import {MatDialogModule} from '@angular/material/dialog';
+import {MatIconModule} from '@angular/material/icon';
+import {MatTooltipModule} from '@angular/material/tooltip';
const MATERIAL_MODULES = [
MatButtonModule,
diff --git a/loaner/chrome_app/src/app/manage/status/status.ng.html b/loaner/chrome_app/src/app/manage/status/status.ng.html
index 736a0afb..eda3d877 100644
--- a/loaner/chrome_app/src/app/manage/status/status.ng.html
+++ b/loaner/chrome_app/src/app/manage/status/status.ng.html
@@ -10,7 +10,8 @@
+ [device]="device"
+ [showImage]="false">
diff --git a/loaner/chrome_app/src/app/manage/status/status.ts b/loaner/chrome_app/src/app/manage/status/status.ts
index 4826c202..c5dd4c06 100644
--- a/loaner/chrome_app/src/app/manage/status/status.ts
+++ b/loaner/chrome_app/src/app/manage/status/status.ts
@@ -13,7 +13,7 @@
// limitations under the License.
import {Component, OnInit} from '@angular/core';
-import {MatDialog} from '@angular/material';
+import {MatDialog} from '@angular/material/dialog';
import * as moment from 'moment';
import {Damaged} from '../../../../../shared/components/damaged';
@@ -105,6 +105,7 @@ export class StatusComponent extends LoaderView implements OnInit {
() => {
this.extend.finished(this.newReturnDate);
this.device.dueDate = this.newReturnDate;
+ this.device.overdue = false;
},
error => {
this.loading = false;
diff --git a/loaner/chrome_app/src/app/manage/status/status_test.ts b/loaner/chrome_app/src/app/manage/status/status_test.ts
index 07156a06..1bb167c7 100644
--- a/loaner/chrome_app/src/app/manage/status/status_test.ts
+++ b/loaner/chrome_app/src/app/manage/status/status_test.ts
@@ -15,7 +15,7 @@
import {HttpClient} from '@angular/common/http';
import {HttpClientTestingModule} from '@angular/common/http/testing';
import {ComponentFixture, TestBed} from '@angular/core/testing';
-import {MatDialogRef} from '@angular/material';
+import {MatDialogRef} from '@angular/material/dialog';
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
import {RouterTestingModule} from '@angular/router/testing';
import * as moment from 'moment';
@@ -106,11 +106,11 @@ describe('StatusComponent', () => {
spyOn(loan, 'getDevice').and.returnValue(of(new Device(testDeviceInfo)));
app.setLoanInfo();
fixture.detectChanges();
- expect(app.device.dueDate).toEqual(testDeviceInfo.due_date);
- expect(app.device.maxExtendDate).toEqual(testDeviceInfo.max_extend_date);
- expect(app.device.givenName).toEqual(testDeviceInfo.given_name);
- expect(app.device.guestAllowed).toEqual(testDeviceInfo.guest_permitted);
- expect(app.device.guestEnabled).toEqual(testDeviceInfo.guest_enabled);
+ expect(app.device.dueDate).toEqual((testDeviceInfo.due_date!));
+ expect(app.device.maxExtendDate).toEqual((testDeviceInfo.max_extend_date!));
+ expect(app.device.givenName).toEqual((testDeviceInfo.given_name!));
+ expect(app.device.guestAllowed).toEqual((testDeviceInfo.guest_permitted!));
+ expect(app.device.guestEnabled).toEqual((testDeviceInfo.guest_enabled!));
});
it('renders content on the page', () => {
@@ -118,6 +118,7 @@ describe('StatusComponent', () => {
app.device.assetTag = 'asset tag';
fixture.detectChanges();
expect(fixture.nativeElement.textContent)
- .toContain('Please return this device by:');
+ .toContain(
+ 'Please return your loaner on time for your fellow colleague');
});
});
diff --git a/loaner/chrome_app/src/app/manage/troubleshoot/material_module.ts b/loaner/chrome_app/src/app/manage/troubleshoot/material_module.ts
index 7a77e1a1..17808b16 100644
--- a/loaner/chrome_app/src/app/manage/troubleshoot/material_module.ts
+++ b/loaner/chrome_app/src/app/manage/troubleshoot/material_module.ts
@@ -13,11 +13,16 @@
// limitations under the License.
import {NgModule} from '@angular/core';
-import {MatCardModule, MatIconModule} from '@angular/material';
+import {MatButtonModule} from '@angular/material/button';
+import {MatCardModule} from '@angular/material/card';
+import {MatIconModule} from '@angular/material/icon';
+import {MatTooltipModule} from '@angular/material/tooltip';
const MATERIAL_MODULES = [
+ MatButtonModule,
MatCardModule,
MatIconModule,
+ MatTooltipModule,
];
@NgModule({
diff --git a/loaner/chrome_app/src/app/manage/troubleshoot/troubleshoot.ng.html b/loaner/chrome_app/src/app/manage/troubleshoot/troubleshoot.ng.html
index 7d10b781..bbe41f3e 100644
--- a/loaner/chrome_app/src/app/manage/troubleshoot/troubleshoot.ng.html
+++ b/loaner/chrome_app/src/app/manage/troubleshoot/troubleshoot.ng.html
@@ -6,28 +6,33 @@
diff --git a/loaner/chrome_app/src/app/manage/troubleshoot/troubleshoot.scss b/loaner/chrome_app/src/app/manage/troubleshoot/troubleshoot.scss
index 89bc14bc..f3351c83 100644
--- a/loaner/chrome_app/src/app/manage/troubleshoot/troubleshoot.scss
+++ b/loaner/chrome_app/src/app/manage/troubleshoot/troubleshoot.scss
@@ -1,7 +1,3 @@
-.icon-padding {
- margin-left: 12px;
-}
-
.troubleshoot-card {
left: 50%;
text-align: center;
diff --git a/loaner/chrome_app/src/app/manage/troubleshoot/troubleshoot.ts b/loaner/chrome_app/src/app/manage/troubleshoot/troubleshoot.ts
index b0eda98c..963c8c89 100644
--- a/loaner/chrome_app/src/app/manage/troubleshoot/troubleshoot.ts
+++ b/loaner/chrome_app/src/app/manage/troubleshoot/troubleshoot.ts
@@ -14,6 +14,7 @@
import {Component} from '@angular/core';
import {IT_CONTACT_EMAIL, IT_CONTACT_PHONE, IT_CONTACT_WEBSITE, TROUBLESHOOTING_INFORMATION} from '../../../../../shared/config';
+import {Background} from '../../shared/background_service';
@Component({
host: {
@@ -29,7 +30,7 @@ export class TroubleshootComponent {
contactWebsite?: string;
troubleshootingInformation: string;
- constructor() {
+ constructor(private readonly background: Background) {
if (IT_CONTACT_EMAIL.length > 0) {
this.contactEmail = IT_CONTACT_EMAIL;
}
@@ -49,4 +50,9 @@ export class TroubleshootComponent {
'Contact your IT department for assistance.';
}
}
+
+ /** Opens the debug view of the Chrome App. */
+ openDebugView() {
+ this.background.openView('debug', true);
+ }
}
diff --git a/loaner/chrome_app/src/app/manage/troubleshoot/troubleshoot_test.ts b/loaner/chrome_app/src/app/manage/troubleshoot/troubleshoot_test.ts
index ce480b5e..59496322 100644
--- a/loaner/chrome_app/src/app/manage/troubleshoot/troubleshoot_test.ts
+++ b/loaner/chrome_app/src/app/manage/troubleshoot/troubleshoot_test.ts
@@ -15,6 +15,8 @@
import {ComponentFixture, TestBed} from '@angular/core/testing';
import {FlexLayoutModule} from '@angular/flex-layout';
+import {Background, BackgroundMock} from '../../shared/background_service';
+
import {TroubleshootComponent} from './index';
import {MaterialModule} from './material_module';
@@ -30,6 +32,7 @@ describe('TroubleshootComponent', () => {
FlexLayoutModule,
MaterialModule,
],
+ providers: [{provide: Background, useClass: BackgroundMock}],
})
.compileComponents();
});
@@ -39,11 +42,11 @@ describe('TroubleshootComponent', () => {
app = fixture.debugElement.componentInstance;
});
- it('should render content on the page', () => {
+ it('renders content on the page', () => {
expect(fixture.nativeElement.textContent).toContain('Having issues?');
});
- it('should show the contact information on the page', () => {
+ it('shows the contact information on the page', () => {
app.contactEmail = 'support@example.com';
app.contactPhone = ['12345678', '910111213'];
app.contactWebsite = 'support.example.com';
@@ -56,4 +59,13 @@ describe('TroubleshootComponent', () => {
expect(fixture.nativeElement.textContent).toContain('Contact IT');
});
+ it('opens the debug view when the button is clicked', () => {
+ const bg = TestBed.get(Background);
+ spyOn(bg, 'openView');
+ const debugButton =
+ fixture.debugElement.nativeElement.querySelector('#debug');
+ debugButton.click();
+ expect(bg.openView).toHaveBeenCalledWith('debug', true);
+ });
+
});
diff --git a/loaner/chrome_app/src/app/offboarding/app.ts b/loaner/chrome_app/src/app/offboarding/app.ts
index 21b47634..c7a69848 100644
--- a/loaner/chrome_app/src/app/offboarding/app.ts
+++ b/loaner/chrome_app/src/app/offboarding/app.ts
@@ -24,7 +24,7 @@ import {LoanerTextCardModule} from '../../../../shared/components/info_card';
import {LoanerProgressModule} from '../../../../shared/components/progress';
import {FlowsEnum, LoanerReturnInstructions, LoanerReturnInstructionsModule} from '../../../../shared/components/return_instructions';
import {Survey, SurveyAnswer, SurveyComponent, SurveyModule, SurveyType} from '../../../../shared/components/survey';
-import {BACKGROUND_LOGO, BACKGROUND_LOGO_ENABLED, ConfigService, PROGRAM_NAME, RETURN_ANIMATION_ALT_TEXT, RETURN_ANIMATION_ENABLED, RETURN_ANIMATION_URL, TOOLBAR_ICON, TOOLBAR_ICON_ENABLED} from '../../../../shared/config';
+import {BACKGROUND_LOGO, BACKGROUND_LOGO_ENABLED, CHROME_MODE, ConfigService, PROGRAM_NAME, RETURN_ANIMATION_ALT_TEXT, RETURN_ANIMATION_ENABLED, RETURN_ANIMATION_URL, TOOLBAR_ICON, TOOLBAR_ICON_ENABLED} from '../../../../shared/config';
import {ApiConfig, apiConfigFactory} from '../../../../shared/services/api_config';
import {NetworkService} from '../../../../shared/services/network_service';
import {AnalyticsModule, AnalyticsService} from '../shared/analytics';
@@ -85,8 +85,9 @@ export class AppRoot implements AfterViewInit, OnInit {
currentStep = 0;
maxStep = 0;
readonly steps = STEPS;
- @ViewChild(LoanerFlowSequence) flowSequence!: LoanerFlowSequence;
- @ViewChild(LoanerFlowSequenceButtons)
+ @ViewChild(LoanerFlowSequence, {static: true})
+ flowSequence!: LoanerFlowSequence;
+ @ViewChild(LoanerFlowSequenceButtons, {static: true})
flowSequenceButtons!: LoanerFlowSequenceButtons;
surveyAnswer!: SurveyAnswer;
@@ -95,12 +96,12 @@ export class AppRoot implements AfterViewInit, OnInit {
returnCompleted = false;
// Flow components to be manipulated.
- @ViewChild(SurveyComponent) surveyComponent!: SurveyComponent;
- @ViewChild(LoanerReturnInstructions)
+ @ViewChild(SurveyComponent, {static: true}) surveyComponent!: SurveyComponent;
+ @ViewChild(LoanerReturnInstructions, {static: true})
returnInstructions!: LoanerReturnInstructions;
// Represents the analytics image in the body.
- @ViewChild('analytics') analyticsImg!: HTMLImageElement|null;
+ @ViewChild('analytics', {static: true}) analyticsImg!: HTMLImageElement|null;
// Text to be populated on an info card for logout step.
logoutPage = {
@@ -138,7 +139,7 @@ device to your nearest shelf as soon as possible.`,
* @param view represents the current page/view.
*/
private updateAnalytics(view: string) {
- if (this.config.analyticsEnabled) {
+ if (this.config.analyticsEnabled && this.config.isAnalyticsIdValid()) {
this.analyticsService.sendView('offboarding', view).subscribe(url => {
if (this.analyticsImg) {
this.analyticsImg.src = window.URL.createObjectURL(url);
@@ -176,8 +177,9 @@ device to your nearest shelf as soon as possible.`,
});
// If there is no network connection, disable the flow buttons.
- this.networkService.internetStatus.subscribe(
- status => this.flowSequenceButtons.allowButtonClick = status);
+ this.networkService.internetStatus.subscribe(status => {
+ this.flowSequenceButtons.allowButtonClick = status;
+ });
// Subscribe to flow state
this.flowSequence.flowState.subscribe(state => {
@@ -279,7 +281,9 @@ experience. This will help us improve and maintain the loaner program.`;
* Handle changes from survey related items including the SurveyComponent.
*/
surveyListener() {
- this.survey.answer.subscribe(val => this.surveyAnswer = val);
+ this.survey.answer.subscribe(val => {
+ this.surveyAnswer = val;
+ });
this.surveyComponent.surveyError.subscribe(val => {
const message = `We are unable to retrieve the survey at the moment,
continue using the app as normal.`;
diff --git a/loaner/chrome_app/src/app/offboarding/app_test.ts b/loaner/chrome_app/src/app/offboarding/app_test.ts
index a875a0fc..384e82a4 100644
--- a/loaner/chrome_app/src/app/offboarding/app_test.ts
+++ b/loaner/chrome_app/src/app/offboarding/app_test.ts
@@ -96,7 +96,7 @@ describe('Offboarding AppRoot', () => {
});
it('sends survey upon request to close the application', () => {
- const surveyService: Survey = TestBed.get(Survey);
+ const surveyService = TestBed.get(Survey);
spyOn(surveyService, 'submitSurvey').and.callThrough();
const fakeSurveyData = {
more_info_text: 'Yes, this is more info.',
@@ -117,7 +117,7 @@ describe('Offboarding AppRoot', () => {
it('should close the offboarding view and NOT send the survey', () => {
expect(app.surveySent).toBeFalsy();
expect(app.surveyAnswer).toBeFalsy();
- const bg: Background = TestBed.get(Background);
+ const bg = TestBed.get(Background);
spyOn(bg, 'closeView');
app.closeApplication();
fixture.detectChanges();
diff --git a/loaner/chrome_app/src/app/offboarding/main.ts b/loaner/chrome_app/src/app/offboarding/main.ts
index aee45f49..e7748009 100644
--- a/loaner/chrome_app/src/app/offboarding/main.ts
+++ b/loaner/chrome_app/src/app/offboarding/main.ts
@@ -12,16 +12,14 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import 'core-js/es6';
-import 'core-js/es6/array';
-import 'core-js/es6/function';
-import 'core-js/es6/map';
-import 'core-js/es6/number';
-import 'core-js/es6/object';
-import 'core-js/es6/reflect';
-import 'core-js/es6/string';
-import 'core-js/es6/symbol';
-import 'core-js/es7/reflect';
+import 'core-js/es/array';
+import 'core-js/es/function';
+import 'core-js/es/map';
+import 'core-js/es/number';
+import 'core-js/es/object';
+import 'core-js/es/string';
+import 'core-js/es/symbol';
+import 'core-js/proposals/reflect-metadata';
import 'zone.js/dist/zone';
import 'rxjs';
diff --git a/loaner/chrome_app/src/app/offboarding/material_module.ts b/loaner/chrome_app/src/app/offboarding/material_module.ts
index c87d4621..2ec439f9 100644
--- a/loaner/chrome_app/src/app/offboarding/material_module.ts
+++ b/loaner/chrome_app/src/app/offboarding/material_module.ts
@@ -13,7 +13,10 @@
// limitations under the License.
import {NgModule} from '@angular/core';
-import {MatButtonModule, MatIconModule, MatToolbarModule, MatTooltipModule} from '@angular/material';
+import {MatButtonModule} from '@angular/material/button';
+import {MatIconModule} from '@angular/material/icon';
+import {MatToolbarModule} from '@angular/material/toolbar';
+import {MatTooltipModule} from '@angular/material/tooltip';
const MATERIAL_MODULES =
[MatButtonModule, MatIconModule, MatToolbarModule, MatTooltipModule];
diff --git a/loaner/chrome_app/src/app/onboarding/app.ts b/loaner/chrome_app/src/app/onboarding/app.ts
index 92f3b6fc..788ed667 100644
--- a/loaner/chrome_app/src/app/onboarding/app.ts
+++ b/loaner/chrome_app/src/app/onboarding/app.ts
@@ -17,13 +17,15 @@ import {AfterViewInit, Component, NgModule, OnInit, ViewChild, ViewEncapsulation
import {FlexLayoutModule} from '@angular/flex-layout';
import {BrowserModule, Title} from '@angular/platform-browser';
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
+import {of} from 'rxjs';
+import {switchMap} from 'rxjs/operators';
import {AnimationMenuModule} from '../../../../shared/components/animation_menu';
import {FlowState, LoanerFlowSequence, LoanerFlowSequenceButtons, LoanerFlowSequenceModule, Step} from '../../../../shared/components/flow_sequence';
import {LoanerProgressModule} from '../../../../shared/components/progress';
import {FlowsEnum, LoanerReturnInstructions, LoanerReturnInstructionsModule} from '../../../../shared/components/return_instructions';
import {Survey, SurveyAnswer, SurveyComponent, SurveyModule, SurveyType} from '../../../../shared/components/survey';
-import {BACKGROUND_LOGO, BACKGROUND_LOGO_ENABLED, ConfigService, PROGRAM_NAME, RETURN_ANIMATION_ALT_TEXT, RETURN_ANIMATION_ENABLED, RETURN_ANIMATION_URL, TOOLBAR_ICON, TOOLBAR_ICON_ENABLED} from '../../../../shared/config';
+import {BACKGROUND_LOGO, BACKGROUND_LOGO_ENABLED, CHROME_MODE, ConfigService, PROGRAM_NAME, RETURN_ANIMATION_ALT_TEXT, RETURN_ANIMATION_ENABLED, RETURN_ANIMATION_URL, TOOLBAR_ICON, TOOLBAR_ICON_ENABLED} from '../../../../shared/config';
import {ApiConfig, apiConfigFactory} from '../../../../shared/services/api_config';
import {NetworkService} from '../../../../shared/services/network_service';
import {AnalyticsModule, AnalyticsService} from '../shared/analytics';
@@ -31,6 +33,7 @@ import {Background} from '../shared/background_service';
import {ChromeAppPlatformLocation,} from '../shared/chrome_app_platform_location';
import {FailAction, FailType, Failure, FailureModule} from '../shared/failure';
import {HttpModule} from '../shared/http/http_module';
+import {Loan} from '../shared/loan';
import {ReturnDateService} from '../shared/return_date_service';
import {MaterialModule} from './material_module';
@@ -84,27 +87,30 @@ export class AppRoot implements AfterViewInit, OnInit {
currentStep = 0;
maxStep = 0;
readonly steps = STEPS;
- @ViewChild(LoanerFlowSequence) flowSequence!: LoanerFlowSequence;
- @ViewChild(LoanerFlowSequenceButtons)
+ @ViewChild(LoanerFlowSequence, {static: true})
+ flowSequence!: LoanerFlowSequence;
+ @ViewChild(LoanerFlowSequenceButtons, {static: true})
flowSequenceButtons!: LoanerFlowSequenceButtons;
surveyAnswer!: SurveyAnswer;
surveySent = false;
// Flow components to be manipulated.
- @ViewChild(WelcomeComponent) welcomeComponent!: WelcomeComponent;
- @ViewChild(SurveyComponent) surveyComponent!: SurveyComponent;
- @ViewChild(ReturnComponent) returnComponent!: ReturnComponent;
- @ViewChild(LoanerReturnInstructions)
+ @ViewChild(WelcomeComponent, {static: true})
+ welcomeComponent!: WelcomeComponent;
+ @ViewChild(SurveyComponent, {static: true}) surveyComponent!: SurveyComponent;
+ @ViewChild(ReturnComponent, {static: true}) returnComponent!: ReturnComponent;
+ @ViewChild(LoanerReturnInstructions, {static: true})
returnInstructions!: LoanerReturnInstructions;
// Represents the analytics image in the body.
- @ViewChild('analytics') analyticsImg!: HTMLImageElement|null;
+ @ViewChild('analytics', {static: true}) analyticsImg!: HTMLImageElement|null;
constructor(
private readonly analyticsService: AnalyticsService,
private readonly bg: Background,
private readonly config: ConfigService,
+ private readonly loan: Loan,
private readonly failure: Failure,
private readonly networkService: NetworkService,
private readonly returnService: ReturnDateService,
@@ -122,7 +128,7 @@ export class AppRoot implements AfterViewInit, OnInit {
* @param view represents the current page/view.
*/
private updateAnalytics(view: string) {
- if (this.config.analyticsEnabled) {
+ if (this.config.analyticsEnabled && this.config.isAnalyticsIdValid()) {
this.analyticsService.sendView('onboarding', view).subscribe(url => {
if (this.analyticsImg) {
this.analyticsImg.src = window.URL.createObjectURL(url);
@@ -155,9 +161,21 @@ export class AppRoot implements AfterViewInit, OnInit {
this.returnInstructions.animationURL = RETURN_ANIMATION_URL;
// Listen for flow finished
- this.flowSequenceButtons.finished.subscribe(finished => {
- if (finished) this.launchManageView();
- });
+ this.flowSequenceButtons.finished
+ .pipe(switchMap(finished => {
+ return finished ? this.loan.completeOnboard() : of(false);
+ }))
+ .subscribe(
+ () => {
+ this.launchManageView();
+ },
+ error => {
+ const message =
+ 'Something happened when completing the onboarding.';
+ this.failure.register(
+ message, FailType.Other, FailAction.Quit, error);
+ this.launchManageView();
+ });
// Listen for changes on the valid date observable.
this.returnService.validDate.subscribe(val => {
@@ -165,8 +183,9 @@ export class AppRoot implements AfterViewInit, OnInit {
});
// If there is no network connection, disable the flow buttons.
- this.networkService.internetStatus.subscribe(
- status => this.flowSequenceButtons.allowButtonClick = status);
+ this.networkService.internetStatus.subscribe(status => {
+ this.flowSequenceButtons.allowButtonClick = status;
+ });
// Subscribe to flow state
this.flowSequence.flowState.subscribe(state => {
@@ -280,7 +299,9 @@ ensure we have an appropriate amount of loaners.`;
* Handle changes from survey related items including the SurveyComponent.
*/
surveyListener() {
- this.survey.answer.subscribe(val => this.surveyAnswer = val);
+ this.survey.answer.subscribe(val => {
+ this.surveyAnswer = val;
+ });
this.surveyComponent.surveyError.subscribe(val => {
const message = `We are unable to retrieve the survey at the moment,
continue using the app as normal.`;
diff --git a/loaner/chrome_app/src/app/onboarding/app_test.ts b/loaner/chrome_app/src/app/onboarding/app_test.ts
index 9a741126..a8e72d37 100644
--- a/loaner/chrome_app/src/app/onboarding/app_test.ts
+++ b/loaner/chrome_app/src/app/onboarding/app_test.ts
@@ -14,6 +14,7 @@
import {HttpClientTestingModule} from '@angular/common/http/testing';
import {ComponentFixture, TestBed} from '@angular/core/testing';
+import {of} from 'rxjs';
import {AnimationMenuService} from '../../../../shared/components/animation_menu';
import {Survey, SurveyMock} from '../../../../shared/components/survey';
@@ -86,14 +87,14 @@ describe('Onboarding AppRoot', () => {
expect(app.currentStep).toBe(1);
});
- it('FAILs to go forward when canProceed is false', () => {
+ it('fails to go forward when canProceed is false', () => {
app.flowSequenceButtons.canProceed = false;
expect(app.currentStep).toBe(0);
app.flowSequenceButtons.goForward();
expect(app.currentStep).toBe(0);
});
- it('should FAIL to go forward when allowButtonClick is false', () => {
+ it('fails to go forward when allowButtonClick is false', () => {
app.flowSequenceButtons.allowButtonClick = false;
expect(app.currentStep).toBe(0);
app.flowSequenceButtons.goForward();
@@ -101,9 +102,9 @@ describe('Onboarding AppRoot', () => {
});
- it('should send survey and update surveySent value', () => {
+ it('sends survey and update surveySent value', () => {
expect(app.surveySent).toBeFalsy();
- const surveyService: Survey = TestBed.get(Survey);
+ const surveyService = TestBed.get(Survey);
spyOn(surveyService, 'submitSurvey').and.callThrough();
const fakeSurveyData = {
more_info_text: 'Yes, this is more info.',
@@ -121,10 +122,20 @@ describe('Onboarding AppRoot', () => {
expect(surveyService.submitSurvey).toHaveBeenCalledWith(fakeSurveyData);
});
- it('should open the manage view and NOT send the survey', () => {
+ it('calls completeOnboard API when finishing all steps', () => {
+ const surveyService: Survey = TestBed.get(Survey);
+ spyOn(surveyService, 'submitSurvey').and.callThrough();
+ const loan: Loan = TestBed.get(Loan);
+ spyOn(loan, 'completeOnboard').and.returnValue(of());
+ app.flowSequenceButtons.goForward();
+ app.flowSequenceButtons.finishFlow();
+ expect(loan.completeOnboard).toHaveBeenCalled();
+ });
+
+ it('opens the manage view and NOT send the survey', () => {
expect(app.surveySent).toBeFalsy();
expect(app.surveyAnswer).toBeFalsy();
- const bg: Background = TestBed.get(Background);
+ const bg = TestBed.get(Background);
spyOn(bg, 'openView');
app.launchManageView();
fixture.detectChanges();
diff --git a/loaner/chrome_app/src/app/onboarding/main.ts b/loaner/chrome_app/src/app/onboarding/main.ts
index aee45f49..e7748009 100644
--- a/loaner/chrome_app/src/app/onboarding/main.ts
+++ b/loaner/chrome_app/src/app/onboarding/main.ts
@@ -12,16 +12,14 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import 'core-js/es6';
-import 'core-js/es6/array';
-import 'core-js/es6/function';
-import 'core-js/es6/map';
-import 'core-js/es6/number';
-import 'core-js/es6/object';
-import 'core-js/es6/reflect';
-import 'core-js/es6/string';
-import 'core-js/es6/symbol';
-import 'core-js/es7/reflect';
+import 'core-js/es/array';
+import 'core-js/es/function';
+import 'core-js/es/map';
+import 'core-js/es/number';
+import 'core-js/es/object';
+import 'core-js/es/string';
+import 'core-js/es/symbol';
+import 'core-js/proposals/reflect-metadata';
import 'zone.js/dist/zone';
import 'rxjs';
diff --git a/loaner/chrome_app/src/app/onboarding/material_module.ts b/loaner/chrome_app/src/app/onboarding/material_module.ts
index 95673c34..cbc3e1cf 100644
--- a/loaner/chrome_app/src/app/onboarding/material_module.ts
+++ b/loaner/chrome_app/src/app/onboarding/material_module.ts
@@ -13,7 +13,7 @@
// limitations under the License.
import {NgModule} from '@angular/core';
-import {MatToolbarModule} from '@angular/material';
+import {MatToolbarModule} from '@angular/material/toolbar';
const MATERIAL_MODULES = [MatToolbarModule];
diff --git a/loaner/chrome_app/src/app/onboarding/return/material_module.ts b/loaner/chrome_app/src/app/onboarding/return/material_module.ts
index 3c503c35..564e6736 100644
--- a/loaner/chrome_app/src/app/onboarding/return/material_module.ts
+++ b/loaner/chrome_app/src/app/onboarding/return/material_module.ts
@@ -13,7 +13,11 @@
// limitations under the License.
import {NgModule} from '@angular/core';
-import {MatButtonModule, MatCardModule, MatDatepickerModule, MatInputModule, MatNativeDateModule} from '@angular/material';
+import {MatButtonModule} from '@angular/material/button';
+import {MatCardModule} from '@angular/material/card';
+import {MatNativeDateModule} from '@angular/material/core';
+import {MatDatepickerModule} from '@angular/material/datepicker';
+import {MatInputModule} from '@angular/material/input';
const MATERIAL_MODULES = [
MatButtonModule,
diff --git a/loaner/chrome_app/src/app/onboarding/return/return.ng.html b/loaner/chrome_app/src/app/onboarding/return/return.ng.html
index 240620d2..91fb6242 100644
--- a/loaner/chrome_app/src/app/onboarding/return/return.ng.html
+++ b/loaner/chrome_app/src/app/onboarding/return/return.ng.html
@@ -5,10 +5,10 @@
- Choose a return date
+ Choose your return date
- Please select an anticipated return date. You can always extend this later if you need to.
+ Please select an anticipated return date.You can always extend this later if you need to.
diff --git a/loaner/chrome_app/src/app/onboarding/return/return_test.ts b/loaner/chrome_app/src/app/onboarding/return/return_test.ts
index 7e80ae98..ed6deb32 100644
--- a/loaner/chrome_app/src/app/onboarding/return/return_test.ts
+++ b/loaner/chrome_app/src/app/onboarding/return/return_test.ts
@@ -97,7 +97,7 @@ describe('ReturnComponent', () => {
spyOn(loan, 'getDevice').and.returnValue(of(new Device(testDeviceInfo)));
app.ready();
fixture.detectChanges();
- expect(app.device.dueDate).toEqual(testDeviceInfo.due_date);
+ expect(app.device.dueDate).toEqual((testDeviceInfo.due_date!));
});
it('fails to retrieve the loan information and provides a helpful card',
diff --git a/loaner/chrome_app/src/app/onboarding/welcome/material_module.ts b/loaner/chrome_app/src/app/onboarding/welcome/material_module.ts
index 7aa4905a..17808b16 100644
--- a/loaner/chrome_app/src/app/onboarding/welcome/material_module.ts
+++ b/loaner/chrome_app/src/app/onboarding/welcome/material_module.ts
@@ -13,7 +13,10 @@
// limitations under the License.
import {NgModule} from '@angular/core';
-import {MatButtonModule, MatCardModule, MatIconModule, MatTooltipModule} from '@angular/material';
+import {MatButtonModule} from '@angular/material/button';
+import {MatCardModule} from '@angular/material/card';
+import {MatIconModule} from '@angular/material/icon';
+import {MatTooltipModule} from '@angular/material/tooltip';
const MATERIAL_MODULES = [
MatButtonModule,
diff --git a/loaner/chrome_app/src/app/onboarding/welcome/welcome.ts b/loaner/chrome_app/src/app/onboarding/welcome/welcome.ts
index b9d14c7b..a49fceb5 100644
--- a/loaner/chrome_app/src/app/onboarding/welcome/welcome.ts
+++ b/loaner/chrome_app/src/app/onboarding/welcome/welcome.ts
@@ -13,7 +13,7 @@
// limitations under the License.
import {Component, ElementRef, OnInit, ViewChild} from '@angular/core';
-import {MatDialog} from '@angular/material';
+import {MatDialog} from '@angular/material/dialog';
import {AnimationMenuComponent} from '../../../../../shared/components/animation_menu';
import {PROGRAM_NAME, WELCOME_ANIMATATION_ALT_TEXT, WELCOME_ANIMATION_ENABLED, WELCOME_ANIMATION_URL} from '../../../../../shared/config';
@@ -26,7 +26,7 @@ import {AnimationMenuService} from '../../../../../shared/services/animation_men
templateUrl: './welcome.ng.html',
})
export class WelcomeComponent implements OnInit {
- @ViewChild('welcomeAnimation') animationElement!: ElementRef;
+ @ViewChild('welcomeAnimation', {static: false}) animationElement!: ElementRef;
playbackRate!: number;
programName: string = PROGRAM_NAME;
diff --git a/loaner/chrome_app/src/app/onboarding/welcome/welcome_test.ts b/loaner/chrome_app/src/app/onboarding/welcome/welcome_test.ts
index 15e6414e..d14e1fdd 100644
--- a/loaner/chrome_app/src/app/onboarding/welcome/welcome_test.ts
+++ b/loaner/chrome_app/src/app/onboarding/welcome/welcome_test.ts
@@ -13,8 +13,7 @@
// limitations under the License.
import {ComponentFixture, TestBed} from '@angular/core/testing';
-import {MatDialog, MatDialogRef} from '@angular/material';
-import {By} from '@angular/platform-browser';
+import {MatDialog, MatDialogRef} from '@angular/material/dialog';
import {AnimationMenuService} from '../../../../../shared/services/animation_menu_service';
import {AnimationMenuServiceMock} from '../../../../../shared/testing/mocks';
@@ -69,9 +68,10 @@ describe('WelcomeComponent', () => {
component.welcomeAnimationEnabled = true;
fixture.detectChanges();
expect(
- fixture.debugElement.query(By.css('.welcome-animation')).nativeElement)
+ fixture.debugElement.nativeElement.querySelector('.welcome-animation'))
.toBeTruthy();
- expect(fixture.debugElement.query(By.css('.welcome-card'))).toBeFalsy();
+ expect(fixture.debugElement.nativeElement.querySelector('.welcome-card'))
+ .toBeFalsy();
});
it('should have a playback rate of 100% for the welcome animation', () => {
@@ -90,17 +90,18 @@ describe('WelcomeComponent', () => {
it('should render welcome text instead of animation', () => {
component.welcomeAnimationEnabled = false;
fixture.detectChanges();
- expect(fixture.debugElement.query(By.css('.welcome-card')).nativeElement)
+ expect(fixture.debugElement.nativeElement.querySelector('.welcome-card'))
.toBeTruthy();
- expect(fixture.debugElement.query(By.css('.welcome-animation')))
+ expect(
+ fixture.debugElement.nativeElement.querySelector('.welcome-animation'))
.toBeFalsy();
});
it('should show the welcome text', () => {
component.welcomeAnimationEnabled = false;
fixture.detectChanges();
- expect(fixture.debugElement.query(By.css('.welcome-card'))
- .nativeElement.textContent)
+ expect(fixture.debugElement.nativeElement.querySelector('.welcome-card')
+ .textContent)
.toContain('Let\'s get started');
});
});
diff --git a/loaner/chrome_app/src/app/shared/analytics/analytics.ts b/loaner/chrome_app/src/app/shared/analytics/analytics.ts
index 57cbe5fb..b9ba6389 100644
--- a/loaner/chrome_app/src/app/shared/analytics/analytics.ts
+++ b/loaner/chrome_app/src/app/shared/analytics/analytics.ts
@@ -56,7 +56,8 @@ export class AnalyticsService {
return chars.join('');
}
- retrieveUuid(): Observable {
+ /** Retrieves the stored UUID or requests to generate one. */
+ retrieveUuid(): Observable<{}> {
return this.storage.local.get('analyticsID').pipe(tap(analyticsID => {
if (!analyticsID) {
const generatedId = this.generateUuid();
@@ -82,7 +83,10 @@ export class AnalyticsService {
responseType: 'blob' as 'json',
};
const structuredView = `chrome_app/${flow}${pageView}`;
- if (this.config.analyticsEnabled) {
+ const appVersion = chrome.runtime.getManifest().version ?
+ chrome.runtime.getManifest().version :
+ 'unknown';
+ if (this.config.analyticsEnabled && this.config.isAnalyticsIdValid()) {
// Confirm that cid is defined, otherwise skip it until it is defined.
return this.retrieveUuid().pipe(
skipWhile(cid => cid === undefined),
@@ -90,7 +94,8 @@ export class AnalyticsService {
cid => this.http.get(
`https://www.google-analytics.com/collect?payload_data&cid=${
cid}&dp=${structuredView}&t=pageview&tid=${
- this.config.analyticsId}&v=1`,
+ this.config.analyticsId}&v=1&an=chrome_app&av=${
+ appVersion}`,
httpOptions)));
}
return new Observable();
diff --git a/loaner/chrome_app/src/app/shared/background_service.ts b/loaner/chrome_app/src/app/shared/background_service.ts
index 77227985..e6942175 100644
--- a/loaner/chrome_app/src/app/shared/background_service.ts
+++ b/loaner/chrome_app/src/app/shared/background_service.ts
@@ -62,6 +62,7 @@ export class Background {
setAlwaysOnTop(id: string, value: boolean) {
chrome.app.window.get(id).setAlwaysOnTop(value);
}
+
}
/**
@@ -85,4 +86,5 @@ export class BackgroundMock implements Background {
setAlwaysOnTop(id: string, value: boolean) {
console.info(`Set value of alwaysOnTop for the view: ${id} to ${value}`);
}
+
}
diff --git a/loaner/chrome_app/src/app/shared/chrome_app_platform_location.ts b/loaner/chrome_app/src/app/shared/chrome_app_platform_location.ts
index 90c362aa..f2ba8246 100644
--- a/loaner/chrome_app/src/app/shared/chrome_app_platform_location.ts
+++ b/loaner/chrome_app/src/app/shared/chrome_app_platform_location.ts
@@ -13,11 +13,13 @@
// limitations under the License.
import {LocationChangeListener, PlatformLocation} from '@angular/common';
+import {Injectable} from '@angular/core';
/**
* A platform location implementation for a Chrome OS app to prevent calls
* to history.
*/
+@Injectable()
export class ChromeAppPlatformLocation extends PlatformLocation {
private appLocation: Location;
@@ -42,6 +44,22 @@ export class ChromeAppPlatformLocation extends PlatformLocation {
this.appLocation.pathname = newPath;
}
+ getState(): unknown {
+ return null;
+ }
+ get href(): string {
+ return '';
+ }
+ get protocol(): string {
+ return '';
+ }
+ get hostname(): string {
+ return '';
+ }
+ get port(): string {
+ return '';
+ }
+
getBaseHrefFromDOM() {
return '/';
}
diff --git a/loaner/chrome_app/src/app/shared/failure/failure.ts b/loaner/chrome_app/src/app/shared/failure/failure.ts
index 1af3ab45..7d8770e7 100644
--- a/loaner/chrome_app/src/app/shared/failure/failure.ts
+++ b/loaner/chrome_app/src/app/shared/failure/failure.ts
@@ -13,7 +13,7 @@
// limitations under the License.
import {Component, Inject, Injectable} from '@angular/core';
-import {MAT_DIALOG_DATA, MatDialog, MatDialogRef} from '@angular/material';
+import {MAT_DIALOG_DATA, MatDialog, MatDialogRef} from '@angular/material/dialog';
import {ConfigService, FAILURE_MESSAGE} from '../../../../../shared/config';
import {Background} from '../background_service';
diff --git a/loaner/chrome_app/src/app/shared/failure/material_module.ts b/loaner/chrome_app/src/app/shared/failure/material_module.ts
index 42998db6..882b0f47 100644
--- a/loaner/chrome_app/src/app/shared/failure/material_module.ts
+++ b/loaner/chrome_app/src/app/shared/failure/material_module.ts
@@ -13,7 +13,9 @@
// limitations under the License.
import {NgModule} from '@angular/core';
-import {MatButtonModule, MatDialogModule, MatExpansionModule} from '@angular/material';
+import {MatButtonModule} from '@angular/material/button';
+import {MatDialogModule} from '@angular/material/dialog';
+import {MatExpansionModule} from '@angular/material/expansion';
const MATERIAL_MODULES = [
MatButtonModule,
diff --git a/loaner/chrome_app/src/app/shared/http.ts b/loaner/chrome_app/src/app/shared/http.ts
index 5a82e1a6..60cb5d0d 100644
--- a/loaner/chrome_app/src/app/shared/http.ts
+++ b/loaner/chrome_app/src/app/shared/http.ts
@@ -17,15 +17,12 @@
* Note: Not a standard angular module.
*/
-import {HeaderInit} from 'node-fetch';
import {ConfigService} from '../../../../shared/config';
let accessToken: string;
const CONFIG = new ConfigService();
const MAX_RETRIES = 5;
-export type HeaderInitTs26 = HeaderInit&string[][];
-
/**
* Headers to be used in the request with oauth information
*/
@@ -42,7 +39,7 @@ function HEADERS() {
* @param headers Any other http header request information.
*/
-export function get(url: string, headers?: HeaderInitTs26) {
+export function get(url: string, headers?: HeadersInit) {
return makeRequest('get', url, undefined, headers);
}
@@ -52,7 +49,7 @@ export function get(url: string, headers?: HeaderInitTs26) {
* @param body The data to be send over the POST HTTP request
* @param headers Any other http header request information.
*/
-export function post(url: string, body: string, headers?: HeaderInitTs26) {
+export function post(url: string, body: {}, headers?: HeadersInit) {
return makeRequest('post', url, body, headers);
}
@@ -64,7 +61,7 @@ export function post(url: string, body: string, headers?: HeaderInitTs26) {
* @param headers Any other http header request information.
*/
function makeRequest(
- method: string, url: string, body?: string, headers?: HeaderInitTs26) {
+ method: string, url: string, body?: {}, headers?: HeadersInit) {
return new Promise((resolve, reject) => {
chrome.identity.getAuthToken(
{interactive: false}, (newAccessToken: string) => {
@@ -99,7 +96,7 @@ function makeRequest(
};
if (method !== 'head' && method !== 'get') {
- options.body = body || '';
+ options.body = JSON.stringify(body);
}
fetch(`${url}`, options)
diff --git a/loaner/chrome_app/src/app/shared/interfaces.d.ts b/loaner/chrome_app/src/app/shared/interfaces.d.ts
index 4d44201e..1b128f2d 100644
--- a/loaner/chrome_app/src/app/shared/interfaces.d.ts
+++ b/loaner/chrome_app/src/app/shared/interfaces.d.ts
@@ -41,3 +41,4 @@ declare interface LoanerStorage {
enrolled?: boolean;
onboardingComplete?: boolean;
}
+
diff --git a/loaner/chrome_app/src/app/shared/loan.ts b/loaner/chrome_app/src/app/shared/loan.ts
index 12d95a62..f74fa5d4 100644
--- a/loaner/chrome_app/src/app/shared/loan.ts
+++ b/loaner/chrome_app/src/app/shared/loan.ts
@@ -79,6 +79,18 @@ export class Loan {
}));
}
+ /** API request to complete onboarding. */
+ completeOnboard(): Observable {
+ let request: DeviceRequestApiParams;
+ return DeviceIdentifier.id().pipe(switchMap(deviceId => {
+ request = {
+ chrome_device_id: deviceId,
+ };
+ const apiUrl = `${this.endpointsDeviceUrl}/user/complete_onboard`;
+ return this.http.post(apiUrl, request);
+ }));
+ }
+
/** Enable guest mode for the loan. */
enableGuestMode(): Observable {
let request: DeviceRequestApiParams;
@@ -141,6 +153,10 @@ export class LoanMock {
return of(true);
}
+ completeOnboard(): Observable {
+ return of();
+ }
+
resumeLoan(): Observable {
return of(true);
}
diff --git a/loaner/chrome_app/src/app/shared/return_date_service.ts b/loaner/chrome_app/src/app/shared/return_date_service.ts
index 64743849..3fd985c7 100644
--- a/loaner/chrome_app/src/app/shared/return_date_service.ts
+++ b/loaner/chrome_app/src/app/shared/return_date_service.ts
@@ -16,6 +16,8 @@ import {Injectable} from '@angular/core';
import * as moment from 'moment';
import {BehaviorSubject, Observable} from 'rxjs';
+import {ConfigService} from '../../../../shared/config';
+
import {FailAction, FailType, Failure} from './failure';
import {Loan} from './loan';
@@ -28,7 +30,7 @@ export class ReturnDateService {
/** Formats the new requested due date via moment for API interaction. */
get formattedNewDueDate() {
- return moment(this.newReturnDate!).format(`YYYY-MM-DD[T][00]:[00]:[00]`);
+ return moment(this.newReturnDate!).format(this.config.momentLongDateFormat);
}
/** Validates the date using the new formatted due date. */
@@ -46,7 +48,11 @@ export class ReturnDateService {
validDate = this.validDateSource.asObservable();
- constructor(private readonly loan: Loan, private readonly failure: Failure) {}
+ constructor(
+ private readonly loan: Loan,
+ private readonly failure: Failure,
+ private readonly config: ConfigService,
+ ) {}
/**
* Used to update the new return date using behavior subjects.
diff --git a/loaner/chrome_app/src/app/shared/storage/storage.ts b/loaner/chrome_app/src/app/shared/storage/storage.ts
index dfbf2e96..264cfa81 100644
--- a/loaner/chrome_app/src/app/shared/storage/storage.ts
+++ b/loaner/chrome_app/src/app/shared/storage/storage.ts
@@ -18,11 +18,7 @@ import {Injectable} from '@angular/core';
import {Observable} from 'rxjs';
export declare interface Data {
- [key: string]: string;
-}
-
-export declare interface DataLoanerStorage {
- [key: string]: LoanerStorage;
+ [key: string]: {};
}
/**
@@ -50,7 +46,7 @@ export class ChromeLocalStorage {
* Retrieve a given value from chrome.storage.local.
* @param key Key for values to retrieve from storage.
*/
- get(key: string): Observable {
+ get(key: string): Observable<{}> {
return new Observable(observer => {
// Fetch initial value from local storage.
chrome.storage.local.get([key], (result: Data) => {
@@ -59,31 +55,11 @@ export class ChromeLocalStorage {
// Listen for new changes and push next in observable.
chrome.storage.onChanged.addListener((changes, namespace) => {
if (namespace === 'local' && changes[key] !== undefined) {
- observer.next(changes[key].newValue as string);
- }
- });
- });
- }
-
- /**
- * Retrieve a given value from chrome.storage.local of type LoanerStorage.
- * @param key Key for values to retrieve from storage.
- */
- getLoanerStorage(key: string): Observable {
- return new Observable(observer => {
- // Fetch initial value from local storage.
- chrome.storage.local.get([key], (result: DataLoanerStorage) => {
- observer.next(result[key]);
- });
- // Listen for new changes and push next in observable.
- chrome.storage.onChanged.addListener((changes, namespace) => {
- if (namespace === 'local' && changes[key] !== undefined) {
- observer.next(changes[key].newValue as LoanerStorage);
+ observer.next(changes[key].newValue as {});
}
});
});
}
-
/**
* Set a value at a given key location in chrome.storage.local.
* @param key Key for value to set in storage.
diff --git a/loaner/deployments/BUILD b/loaner/deployments/BUILD
index 6ea4e41c..e5663eee 100644
--- a/loaner/deployments/BUILD
+++ b/loaner/deployments/BUILD
@@ -27,10 +27,11 @@ py_binary(
srcs = [
"deploy_impl.py",
],
- default_python_version = "PY3",
+ python_version = "PY3",
srcs_version = "PY2AND3",
deps = [
":deploy_impl_lib",
+ "@six_archive//:six",
],
)
@@ -39,9 +40,11 @@ py_binary(
srcs = [
"gng_impl.py",
],
- default_python_version = "PY3",
+ python_version = "PY3",
+ srcs_version = "PY2AND3",
deps = [
":gng_impl_lib",
+ "@six_archive//:six",
],
)
@@ -97,7 +100,7 @@ py_test(
"deploy_impl_test.py",
],
deps = [
- ":deploy_impl",
+ ":deploy_impl_lib",
"@absl_archive//absl:app",
"@absl_archive//absl/flags",
"@absl_archive//absl/testing:absltest",
diff --git a/loaner/deployments/deploy.sh b/loaner/deployments/deploy.sh
index 77694b4a..a330cf2f 100755
--- a/loaner/deployments/deploy.sh
+++ b/loaner/deployments/deploy.sh
@@ -148,8 +148,8 @@ found here: https://docs.bazel.build/versions/master/install.html"
| cut -d ' ' -f3 \
| sed -E 's/(^0*|\.)//g');;
esac
- [[ "${BAZEL_VERSION}" -ge "90" ]] || error_message "The bazel vesrion \
-installed is lower than the minimum required version (0.9.0), please update \
+ [[ "${BAZEL_VERSION}" -ge "260" ]] || error_message "The bazel version \
+installed is lower than the minimum required version (0.26.0), please update \
bazel."
success_message "bazel was found on PATH and is at or above the minimum \
version."
@@ -244,7 +244,7 @@ auth login"
info_message "Initiating the build of the python deployment script..."
bazel build //loaner/deployments:deploy_impl
- ../bazel-out/k8-py3-fastbuild/bin/loaner/deployments/deploy_impl \
+ ../bazel-out/k8-fastbuild/bin/loaner/deployments/deploy_impl \
--loaner_path "$(pwd -P)" \
--app_servers "${APP_SERVERS}" \
--build_target "${BUILD_TARGET}" \
diff --git a/loaner/deployments/deploy_impl.py b/loaner/deployments/deploy_impl.py
index 07c3ed18..e91c0e64 100644
--- a/loaner/deployments/deploy_impl.py
+++ b/loaner/deployments/deploy_impl.py
@@ -12,6 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+# Lint as: python2, python3
"""The Grab n Go App Management Script.
usage: deploy_impl.py [FLAGS] [APPLICATION] [DEPLOYMENT]
@@ -49,6 +50,7 @@
from absl import flags
from absl import logging
+import six
from six.moves import input
@@ -108,6 +110,36 @@ def __init__(self, errno):
super(ManifestError, self).__init__()
+def _EnsureStr(s, encoding='utf-8', errors='strict'):
+ """Coerce *s* to `str`.
+
+ Args:
+ s: string or bytes
+ encoding: string encoding
+ errors: raise errors
+
+ Returns:
+ PY3 and PY3 str type
+
+ For Python 2:
+ - `unicode` -> encoded to `str`
+ - `str` -> `str`
+ For Python 3:
+ - `str` -> `str`
+ - `bytes` -> decoded to `str`
+ # Needs to be removed once six version is uograded to 1.12.0 or above
+ # And replace with `six.ensure_str`
+
+ """
+ if not isinstance(s, (six.text_type, six.binary_type)):
+ raise TypeError("not expecting type '%s'" % type(s))
+ if six.PY2 and isinstance(s, six.text_type):
+ s = s.encode(encoding, errors)
+ elif six.PY3 and isinstance(s, six.binary_type):
+ s = s.decode(encoding, errors)
+ return s
+
+
def _ParseAppServers(app_servers):
"""Parse the app servers for name and project id.
@@ -119,7 +151,7 @@ def _ParseAppServers(app_servers):
A dictionary with the friendly name as the key and the Google Cloud Project
ID as the value.
"""
- return dict(server.split('=', 1) for server in app_servers)
+ return dict(_EnsureStr(server).split('=', 1) for server in app_servers)
def _AppServerValidator(app_servers):
@@ -258,7 +290,7 @@ def __init__(
self._web_app_dir = web_app_dir
self._yaml_files = yaml_files
self._app_servers = _ParseAppServers(app_servers)
- if self._deployment_type not in self._app_servers.keys():
+ if self._deployment_type not in list(self._app_servers.keys()):
raise app.UsageError(
'Application name provided is not in the list of App Servers.\n'
'Please check the name and/or the deploy.sh configuration.')
@@ -292,8 +324,7 @@ def app_path(self):
@property
def app_engine_deps_path(self):
"""Retrieve the App Engine Dependencies path as a string."""
- return os.path.join(
- self.app_path, 'external', 'com_google_appengine_python')
+ return os.path.join(self.app_path, 'external', 'com_google_appengine_py')
@property
def frontend_src_path(self):
@@ -504,10 +535,8 @@ def main(argv):
# No arguments passed, show usage statement.
if len(argv) < 1:
app.usage(shorthelp=True, exitcode=1)
-
# Application to deploy: web or chrome.
application = argv[0]
-
# Server to deploy to: local, dev, or prod.
try:
deployment_type = argv[1]
diff --git a/loaner/deployments/deploy_impl_test.py b/loaner/deployments/deploy_impl_test.py
index a52264e0..415c5177 100644
--- a/loaner/deployments/deploy_impl_test.py
+++ b/loaner/deployments/deploy_impl_test.py
@@ -12,20 +12,13 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+# Lint as: python2, python3
"""Tests for deployments.deploy_impl."""
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
-# Prefer Python 3 and fall back on Python 2.
-# pylint:disable=g-statement-before-imports,g-import-not-at-top
-try:
- import builtins
-except ImportError:
- import __builtin__ as builtins
-# pylint:enable=g-statement-before-imports,g-import-not-at-top
-
import datetime
import json
import subprocess
@@ -34,13 +27,21 @@
from absl import flags
from pyfakefs import fake_filesystem
from pyfakefs import fake_filesystem_shutil
-from pyfakefs import mox3_stubout
-
import freezegun
import mock
-from absl.testing import absltest
from loaner.deployments import deploy_impl
+from absl.testing import absltest
+
+# Prefer Python 3 and fall back on Python 2.
+# pylint:disable=g-statement-before-imports,g-import-not-at-top
+try:
+ import builtins
+except ImportError:
+ import six.moves.builtins as builtins
+# pylint:enable=g-statement-before-imports,g-import-not-at-top
+
+from pyfakefs import mox3_stubout
_WORKSPACE_PATH = '/this/is/a/workspace'
_LOANER_PATH = _WORKSPACE_PATH + '/loaner'
@@ -261,9 +262,8 @@ def testAppEngineServerConfigInit(self):
self.assertEndsWith(test_app_engine_config.app_path, '.runfiles/gng')
# Test that the app_engine_path ends with the bazel package name for app
# engine.
- self.assertEndsWith(
- test_app_engine_config.app_engine_deps_path,
- '.runfiles/gng/external/com_google_appengine_python')
+ self.assertEndsWith(test_app_engine_config.app_engine_deps_path,
+ '.runfiles/gng/external/com_google_appengine_py')
# Test that the frontend_src_path ends with the frontend package.
self.assertEndsWith(
test_app_engine_config.frontend_src_path,
@@ -306,7 +306,7 @@ def testBuildWebAppBackend(self, mock_execute):
"""Test that the build web application backend executes."""
fake_app_engine_deps_path = (
'/this/is/a/workspace/bazel-bin/loaner/web_app/runfiles.runfiles/gng/'
- 'external/com_google_appengine_python')
+ 'external/com_google_appengine_py')
self.fs.CreateDirectory(fake_app_engine_deps_path)
test_app_engine_config = self.CreateTestAppEngineConfig()
test_app_engine_config._BuildWebAppBackend()
@@ -451,6 +451,38 @@ def testDeployChromeApp(self, mock_buildchromeapp):
test_chrome_app_config.DeployChromeApp()
assert mock_buildchromeapp.call_count == 1
+ @mock.patch.object(
+ deploy_impl, 'AppEngineServerConfig', return_value=mock.Mock())
+ def testMainWebApp(self, mocked_appengine_server_config):
+ deploy_impl.main(argv=['first-arg', 'web'])
+ mocked_appengine_server_config.assert_called_once_with(
+ app_servers=deploy_impl.FLAGS.app_servers,
+ build_target=deploy_impl.FLAGS.build_target,
+ deployment_type='local',
+ loaner_path=deploy_impl.FLAGS.loaner_path,
+ web_app_dir=deploy_impl.FLAGS.web_app_dir,
+ yaml_files=deploy_impl.FLAGS.yaml_files,
+ version=deploy_impl.FLAGS.version)
+
+ @mock.patch.object(deploy_impl, 'ChromeAppConfig', return_value=mock.Mock())
+ def testMainChromeApp(self, mocked_chrome_app_config):
+ deploy_impl.main(argv=['first-arg', 'chrome'])
+ mocked_chrome_app_config.assert_called_once_with(
+ chrome_app_dir=deploy_impl.FLAGS.chrome_app_dir,
+ deployment_type='local',
+ loaner_path=deploy_impl.FLAGS.loaner_path)
+
+ @mock.patch.object(app, 'usage', return_value=mock.Mock())
+ def testMainWithoutParam(self, mocked_app_usage):
+ with self.assertRaises(IndexError):
+ deploy_impl.main(argv=[])
+ mocked_app_usage.assert_called_once_with(shorthelp=True, exitcode=1)
+
+ @mock.patch.object(app, 'usage', return_value=mock.Mock())
+ def testMainWithInvalidAppType(self, mocked_app_usage):
+ deploy_impl.main(argv=['first-arg', 'fake-app'])
+ mocked_app_usage.assert_called_once_with(shorthelp=True, exitcode=1)
+
if __name__ == '__main__':
absltest.main()
diff --git a/loaner/deployments/gng_impl.py b/loaner/deployments/gng_impl.py
index 4da4cc0b..0f57337c 100644
--- a/loaner/deployments/gng_impl.py
+++ b/loaner/deployments/gng_impl.py
@@ -12,6 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+# Lint as: python2, python3
"""The Grab n Go management script.
Usage: gng_impl [FLAGS]
@@ -278,7 +279,7 @@ def run(self):
utils.write('Action: {!r}\nDescription: {}\n'.format(
opt.name, opt.description))
action = utils.prompt_enum(
- '', accepted_values=self._options.keys(),
+ '', accepted_values=list(self._options.keys()),
case_sensitive=False).strip().lower()
callback = self._options[action].callback
if callback is None:
diff --git a/loaner/deployments/gng_impl_test.py b/loaner/deployments/gng_impl_test.py
index 59cfcd6c..8b102b1d 100644
--- a/loaner/deployments/gng_impl_test.py
+++ b/loaner/deployments/gng_impl_test.py
@@ -12,20 +12,13 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+# Lint as: python2, python3
"""Tests for deployments.gng_impl."""
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
-# Prefer Python 2 and fall back on Python 3.
-# pylint:disable=g-statement-before-imports,g-import-not-at-top
-try:
- import __builtin__ as builtins
-except ImportError:
- import builtins
-# pylint:enable=g-statement-before-imports,g-import-not-at-top
-
import datetime
import getpass
import sys
@@ -33,24 +26,29 @@
from absl import logging
from absl.testing import flagsaver
from absl.testing import parameterized
-
from pyfakefs import fake_filesystem
-from pyfakefs import mox3_stubout
-
import freezegun
import mock
-
from six.moves import StringIO
from google.auth import credentials
-
-from absl.testing import absltest
from loaner.deployments import gng_impl
from loaner.deployments.lib import app_constants
from loaner.deployments.lib import auth
from loaner.deployments.lib import common
from loaner.deployments.lib import storage
from loaner.deployments.lib import utils
+from absl.testing import absltest
+
+# Prefer Python 2 and fall back on Python 3.
+# pylint:disable=g-statement-before-imports,g-import-not-at-top
+try:
+ import six.moves.builtins as builtins
+except ImportError:
+ import builtins
+# pylint:enable=g-statement-before-imports,g-import-not-at-top
+
+from pyfakefs import mox3_stubout
# The following constants are YAML file contents that are written to the fake
# file system.
diff --git a/loaner/deployments/lib/password_test.py b/loaner/deployments/lib/password_test.py
index 6dfe36ea..ac509c78 100644
--- a/loaner/deployments/lib/password_test.py
+++ b/loaner/deployments/lib/password_test.py
@@ -29,7 +29,7 @@ class PasswordTest(parameterized.TestCase, absltest.TestCase):
@parameterized.parameters(8, 20, 50, 70, 100)
def test_generate(self, length):
pw = password.generate(length)
- self.assertEqual(length, len(pw))
+ self.assertLen(pw, length)
@parameterized.parameters(2, 7, 101, 200)
def test_generate__value_error(self, length):
diff --git a/loaner/package.json b/loaner/package.json
index 5b079bc8..2d2590d0 100644
--- a/loaner/package.json
+++ b/loaner/package.json
@@ -1,8 +1,7 @@
{
"name": "gng",
- "version": "0.0.1",
+ "version": "1.0.0",
"license": "Apache 2.0",
- "description": "",
"scripts": {
"build:frontend": "webpack --config web_app/frontend/config/webpack.aot.js",
"start:frontend": "webpack-dev-server --port=4200 --host=0.0.0.0 --config web_app/frontend/config/webpack.aot.js",
@@ -16,79 +15,68 @@
"fix:chromeapp": "npm run lint:chromeapp:typescript -- --fix"
},
"dependencies": {
- "@angular/animations": "6.1.10",
- "@angular/cdk": "^6.1.0",
- "@angular/common": "6.1.10",
- "@angular/compiler": "6.1.10",
- "@angular/core": "6.1.10",
- "@angular/flex-layout": "6.0.0-beta.18",
- "@angular/forms": "6.1.10",
- "@angular/http": "6.1.10",
- "@angular/material": "^6.1.0",
- "@angular/platform-browser": "6.1.10",
- "@angular/platform-browser-dynamic": "6.1.10",
- "@angular/router": "6.1.10",
- "core-js": "2.5.1",
- "es6-shim": "0.35.3",
+ "@angular/animations": "8.2.14",
+ "@angular/cdk": "8.2.3",
+ "@angular/common": "8.2.14",
+ "@angular/compiler": "8.2.14",
+ "@angular/core": "8.2.14",
+ "@angular/flex-layout": "8.0.0-beta.27",
+ "@angular/forms": "8.2.14",
+ "@angular/material": "8.2.3",
+ "@angular/platform-browser": "8.2.14",
+ "@angular/platform-browser-dynamic": "8.2.14",
+ "@angular/router": "8.2.14",
+ "core-js": "3.6.4",
+ "es6-shim": "0.35.5",
"hammerjs": "2.0.8",
- "marked": "0.3.19",
+ "marked": "0.8.0",
"material-design-icons": "3.0.1",
- "moment": "2.20.1",
- "reflect-metadata": "0.1.10",
- "roboto-fontface": "0.9.0",
- "rxjs": "6.0.0",
- "zone.js": "0.8.26"
+ "moment": "2.24.0",
+ "reflect-metadata": "0.1.13",
+ "roboto-fontface": "0.10.0",
+ "rxjs": "6.5.4",
+ "zone.js": "0.10.2"
},
"devDependencies": {
- "@angular-devkit/core": "0.3.2",
- "@angular-devkit/schematics": "0.3.2",
- "@angular/cli": "7.1.3",
- "@angular/compiler-cli": "6.1.10",
- "@ngtools/webpack": "1.10.1",
- "@types/bluebird": "3.5.21",
- "@types/chrome-apps": "0.0.7",
- "@types/gapi": "0.0.35",
- "@types/gapi.auth2": "0.0.47",
- "@types/jasmine": "2.8.6",
- "@types/karma": "1.7.2",
- "@types/marked": "0.3.0",
- "@types/node": "9.4.6",
- "@types/node-fetch": "1.6.7",
+ "@angular-devkit/core": "8.3.23",
+ "@angular-devkit/schematics": "8.3.23",
+ "@angular/cli": "8.3.23",
+ "@angular/compiler-cli": "8.2.14",
+ "@ngtools/webpack": "8.3.23",
+ "@types/chrome-apps": "0.0.9",
+ "@types/gapi": "0.0.39",
+ "@types/gapi.auth2": "0.0.51",
+ "@types/jasmine": "3.5.0",
+ "@types/karma": "3.0.5",
+ "@types/marked": "0.7.2",
+ "@types/node": "13.1.7",
+ "@types/node-fetch": "2.5.4",
"angular2-template-loader": "0.6.2",
- "awesome-typescript-loader": "3.2.3",
- "babel-core": "6.25.0",
- "copy-webpack-plugin": "4.1.1",
- "css-loader": "0.28.10",
+ "awesome-typescript-loader": "5.2.1",
+ "copy-webpack-plugin": "5.1.1",
"extract-text-webpack-plugin": "3.0.2",
- "file-loader": "1.1.10",
- "html-loader": "0.5.1",
- "html-webpack-plugin": "2.30.1",
- "jasmine-core": "2.9.1",
- "json-loader": "0.5.7",
- "karma": "3.1.4",
- "karma-chrome-launcher": "2.2.0",
- "karma-coverage-istanbul-reporter": "1.3.0",
- "karma-jasmine": "1.1.0",
+ "file-loader": "5.0.2",
+ "html-loader": "0.5.5",
+ "html-webpack-plugin": "3.2.0",
+ "jasmine-core": "3.5.0",
+ "karma": "4.4.1",
+ "karma-chrome-launcher": "3.1.0",
+ "karma-jasmine": "3.1.0",
"karma-sourcemap-loader": "0.3.7",
- "karma-webpack": "3.0.5",
- "node-fetch": "2.0.0",
- "node-sass": "4.5.3",
- "node-static": "0.7.9",
- "null-loader": "0.1.1",
- "raw-loader": "0.5.1",
- "rimraf": "2.6.2",
- "sass-loader": "6.0.6",
- "style-loader": "0.20.2",
- "systemjs": "0.21.0",
- "ts-loader": "4.0.0",
- "tslint": "5.7.0",
- "tslint-eslint-rules": "5.1.0",
- "tslint-loader": "3.5.3",
- "typescript": "~2.9.2",
- "uglifyjs-webpack-plugin": "1.1.5",
- "vrsource-tslint-rules": "5.1.0",
- "webpack": "3.11.0",
- "webpack-dev-server": "3.1.10",
- "webpack-merge": "4.1.0"
+ "karma-spec-reporter": "0.0.32",
+ "karma-webpack": "4.0.2",
+ "node-sass": "4.13.1",
+ "raw-loader": "4.0.0",
+ "rimraf": "3.0.0",
+ "sass-loader": "8.0.2",
+ "svg-inline-loader": "0.8.0",
+ "terser-webpack-plugin": "2.3.2",
+ "to-string-loader": "1.1.6",
+ "tslint": "5.20.1",
+ "typescript": "3.5.3",
+ "webpack": "4.41.5",
+ "webpack-cli": "3.3.10",
+ "webpack-dev-server": "3.10.1",
+ "webpack-merge": "4.2.2"
}
}
diff --git a/loaner/shared/api_service_test.ts b/loaner/shared/api_service_test.ts
index 339c2212..8d3ef6b7 100644
--- a/loaner/shared/api_service_test.ts
+++ b/loaner/shared/api_service_test.ts
@@ -127,4 +127,62 @@ describe('ConfigService', () => {
.toBe(
'https://endpoints-dot-qa-app-engine-project.appspot.com/_ah/api');
});
+
+ it('provides the correct ID for analytics when on prod web app', () => {
+ config.ON_LOCAL = false;
+ config.ON_DEV = false;
+ config.ON_QA = false;
+ config.ON_PROD = true;
+ config.IS_FRONTEND = true;
+ expect(config.analyticsId).toBe('');
+ });
+
+ it('provides the correct ID for analytics when on the qa web app', () => {
+ config.ON_LOCAL = false;
+ config.ON_DEV = false;
+ config.ON_QA = true;
+ config.ON_PROD = false;
+ config.IS_FRONTEND = true;
+ expect(config.analyticsId).toBe('');
+ });
+
+ it('provides the correct ID for analytics when on the dev web app', () => {
+ config.ON_LOCAL = false;
+ config.ON_DEV = true;
+ config.ON_QA = false;
+ config.ON_PROD = false;
+ config.IS_FRONTEND = true;
+ expect(config.analyticsId).toBe('');
+ });
+
+ it('provides the correct ID for analytics when on prod chrome app', () => {
+ config.ON_LOCAL = false;
+ config.ON_DEV = false;
+ config.ON_QA = false;
+ config.ON_PROD = false;
+ config.IS_FRONTEND = false;
+ spyOnProperty(config, 'chromeMode', 'get')
+ .and.returnValue(CHROME_MODE.PROD);
+ expect(config.analyticsId).toBe('');
+ });
+
+ it('provides the correct ID for analytics when on the qa chrome app', () => {
+ config.ON_LOCAL = false;
+ config.ON_DEV = false;
+ config.ON_QA = false;
+ config.ON_PROD = false;
+ config.IS_FRONTEND = false;
+ spyOnProperty(config, 'chromeMode', 'get').and.returnValue(CHROME_MODE.QA);
+ expect(config.analyticsId).toBe('');
+ });
+
+ it('provides the correct ID for analytics when on the dev chrome app', () => {
+ config.ON_LOCAL = false;
+ config.ON_DEV = false;
+ config.ON_QA = false;
+ config.ON_PROD = false;
+ config.IS_FRONTEND = false;
+ spyOnProperty(config, 'chromeMode', 'get').and.returnValue(CHROME_MODE.DEV);
+ expect(config.analyticsId).toBe('');
+ });
});
diff --git a/loaner/shared/assets/almost_overdue.svg b/loaner/shared/assets/almost_overdue.svg
new file mode 100644
index 00000000..d91bd7a5
--- /dev/null
+++ b/loaner/shared/assets/almost_overdue.svg
@@ -0,0 +1 @@
+
diff --git a/loaner/shared/assets/gng_image.png b/loaner/shared/assets/gng_image.png
new file mode 100644
index 00000000..3bf821df
Binary files /dev/null and b/loaner/shared/assets/gng_image.png differ
diff --git a/loaner/shared/assets/healthy.svg b/loaner/shared/assets/healthy.svg
new file mode 100644
index 00000000..145f3f54
--- /dev/null
+++ b/loaner/shared/assets/healthy.svg
@@ -0,0 +1 @@
+
diff --git a/loaner/shared/assets/overdue.svg b/loaner/shared/assets/overdue.svg
new file mode 100644
index 00000000..cce2ff43
--- /dev/null
+++ b/loaner/shared/assets/overdue.svg
@@ -0,0 +1 @@
+
diff --git a/loaner/shared/components/animation_menu/animation_menu.ts b/loaner/shared/components/animation_menu/animation_menu.ts
index 5619558c..79025f15 100644
--- a/loaner/shared/components/animation_menu/animation_menu.ts
+++ b/loaner/shared/components/animation_menu/animation_menu.ts
@@ -13,7 +13,7 @@
// limitations under the License.
import {Component} from '@angular/core';
-import {MatDialogRef} from '@angular/material';
+import {MatDialogRef} from '@angular/material/dialog';
import {AnimationMenuService} from '../../services/animation_menu_service';
@@ -35,8 +35,9 @@ export class AnimationMenuComponent {
constructor(
private animationService: AnimationMenuService,
public dialogRef: MatDialogRef) {
- this.animationService.getAnimationSpeed().subscribe(
- speed => this.playbackRate = speed);
+ this.animationService.getAnimationSpeed().subscribe(speed => {
+ this.playbackRate = speed;
+ });
}
closeDialog() {
diff --git a/loaner/shared/components/animation_menu/animation_menu_test.ts b/loaner/shared/components/animation_menu/animation_menu_test.ts
index 1442bdf2..83312332 100644
--- a/loaner/shared/components/animation_menu/animation_menu_test.ts
+++ b/loaner/shared/components/animation_menu/animation_menu_test.ts
@@ -13,8 +13,7 @@
// limitations under the License.
import {ComponentFixture, TestBed} from '@angular/core/testing';
-import {MatDialogRef} from '@angular/material';
-import {By} from '@angular/platform-browser';
+import {MatDialogRef} from '@angular/material/dialog';
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
import {AnimationMenuService} from '../../services/animation_menu_service';
@@ -60,15 +59,15 @@ describe('AnimationMenuComponent', () => {
});
it('should show the slider', () => {
- expect(fixture.debugElement.query(By.css('.mat-dialog-title'))
- .nativeElement.innerText)
+ expect(fixture.debugElement.nativeElement.querySelector('.mat-dialog-title')
+ .innerText)
.toBe('Animation Menu');
});
it('should render the close button', () => {
fixture.detectChanges();
expect(
- fixture.debugElement.query(By.css('#close')).nativeElement.textContent)
+ fixture.debugElement.nativeElement.querySelector('#close').textContent)
.toContain('Close');
});
});
diff --git a/loaner/shared/components/animation_menu/material_module.ts b/loaner/shared/components/animation_menu/material_module.ts
index 4e5e21ac..366dc242 100644
--- a/loaner/shared/components/animation_menu/material_module.ts
+++ b/loaner/shared/components/animation_menu/material_module.ts
@@ -13,7 +13,9 @@
// limitations under the License.
import {NgModule} from '@angular/core';
-import {MatButtonModule, MatDialogModule, MatSliderModule} from '@angular/material';
+import {MatButtonModule} from '@angular/material/button';
+import {MatDialogModule} from '@angular/material/dialog';
+import {MatSliderModule} from '@angular/material/slider';
const MATERIAL_MODULES = [
MatButtonModule,
diff --git a/loaner/shared/components/damaged/damaged.ts b/loaner/shared/components/damaged/damaged.ts
index 14b8ffb0..ab785f52 100644
--- a/loaner/shared/components/damaged/damaged.ts
+++ b/loaner/shared/components/damaged/damaged.ts
@@ -13,7 +13,7 @@
// limitations under the License.
import {Component, Injectable} from '@angular/core';
-import {MatDialog, MatDialogRef} from '@angular/material';
+import {MatDialog, MatDialogRef} from '@angular/material/dialog';
import {Subject} from 'rxjs';
import {LoaderView} from '../loader';
diff --git a/loaner/shared/components/damaged/damaged_test.ts b/loaner/shared/components/damaged/damaged_test.ts
index 28df69b1..13ad482b 100644
--- a/loaner/shared/components/damaged/damaged_test.ts
+++ b/loaner/shared/components/damaged/damaged_test.ts
@@ -15,8 +15,7 @@
import {CommonModule} from '@angular/common';
import {ComponentFixture, TestBed} from '@angular/core/testing';
import {FormsModule} from '@angular/forms';
-import {MatDialogRef} from '@angular/material';
-import {By} from '@angular/platform-browser';
+import {MatDialogRef} from '@angular/material/dialog';
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
import {DamagedDialogComponent, DamagedModule} from './index';
@@ -58,29 +57,31 @@ describe('DamagedDialogComponent', () => {
it('should render the submit button', () => {
expect(
- fixture.debugElement.query(By.css('#submit')).nativeElement.textContent)
+ fixture.debugElement.nativeElement.querySelector('#submit').textContent)
.toContain('Submit');
});
it('should render the cancel button', () => {
expect(
- fixture.debugElement.query(By.css('#cancel')).nativeElement.textContent)
+ fixture.debugElement.nativeElement.querySelector('#cancel').textContent)
.toContain('Cancel');
});
it('should show the correct title', () => {
- expect(fixture.debugElement.query(By.css('.mat-dialog-title'))
- .nativeElement.innerText)
+ expect(fixture.debugElement.nativeElement.querySelector('.mat-dialog-title')
+ .innerText)
.toBe('Oh no!');
});
it('should show and hide the loader', () => {
component.waiting();
fixture.detectChanges();
- expect(fixture.debugElement.query(By.css('loader'))).toBeDefined();
+ expect(fixture.debugElement.nativeElement.querySelector('loader'))
+ .toBeDefined();
component.ready();
fixture.detectChanges();
- expect(fixture.debugElement.query(By.css('loader'))).toBeFalsy();
+ expect(fixture.debugElement.nativeElement.querySelector('loader'))
+ .toBeFalsy();
});
it('should render the close button', () => {
@@ -88,7 +89,7 @@ describe('DamagedDialogComponent', () => {
component.ready();
fixture.detectChanges();
expect(
- fixture.debugElement.query(By.css('#close')).nativeElement.textContent)
+ fixture.debugElement.nativeElement.querySelector('#close').textContent)
.toContain('Close');
});
});
diff --git a/loaner/shared/components/damaged/material_module.ts b/loaner/shared/components/damaged/material_module.ts
index 612de8fa..0fa0a307 100644
--- a/loaner/shared/components/damaged/material_module.ts
+++ b/loaner/shared/components/damaged/material_module.ts
@@ -13,7 +13,11 @@
// limitations under the License.
import {NgModule} from '@angular/core';
-import {MatButtonModule, MatDialogModule, MatInputModule, MatRippleModule, MatTooltipModule} from '@angular/material';
+import {MatButtonModule} from '@angular/material/button';
+import {MatRippleModule} from '@angular/material/core';
+import {MatDialogModule} from '@angular/material/dialog';
+import {MatInputModule} from '@angular/material/input';
+import {MatTooltipModule} from '@angular/material/tooltip';
const MATERIAL_MODULES = [
MatButtonModule,
diff --git a/loaner/shared/components/extend/extend.ts b/loaner/shared/components/extend/extend.ts
index 48a88b26..40db0f7a 100644
--- a/loaner/shared/components/extend/extend.ts
+++ b/loaner/shared/components/extend/extend.ts
@@ -13,10 +13,11 @@
// limitations under the License.
import {Component, Injectable, OnInit} from '@angular/core';
-import {MatDialog, MatDialogRef} from '@angular/material';
+import {MatDialog, MatDialogRef} from '@angular/material/dialog';
import * as moment from 'moment';
import {Subject} from 'rxjs';
+import {ConfigService} from '../../config';
import {LoaderView} from '../loader';
/** Creates the actual dialog for the extend flow. */
@@ -70,7 +71,10 @@ export class ExtendDialogComponent extends LoaderView implements OnInit {
toBeSubmitted = true;
validDate = true;
- constructor(public dialogRef: MatDialogRef) {
+ constructor(
+ public dialogRef: MatDialogRef,
+ private readonly config: ConfigService,
+ ) {
super(false);
}
@@ -135,7 +139,7 @@ export class ExtendDialogComponent extends LoaderView implements OnInit {
extendDate() {
/** Updates the new return date to the proper API format. */
const formattedNewDueDate =
- moment(this.newReturnDate!).format(`YYYY-MM-DD[T][00]:[00]:[00]`);
+ moment(this.newReturnDate!).format(this.config.momentLongDateFormat);
if (this.validateDate(formattedNewDueDate)) {
this.loading = true;
diff --git a/loaner/shared/components/extend/extend_test.ts b/loaner/shared/components/extend/extend_test.ts
index 7f9ff365..b9073f73 100644
--- a/loaner/shared/components/extend/extend_test.ts
+++ b/loaner/shared/components/extend/extend_test.ts
@@ -13,9 +13,11 @@
// limitations under the License.
import {ComponentFixture, TestBed} from '@angular/core/testing';
-import {MatDialogRef} from '@angular/material';
+import {MatDialogRef} from '@angular/material/dialog';
import * as moment from 'moment';
+import {ConfigService} from '../../config';
+
import {ExtendDialogComponent, ExtendModule} from './index';
/** Mock material DialogRef. */
@@ -25,20 +27,25 @@ describe('ExtendDialogComponent', () => {
let component: ExtendDialogComponent;
let fixture: ComponentFixture;
let compiled: HTMLElement;
+ let config: ConfigService;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [
ExtendModule,
],
- providers: [{
- provide: MatDialogRef,
- useClass: MatDialogRefMock,
- }],
+ providers: [
+ {
+ provide: MatDialogRef,
+ useClass: MatDialogRefMock,
+ },
+ ConfigService
+ ],
});
fixture = TestBed.createComponent(ExtendDialogComponent);
component = fixture.debugElement.componentInstance;
+ config = TestBed.get(ConfigService);
component.dueDate = new Date(2018, 1, 1);
component.maxExtendDate = new Date(2018, 1, 2);
@@ -74,7 +81,7 @@ describe('ExtendDialogComponent', () => {
component.maxExtendDate =
moment(component.maxExtendDate).add(14, 'days').toDate();
const formattedNewDueDate =
- moment(component.newReturnDate).format(`YYYY-MM-DD[T][00]:[00]:[00]`);
+ moment(component.newReturnDate).format(config.momentLongDateFormat);
expect(component.validateDate(formattedNewDueDate)).toBe(true);
});
@@ -89,7 +96,7 @@ describe('ExtendDialogComponent', () => {
component.maxExtendDate =
moment(component.maxExtendDate).add(14, 'days').toDate();
const formattedNewDueDate =
- moment(component.newReturnDate).format(`YYYY-MM-DD[T][00]:[00]:[00]`);
+ moment(component.newReturnDate).format(config.momentLongDateFormat);
expect(component.validateDate(formattedNewDueDate)).toBe(false);
});
diff --git a/loaner/shared/components/extend/material_module.ts b/loaner/shared/components/extend/material_module.ts
index bc45d2ff..618707ae 100644
--- a/loaner/shared/components/extend/material_module.ts
+++ b/loaner/shared/components/extend/material_module.ts
@@ -13,7 +13,12 @@
// limitations under the License.
import {NgModule} from '@angular/core';
-import {MatButtonModule, MatDatepickerModule, MatDialogModule, MatInputModule, MatNativeDateModule, MatRippleModule, MatTooltipModule} from '@angular/material';
+import {MatButtonModule} from '@angular/material/button';
+import {MatNativeDateModule, MatRippleModule} from '@angular/material/core';
+import {MatDatepickerModule} from '@angular/material/datepicker';
+import {MatDialogModule} from '@angular/material/dialog';
+import {MatInputModule} from '@angular/material/input';
+import {MatTooltipModule} from '@angular/material/tooltip';
const MATERIAL_MODULES = [
MatButtonModule,
diff --git a/loaner/shared/components/flow_sequence/material_module.ts b/loaner/shared/components/flow_sequence/material_module.ts
index cc474b21..dcdfb58c 100644
--- a/loaner/shared/components/flow_sequence/material_module.ts
+++ b/loaner/shared/components/flow_sequence/material_module.ts
@@ -13,7 +13,9 @@
// limitations under the License.
import {NgModule} from '@angular/core';
-import {MatButtonModule, MatIconModule, MatTooltipModule} from '@angular/material';
+import {MatButtonModule} from '@angular/material/button';
+import {MatIconModule} from '@angular/material/icon';
+import {MatTooltipModule} from '@angular/material/tooltip';
const MATERIAL_MODULES = [
MatButtonModule,
diff --git a/loaner/shared/components/guest/guest.ts b/loaner/shared/components/guest/guest.ts
index f226ec6e..1762af22 100644
--- a/loaner/shared/components/guest/guest.ts
+++ b/loaner/shared/components/guest/guest.ts
@@ -13,7 +13,7 @@
// limitations under the License.
import {Component, Injectable} from '@angular/core';
-import {MatDialog, MatDialogRef} from '@angular/material';
+import {MatDialog, MatDialogRef} from '@angular/material/dialog';
import {Subject} from 'rxjs';
import {LoaderView} from '../loader';
diff --git a/loaner/shared/components/guest/guest_test.ts b/loaner/shared/components/guest/guest_test.ts
index 28578b61..6418913d 100644
--- a/loaner/shared/components/guest/guest_test.ts
+++ b/loaner/shared/components/guest/guest_test.ts
@@ -13,7 +13,7 @@
// limitations under the License.
import {ComponentFixture, TestBed} from '@angular/core/testing';
-import {MatDialogModule, MatDialogRef} from '@angular/material';
+import {MatDialogModule, MatDialogRef} from '@angular/material/dialog';
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
import {LoaderModule} from '../loader';
diff --git a/loaner/shared/components/guest/material_module.ts b/loaner/shared/components/guest/material_module.ts
index fbe8ebec..cc6e922a 100644
--- a/loaner/shared/components/guest/material_module.ts
+++ b/loaner/shared/components/guest/material_module.ts
@@ -13,7 +13,9 @@
// limitations under the License.
import {NgModule} from '@angular/core';
-import {MatButtonModule, MatDialogModule, MatRippleModule} from '@angular/material';
+import {MatButtonModule} from '@angular/material/button';
+import {MatRippleModule} from '@angular/material/core';
+import {MatDialogModule} from '@angular/material/dialog';
const MATERIAL_MODULES = [
MatButtonModule,
diff --git a/loaner/shared/components/info_card/material_module.ts b/loaner/shared/components/info_card/material_module.ts
index 619db599..f439f83d 100644
--- a/loaner/shared/components/info_card/material_module.ts
+++ b/loaner/shared/components/info_card/material_module.ts
@@ -13,7 +13,7 @@
// limitations under the License.
import {NgModule} from '@angular/core';
-import {MatCardModule} from '@angular/material';
+import {MatCardModule} from '@angular/material/card';
const MATERIAL_MODULES = [MatCardModule];
diff --git a/loaner/shared/components/loan_management/greetings_card/greetings_card.scss b/loaner/shared/components/loan_management/greetings_card/greetings_card.scss
index 6b9900a8..9a85cee8 100644
--- a/loaner/shared/components/loan_management/greetings_card/greetings_card.scss
+++ b/loaner/shared/components/loan_management/greetings_card/greetings_card.scss
@@ -2,3 +2,4 @@ mat-card {
text-align: center;
width: 570px;
}
+
diff --git a/loaner/shared/components/loan_management/greetings_card/material_module.ts b/loaner/shared/components/loan_management/greetings_card/material_module.ts
index d0c82110..3bdc7944 100644
--- a/loaner/shared/components/loan_management/greetings_card/material_module.ts
+++ b/loaner/shared/components/loan_management/greetings_card/material_module.ts
@@ -13,7 +13,7 @@
// limitations under the License.
import {NgModule} from '@angular/core';
-import {MatCardModule} from '@angular/material';
+import {MatCardModule} from '@angular/material/card';
const MATERIAL_MODULES = [
MatCardModule,
diff --git a/loaner/shared/components/loan_management/loan_actions_card/loan_actions_card.ng.html b/loaner/shared/components/loan_management/loan_actions_card/loan_actions_card.ng.html
index b2d6044b..235828b1 100644
--- a/loaner/shared/components/loan_management/loan_actions_card/loan_actions_card.ng.html
+++ b/loaner/shared/components/loan_management/loan_actions_card/loan_actions_card.ng.html
@@ -1,29 +1,47 @@
-
- Maintain your loan
-
This device is marked as being returned. If you would like to resume
your loan, please click the button below.
-
-