OWS-Reporting
=====================================

Microservice for reporting backed by Mode Analytics -> Snowflake or Snowflake directly. Allows for the generation
of embeddable reports and the download of generated reports.

[*Tech Design*](https://docs.google.com/document/d/1Kt-9d1fZFXWhA_4CKNY047MUVdlNkXZUM7OkCiD32cA)

## Getting Started

### Installation

This service requires Python 3.13+

Use the make file commands to set up a dev environment locally

```bash
$ make clean
$ make pip_dev
$ make dev
```

### Permissions

In PROD and QA environments, to accept and authorize incoming and outgoing
requests, make sure you have the right DynamoDB permissions, as highlighted
in the corresponding [tech design](https://docs.google.com/document/d/1eHoI_BddTFMi15yCaHS6KvhSSoTrMEd3WwJINIpgNpM/edit).

### Running

When all dependencies have been installed, you can run the flask application
on your local instance by running:

```bash
(env) $ python3 dev.py
```

or

```bash
make dev
```

By using the development server, you will have access to specific features that
are not necessarily available in production, such as the exception tracer.

### Testing

Tests and linting can be run locally via make commands as well

Note that unit tests talk to snowflake (make sure to approve MFA!) and integration tests talk to snowflake (MFA!) & art_relations

```bash
$ make clean
$ make pip_dev

# linting
$ make lint

# tests (all tests)
$ make test

# tests (just unit)
$ make test_unit

# tests (just integrations)
$ make test_integration
```

However if you are in M1 dependency hell, or want consistency with what Jenkins does, run tests via Docker

```bash
# lint, run unit tests, run integration tests
$ make test_docker_all

# lint, run unit tests, run integration tests
$ make test_docker_lint_unit

# lint, run unit tests, run integration tests
$ make test_docker_integration

# run specific test
$ TESTS="tests/models/test_persister.py" docker-compose up --exit-code-from lint-and-test --abort-on-container-exit --build lint-and-test
```

### Updating

To install new dependencies.

```bash
(env) $ pip install -r requirements.txt
(env) $ pip install -r requirements-dev.txt
```

or

```bash
make pip_dev
```

### Environment Variables
Authentication credentials are needed to interface with the Mode API. They should be added to your
local environment.

```
# MODE ACCESS CREDS
export MODE_ACCESS_KEY=
export MODE_SECRET_KEY=

# MODE API ACCESS CREDS
export MODE_API_KEY=
export MODE_API_TOKEN=
```

### Notes

Windows users will not be able to use the `make` commands as Make is a Unix util.
Windows users can attempt to install [GNUWin](http://gnuwin32.sourceforge.net/packages/make.htm) to get this functionality.

### Grass Access Validation

The boilerplate includes module `validation.access` which contains functionality for validating Grass headers in the context of a Flask request. This is valuable for making microservice endpoints compatible for both Grass and non-Grass (i.e. microservice-to-microservice) requests. It makes it easier to facilitate endpoint reuse.

For example, suppose you need to validate that a request where Grass headers are required. Additionally, the `vendor_id` passed in the route must match - thus the Grass Account Type must be 'vendor' and the Grass Account Id must match `vendor_id`. You would use as follows:

```python
@app.route('/vendor/<vendor_id>/something')
def do_something_only_for_vendors(vendor_id):
    """Do something only for vendors.

    Args:
        vendor_id (int): unique identifier for vendor.
    """

    validation = access.verify_grass_access(
        request, required=True, vendor=vendor_id)
    if not validation:
        return validation

    // do something
```

Another example - suppose Grass headers are not required. Additionally, the `subaccount_id` passed in the route must match. The only acceptable Account Type is subaccount:

```python
@app.route('/subaccount/<subaccount_id>/something')
def do_something_only_for_subaccounts(subaccount_id):
    """Do something only for subaccounts.

    Args:
        subaccount_id (int): unique identifier for subaccount.
    """

    account_type, account_id = access.get_grass_headers(request)
    validation = access.verify_grass_access(
        request, required=False, subaccount=subaccount_id)
    if not validation:
        return validation

    // do something
```

Now, building on the last example, we can accept Account Type of vendor AND subaccount:

```python
@app.route('/subaccount/<subaccount_id>/something')
def do_something(subaccount_id):
    """Do something.

    Args:
        subaccount_id (int): unique identifier for subaccount.
    """

    account_type, account_id = access.get_grass_headers(request)
    validation = access.verify_grass_access(
        request, required=False, vendor=account_id, subaccount=subaccount_id)
    if not validation:
        return validation

    // do something
```
