# Analytics Node Client

Analytics Client NodeJS services. Uses the GASv3 platform.

* [Event Guidelines](http://go.atlassian.com/analytics)
* [Trait Guidelines](https://developer.atlassian.com/platform/targeting/overview/concepts/traits/)
* Questions / Other support: visit us in Slack: [#help-analytics-pipeline](https://atlassian.enterprise.slack.com/archives/CFG85MPMX).

## Usage

### Creating a client

```javascript
const { analyticsClient } = require('@atlassiansox/analytics-node-client');

const client = analyticsClient({
    env: 'prod', // prod, stg or dev
    product: 'jira', // required
    subproduct: 'software' // Optional
    sendEventHook: (event) => {} // Optional callback
    perimeter: 'fedramp-moderate' // Optional. The FedRAMP perimeter level.
});
```
**To send events to the FedRAMP stg-apse environment, you need to set the perimeter field to fedramp-moderate**


#### User information

If a `userId` is specified, then a valid `userIdType` is also required. If no `userId` is specified, then `anonymousId` must be used.

We support the following `userIdType`s:

* `ATLASSIAN_ACCOUNT`
* `TRELLO`
* `HASHED_EMAIL`
* `OPSGENIE`
* `HALP`
* `CUSTOMER_ACCOUNT` - This type is the JSM customer account

#### Tenant information

If a `tenantId` is specified, then a valid `tenantIdType` is also required.

We support the following `tenantIdType`s:

* `CLOUD_ID` - Atlassian Cloud
* `HALP_TEAM_ID` - Halp
* `NONE` - Products without a tenant concept

#### Org information

You can provide org information by specifying an `orgId`.

#### Workspace information

If you specify a `workspaceId`, you can use it to provide information about the workspace

#### Containers

The `containers` field is meant to be the new representation of the container hierarchy where the event takes place. Think something like issue -> board -> project.
Check more info about the `containers` field in this [doco](https://hello.atlassian.net/wiki/spaces/ANALYTICS/pages/463696450/DACI+Flexible+entity+hierarchy+in+analytics+events).
There is no strict definition or schema for the fields allowed in the containers; except all containers need to have an `id` field, and can optionally have a `type` field. Only string values are allowed for `id` and `type`. Any other fields added will be silently stripped out before sending the event.
The `containers` field will be validated, and will throw and `Error` if the validation does not pass, preventing the event from being sent.
You need to put the values that will make the analytical events valuable to your use case; most probayly matching the expectations set by some analyst's queries on top of the Socrates, Redash or Amplitude tables.
An example of a `containers` structure:

```json
"containers": {
    "project": {
        "id": "projectId",
        "type": "software"
    },
    "board": {
        "id": "boardId",
        "type": "kanban",
    },
    "issue": {
        "id": "issueId",
        "type": "bug"
    }
}
```

#### Reporting OS info/version

OS version can be optionally reported with `os` attribute of an event:

```javascript
{
    ...
    os: {
        name: os.platform(),
        version: os.version()
    }
}
```

#### Reporting event creation time

Creation time of the analytics event can be optionally reported with the `timestamp` attribute of an event:

```javascript
this.analyticsClient.track({
    ...
    timestamp: new Date()
})
```

For compatibility with the underlying `node-analytics` library the timestamp must be supplied as a Date object.
This creation timestamp will be reflected as `originalTimestamp` after processing.

#### Custom retryDelayFn

This client uses [analytics-node](https://github.com/segmentio/analytics-node) under the hood which uses [axios](https://github.com/axios/axios) to send events and [axios-retry](https://github.com/softonic/axios-retry) to retry sending events in case of failure.

axios-retry uses a function parameter to determine how long the client should wait after a failed HTTP request before trying again. By default, [analytics-node](https://github.com/segmentio/analytics-node/blob/master/index.js#L62) uses [exponential-delay](https://github.com/softonic/axios-retry/blob/master/es/index.js#L77).

If you wish to change this behaviour, you can provide the client with `retryDelayFn` in the main argument object when creating the client.

```javascript
const { analyticsClient } = require('@atlassiansox/analytics-node-client');

const client = analyticsClient({
    env: 'prod', // prod, stg or dev
    product: 'jira', // required
    subproduct: 'software' // Optional
    sendEventHook: (event) => {} // Optional callback\

    // Please dont actually use this, its just an example
    retryDelayFn: (retryAtempt) => retryAtempt * 10 * (0.5 + Math.random()), 
});
```



## Sending a TrackEvent

```javascript
client.sendTrackEvent({
    userId: 'some-user-id',
    anonymousId: 'some-anonymous-id',
    userIdType: 'atlassianAccount',
    tenantIdType: 'cloudId',
    tenantId: 'some-tenant-id',
    orgId: 'some-org-id',
    workspaceId: 'some-workspace-id',
    trackEvent: {
        source: 'api', // required
        action: 'testAction', // required
        actionSubject: 'testActionSubject', // required
        actionSubjectId: 'my-action-subject-id'
        attributes: {
            attribute: 'my-attribute-info',
        },
        containers: { // optional
            project: {
                id: 'b1875f21-434f-4d3f-a57c-2962b154d947', // required
                type: 'kanban' // optional
            },
            board: {
                id: 'b5533697-c14c-442b-8773-03da44741831', // required
                type: 'public' // optional
            }
        }
    }
});
```

### TrackEvent with OS details:

```javascript
client.sendTrackEvent({
    userId: 'some-user-id',
    anonymousId: 'some-anonymous-id',
    userIdType: 'atlassianAccount',
    tenantIdType: 'cloudId',
    tenantId: 'some-tenant-id',
    orgId: 'some-org-id',
    workspaceId: 'some-workspace-id',
    trackEvent: {
        source: 'api', // required
        action: 'testAction', // required
        actionSubject: 'testActionSubject', // required
        actionSubjectId: 'my-action-subject-id'
        attributes: {
            attribute: 'my-attribute-info',
        },
        containers: { // optional
            project: {
                id: 'b1875f21-434f-4d3f-a57c-2962b154d947', // required
                type: 'kanban' // optional
            },
            board: {
                id: 'b5533697-c14c-442b-8773-03da44741831', // required
                type: 'public' // optional
            }
        }
    },
    os: {
        name: os.platformt(),
        version: os.version()
    }
});
```

## Sending an UIEvent

```javascript
client.sendUIEvent({
    userId: 'some-user-id',
    anonymousId: 'some-anonymous-id',
    userIdType: 'atlassianAccount',
    tenantIdType: 'cloudId',
    tenantType: 'my-tenant-type',
    orgId: 'some-org-id',
    workspaceId: 'some-workspace-id',
    uiEvent: {
        source: 'api',
        action: 'testAction', // required
        actionSubject: 'testActionSubject', // required
        actionSubjectId: 'my-action-subject-id',
        attributes: {
            attribute: 'my-attribute-info',
        },
        containers: { // optional
            project: {
                id: 'b1875f21-434f-4d3f-a57c-2962b154d947', // required
                type: 'kanban' // optional
            },
            board: {
                id: 'b5533697-c14c-442b-8773-03da44741831', // required
                type: 'public' // optional
            }
        }
    }
});
```

## Sending a ScreenEvent

```javascript
client.sendScreenEvent({
    userId: 'some-user-id',
    anonymousId: 'some-anonymous-id',
    userIdType: 'atlassianAccount',
    tenantIdType: 'cloudId',
    tenantType: 'my-tenant-type',
    orgId: 'some-org-id',
    workspaceId: 'some-workspace-id',
    name: 'some-screen-name', // required
    screenEvent: {
        origin: 'some-origin', // required
        platform: 'some-platform', // required
        attributes: {
            attribute: 'my-attribute-info',
        },
        containers: { // optional
            project: {
                id: 'b1875f21-434f-4d3f-a57c-2962b154d947', // required
                type: 'kanban' // optional
            },
            board: {
                id: 'b5533697-c14c-442b-8773-03da44741831', // required
                type: 'public' // optional
            }
        }
    }
});
```

## Sending an OperationalEvent

```javascript
client.sendOperationalEvent({
    userId: 'some-user-id',
    anonymousId: 'some-anonymous-id',
    userIdType: 'atlassianAccount',
    tenantIdType: 'cloudId',
    tenantId: 'some-tenant-id',
    orgId: 'some-org-id',
    workspaceId: 'some-workspace-id',
    operationalEvent: {
        source: 'api', // required
        action: 'testAction', // required
        actionSubject: 'testActionSubject', // required
        actionSubjectId: 'my-action-subject-id'
        attributes: {
            attribute: 'my-attribute-info',
        },
        containers: { // optional
            project: {
                id: 'b1875f21-434f-4d3f-a57c-2962b154d947', // required
                type: 'kanban' // optional
            },
            board: {
                id: 'b5533697-c14c-442b-8773-03da44741831', // required
                type: 'public' // optional
            }
        }
    }
});
```

### Set alias or group (optional):

The `aliases` container allows you to assign an extra customer identity to GASv3 analytics Track, UI, Screen and Operational events.

```javascript
analyticsClient.setAlias(
    aliasTypes.A_FUTURE_MEMBER_ID,
    'dummy-1234',
);
```
The `groups` container aids in identifying the account or organization associated with your users.
```javascript
analyticsClient.setGroup(
    groupTypes.TRANSACTION_ACCOUNT_ID,
    'dummy-1234',
);
```
**Note** The `setAlias` and `setGroup` methods will only accept values that are in the enums `aliasTypes` and `groupTypes`. If you have a specific use case and wish to include a new enum, please get in touch with us at [#help-analytics-pipeline](https://atlassian.enterprise.slack.com/archives/CFG85MPMX).

Currently, [we support the following `groupTypes` values](https://bitbucket.org/atlassian/analytics-node-client/src/master/src/constants/group-type.js):
- `TRANSACTION_ACCOUNT_ID`


## Sending a TraitEvent

```javascript
client.sendTraitEvent({
    entityType: entityTypes.ATLASSIAN_ACCOUNT, // required
    entityId: '557058:qweqwe-1793-45a0-9732-9ef656b7ab43', // required
    entityTraits: { // required
        'first-logged-in-to-your-service': new Date() // at least one trait required
    }
});
```

## Error Handling

If an error is encountered while flushing messages, some versions of this
library will throw an uncaught exception which can only be handled in
`process.on('unhandledRejection', ...)`, and will, otherwise, crash your
process.

As of v3.1.0 of this library, you can pass an `errorHandler` function to
the constructor and that function will be called instead of crashing your
process. You can use this function to log the error.

Likely, you don't want your process to crash when you get a 429 from GAS
(essentially creating a hard coupling from your service to GAS), so we
recommend passing this property.

## Developing Locally

Setup: `npm install`
Generating Typescript Types: `npm run generateTypes`
Cleaning up Typescript Types: `npm run cleanTypes`

### Tests:

`npm test`

### Writing changesets:

Changesets are an important part of the development process. Version bumping is automatic and controlled via changeset files.

Please adhere to the [this](https://developer.atlassian.com/cloud/framework/atlassian-frontend/development/versioning/#versioning-guidelines) guideline when selecting the bump type for your package.

Run the command line script `npx changeset`, write changesets that users understand and can take action on and commit changes.

## Migration from v3 to v4 Guide

1. System requirements

- Node >= 14

2. Changes to `flush()`

The `flush()` function has been deprecated. 

If you don’t want to batch messages, you can turn batching off by setting the`maxEventsInBatch` setting to 1, like so:

```
const client = analyticsClient({
  ...
  maxEventsInBatch: 1
});
```

If you want to avoid losing events after shutting down your console. Call `.gracefulShutdown()` to stop collecting new events and flush all existing events.

```
await client .gracefulShutdown()
```

***Note*** Calling `.gracefulShutdown()` will stop collecting new events.

If you need to call gracefulShudown() function and subsequently trigger new events, you should instantiate a new instance of the analytics client.

3. Changes to `flushAt`

The `flushAt` configuration option has been renamed to `maxEventsInBatch`.

4. Changes to `retryDelayFn`

The `retryDelayFn` has been removed from `@segment/analytics-node`. Use `maxRetries` to configure the number of times to retry flushing a batch.

5. Changes to `timeout`

The `timeout` option has been removed from `@segment/analytics-node`. Use `httpRequestTimeout` to configure the maximum number of milliseconds to wait for an http request.
