> ## Documentation Index
> Fetch the complete documentation index at: https://docs.extole.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Add the Activity and Event Views

> Build the report-runner and event-stream views, create the report runner and event stream behind them, and publish so each view resolves its element.

Part of the [partner integration guide](/technical/building-partner-integrations/integration-lifecycle/management-api-integration).

# Overview

This page builds two of an integration's three baseline views: a **report-runner view** charting what the integration processed, and an **event-stream view** carrying a live feed of the events it produces. [Integration Categories](/technical/building-partner-integrations/integration-types/integration-categories) covers what each category charts and streams.

## When You Would Build These

Every build path needs both, whatever the partner does. The third view comes from [Create the Integration Campaign and Component Model](/technical/building-partner-integrations/integration-lifecycle/integration-component-model).

<Warning>
  Each view resolves its element only after the campaign is republished, and skipping a publish returns no error — the activity tab reports no report runner configured and there is no event feed.
</Warning>

## Before You Start

You need an integration campaign whose model component owns a `views` socket and [an access token](/technical/building-partner-integrations/integration-lifecycle/management-api-integration#one-host-two-access-tokens) authorized to manage campaigns, components, report runners, and event streams.

## How to Build

### Create the Two View Components

Name the components `report-runner-view` and `event-streams` — literal names that let a client-local build be compared against the maintained one. Tab labels come from each view's `title`.

Both install into the `views` socket via `installed_into_socket`; `socket_name` is not a property of a component create. Give each `title`, `status`, and `settingsToDisplay`, the last typed `STRING_LIST`.

```bash theme={null}
curl --request POST \
  "$EXTOLE_API_HOST/v2/campaigns/$CAMPAIGN_ID/version/$CAMPAIGN_VERSION/components" \
  --header "Authorization: Bearer $MANAGEMENT_API_ACCESS_TOKEN" \
  --header "Content-Type: application/json" \
  --data '{
    "name": "report-runner-view",
    "display_name": "Example Activity",
    "description": "Daily activity processed by the Example integration.",
    "types": ["report-runner-view-v10.0"],
    "installed_into_socket": "views",
    "component_ids": ["'"$INTEGRATION_COMPONENT_ID"'"],
    "variables": [
      { "name": "order", "type": "INTEGER", "values": { "default": 2 } },
      { "name": "title", "type": "STRING", "values": { "default": "Example Activity" } },
      { "name": "status", "type": "STRING", "values": { "default": "READY" } },
      { "name": "settingsToDisplay", "type": "STRING_LIST", "values": { "default": [] } },
      {
        "name": "reportRunnerId",
        "display_name": "Report Runner ID",
        "type": "STRING",
        "values": {
          "default": "javascript@buildtime:(function(){ let elements = Java.from(context.getComponent().createElementsQuery().withType(\"REPORT_RUNNER\").list()); return elements && elements.length > 0 ? elements[0].getId() : null; })()"
        },
        "tags": ["importance:expert"]
      }
    ]
  }'
```

Create the event-stream view the same way, with `withType("EVENT_STREAM")` in place of `withType("REPORT_RUNNER")`:

```bash theme={null}
curl --request POST \
  "$EXTOLE_API_HOST/v2/campaigns/$CAMPAIGN_ID/version/$CAMPAIGN_VERSION/components" \
  --header "Authorization: Bearer $MANAGEMENT_API_ACCESS_TOKEN" \
  --header "Content-Type: application/json" \
  --data '{
    "name": "event-streams",
    "display_name": "Example Events",
    "description": "Live feed of events produced by the Example integration.",
    "types": ["event-stream-view-v10.0"],
    "installed_into_socket": "views",
    "component_ids": ["'"$INTEGRATION_COMPONENT_ID"'"],
    "variables": [
      { "name": "order", "type": "INTEGER", "values": { "default": 3 } },
      { "name": "title", "type": "STRING", "values": { "default": "Example Events" } },
      { "name": "status", "type": "STRING", "values": { "default": "READY" } },
      { "name": "settingsToDisplay", "type": "STRING_LIST", "values": { "default": [] } },
      {
        "name": "eventStreamId",
        "display_name": "Event Stream ID",
        "type": "STRING",
        "values": {
          "default": "javascript@buildtime:(function(){ let elements = Java.from(context.getComponent().createElementsQuery().withType(\"EVENT_STREAM\").list()); return elements && elements.length > 0 ? elements[0].getId() : null; })()"
        },
        "tags": ["importance:expert"]
      }
    ]
  }'
```

* **Type the element setting `STRING`** — `reportRunnerId` on the report view, `eventStreamId` on the event-stream view; there is no `REPORT_RUNNER_ID` or `EVENT_STREAM_ID` type. Each holds a buildtime query for the element its own component owns, so the view survives the element being recreated.
* **Wrap every Java collection in `Java.from`**, as both expressions do; settings are evaluated as part of the create.
* **Give every view an `order`** typed `INTEGER`, lowest first, configuration at the front.

### Map the Report's Columns to a Chart

Give the report-runner view a `reportColumnsMapping` setting typed `JSON`, the mapping serialized as an escaped string. Every column it names — the axis column and each series column — must be one the runner's `mappings` expression produces, by exactly the name that expression assigns.

```bash theme={null}
curl --request POST \
  "$EXTOLE_API_HOST/v2/campaigns/$CAMPAIGN_ID/version/$CAMPAIGN_VERSION/components/$REPORT_VIEW_COMPONENT_ID/settings" \
  --header "Authorization: Bearer $MANAGEMENT_API_ACCESS_TOKEN" \
  --header "Content-Type: application/json" \
  --data '{
    "name": "reportColumnsMapping",
    "display_name": "Report columns mapping",
    "type": "JSON",
    "values": {
      "default": "{\"chart\":{\"type\":\"line\"},\"xAxis\":{\"column\":\"date\",\"type\":\"datetime\"},\"series\":[{\"name\":\"Count\",\"column\":\"count\",\"aggregation\":\"sum\"}]}"
    },
    "tags": ["importance:expert"]
  }'
```

### Republish Before Attaching Elements

A bundled component declares `elements` inline; the API creates each as its own resource attached by `component_ids` — `report_runners` becomes `POST /v7/report-runners`, `event_streams` becomes `POST /v6/event-streams` with filters added afterward. Attach each to the **view** that displays it: the runner to `$REPORT_VIEW_COMPONENT_ID`, the stream to `$EVENT_STREAM_VIEW_COMPONENT_ID`.

Publish now, after creating the views and before creating their elements: a `component_ids` reference resolves against the published campaign, the same rule that governs webhooks and reward suppliers.

```bash theme={null}
curl --request POST \
  "$EXTOLE_API_HOST/v2/campaigns/$CAMPAIGN_ID/version/$CAMPAIGN_VERSION/publish" \
  --header "Authorization: Bearer $MANAGEMENT_API_ACCESS_TOKEN" \
  --header "Content-Type: application/json" \
  --data '{}'
```

<Note>
  The publish operation is **not carried in the OpenAPI specification**, so a reference lookup returns nothing found. This page and [Validate and Publish an Integration](/technical/building-partner-integrations/integration-lifecycle/integration-validation) are its documentation.
</Note>

### Choose or Create the Report Type

`report_type` is an account-scoped identifier, so read the account's types and match on display name. Filter the listing: a mature account holds a couple of hundred types and the unfiltered response runs to roughly a megabyte.

```bash theme={null}
curl --request GET "$EXTOLE_API_HOST/v6/report-types?display_name=Customer%20Activity" \
  --header "Authorization: Bearer $MANAGEMENT_API_ACCESS_TOKEN"
```

The listing accepts `display_name`, `search_query`, `report_type_id`, `tags`, `limit`, and `offset`. **Pass the type's `name` as `report_type`** — an opaque string such as `r84a5841xf0hehbzsf6j`. A report type has no `id` field, so a projection asking for one returns a list of nulls. Read a candidate in full with `GET /v6/report-types/{id}`, whose path segment takes that same `name`, before choosing between types sharing a display name. Re-read before acting on an empty result: a truncated response is not an absent type.

Two properties decide whether a type works:

* **The parameters it declares** are the only ones the runner may send, with values from its own enumerations: a time range is `ALL_TIME`, not `all_time`, and a locale list accepts only locales the account declares. Add them one at a time when one is refused.
* **The mappings dialect it accepts.** A row-shaped `mappings` parameter rejects the grouping functions `group_count` and `GROUP_SUM` a charted activity report needs; a metric-shaped one accepts them. Read the parameter's type before writing the expression, and choose the parent by that, not by a close-sounding display name.

A configured report type is a saved set of parameter defaults over a parent type:

```bash theme={null}
curl --request POST "$EXTOLE_API_HOST/v6/report-types" \
  --header "Authorization: Bearer $MANAGEMENT_API_ACCESS_TOKEN" \
  --header "Content-Type: application/json" \
  --data '{
    "display_name": "Example Activity",
    "description": "Activity processed by the Example integration.",
    "type": "CONFIGURED",
    "parent_report_type_id": "'"$PARENT_REPORT_TYPE_ID"'",
    "categories": ["Customer Activity"],
    "scopes": ["CLIENT_SUPERUSER"],
    "allowed_scopes": ["CLIENT_ADMIN", "CLIENT_SUPERUSER"],
    "visibility": "PUBLIC",
    "formats": ["CSV", "JSON"],
    "parameters": [
      { "name": "mappings", "default_value": "date=START_DATE(event.eventTime, period:\"DAY\"); count=group_count(event.id, step_name:\"converted\")" },
      { "name": "container", "default_value": "production" },
      { "name": "time_range", "default_value": "" },
      { "name": "campaign_states", "default_value": "" },
      { "name": "visit_type", "default_value": "" },
      { "name": "unattributed_events", "default_value": "" },
      { "name": "quality", "default_value": "" }
    ]
  }'
```

Name **every** parameter the parent declares, empty default for the ones you do not set: a configured type declares what its runners may pass, and the runner below sends seven. Read the parent with `GET /v6/report-types` and copy its parameter names.

### Build the Report Behind the Activity Tab

A report runner is a scheduled report, its parameter values, and an attachment to the view that charts it:

```bash theme={null}
curl --request POST "$EXTOLE_API_HOST/v7/report-runners" \
  --header "Authorization: Bearer $MANAGEMENT_API_ACCESS_TOKEN" \
  --header "Content-Type: application/json" \
  --data '{
    "type": "SCHEDULED",
    "name": "Partner Example Activity Report",
    "report_type": "'"$REPORT_TYPE_ID"'",
    "formats": ["CSV", "JSON"],
    "scopes": ["CLIENT_SUPERUSER"],
    "tags": ["partner-graph"],
    "frequency": "WEEKLY",
    "schedule_start_date": "'"$SCHEDULE_START_DATE"'",
    "enabled": true,
    "execution_policy": "AWAIT_DATA",
    "parameters": {
      "container": "production",
      "time_range": "ALL_TIME",
      "campaign_states": "ALL",
      "visit_type": "NEW_TO_CLIENT",
      "unattributed_events": "false",
      "quality": "ALL",
      "mappings": "date=START_DATE(event.eventTime, period:\"DAY\"); count=group_count(event.id, step_name:\"converted\")"
    },
    "component_ids": ["'"$REPORT_VIEW_COMPONENT_ID"'"]
  }'
```

The `mappings` expression is where the partner shows up: count and group the events this integration produces — business events a Partner to Extole build maps to, reward events a fulfillment partner generates, activity an Extole to Partner install forwards — and give each series a column the chart mapping can name.

Where the partner page publishes a runner contract, copy it literally: name, schedule, formats, tags, scopes, execution policy, and every parameter and mapping expression.

Give `schedule_start_date` an ISO-8601 timestamp with an offset, dated in the future. A runner's type is fixed once created: one made `REFRESHING` cannot become scheduled.

### Build the Event Stream Behind the Events Tab

Create the stream after the republish above, then add each filter. Filters are created under the stream with a `type` discriminator in the body rather than a path segment, the opposite of reward webhook filters. The create returns the `EVENT_STREAM_ID` filters need.

```bash theme={null}
curl --request POST "$EXTOLE_API_HOST/v6/event-streams" \
  --header "Authorization: Bearer $MANAGEMENT_API_ACCESS_TOKEN" \
  --header "Content-Type: application/json" \
  --data '{
    "name": "javascript@buildtime:context.getComponent().getName() + '\'' Events'\''",
    "description": "A live feed of events produced by the Example integration. The feed runs for 1 hour by default. Refresh the feed to poll for new events.",
    "tags": ["internal:app_type=example"],
    "component_ids": ["'"$EVENT_STREAM_VIEW_COMPONENT_ID"'"]
  }'

curl --request POST "$EXTOLE_API_HOST/v6/event-streams/$EVENT_STREAM_ID/filters" \
  --header "Authorization: Bearer $MANAGEMENT_API_ACCESS_TOKEN" \
  --header "Content-Type: application/json" \
  --data '{"type": "APPLICATION_TYPE", "app_types": ["example"]}'
```

Filter the feed to this integration's activity: `APPLICATION_TYPE` narrows it to the partner's app type and belongs on every stream. Add an `EVENT_TYPE` filter when the integration produces one recognizable class of event, as reward fulfillment does with `{"type": "EVENT_TYPE", "event_types": ["REWARD", "SEND_REWARD"]}`.

### Republish Again So the Views Resolve Their Elements

Neither view stores the identifier. Each carries a `javascript@buildtime` query for the element attached to its own component:

```text theme={null}
javascript@buildtime:(function(){ let elements = Java.from(context.getComponent().createElementsQuery().withType('REPORT_RUNNER').list()); return elements && elements.length > 0 ? elements[0].getId() : null; })();
```

The event-stream view carries the same query with `withType('EVENT_STREAM')`. Both evaluate when the campaign is built and return null when nothing of that type is attached. So the sequence is publish, create the elements, publish again:

```bash theme={null}
CAMPAIGN_VERSION=$(
  curl --silent --show-error --fail-with-body \
    "$EXTOLE_API_HOST/v2/campaigns/$CAMPAIGN_ID" \
    --header "Authorization: Bearer $MANAGEMENT_API_ACCESS_TOKEN" |
  jq --raw-output '.version'
)

curl --request POST \
  "$EXTOLE_API_HOST/v2/campaigns/$CAMPAIGN_ID/version/$CAMPAIGN_VERSION/publish" \
  --header "Authorization: Bearer $MANAGEMENT_API_ACCESS_TOKEN" \
  --header "Content-Type: application/json" \
  --data '{}'
```

[Refresh `$CAMPAIGN_VERSION`](/technical/building-partner-integrations/integration-lifecycle/management-api-integration#refresh-the-campaign-version-between-mutations) first.

## How to Test

Read the **built** campaign — a component's own definition holds the buildtime query, not an identifier — and confirm:

* `reportRunnerId` and `eventStreamId` are non-null.
* Each element's `component_ids` names its own view.
* The stream carries `APPLICATION_TYPE`, plus any `EVENT_TYPE` filter needed.
* Every column `reportColumnsMapping` names is one the runner's `mappings` produces.
* `schedule_start_date` is in the future.

### Error Handling

| Response                                                                | Cause                                                                                                                                           | Fix                                                  |
| ----------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------- |
| `invalid_component_reference`                                           | The element's view was added since the last publish                                                                                             | Publish first                                        |
| `campaign_build_failed` naming a variable                               | A buildtime expression iterated a Java collection without `Java.from`                                                                           | Wrap it and recreate                                 |
| `variable_value_invalid_type` on `reportColumnsMapping`                 | Default sent as a nested object                                                                                                                 | Send an escaped JSON string                          |
| Malformed JSON echoing an invented type                                 | Setting typed `REPORT_RUNNER_ID` or `EVENT_STREAM_ID`                                                                                           | Type both `STRING`                                   |
| Validation against three subschemas                                     | `title`, `status`, or `settingsToDisplay` missing, or `settingsToDisplay` typed `JSON`                                                          | Send all three, `settingsToDisplay` as `STRING_LIST` |
| Invalid format on `parameters`                                          | An undeclared parameter, such as `time_range` against a configured type omitting it, a missing required one, or a value outside the enumeration | Add declared parameters one at a time                |
| An attempt to remove static parameters                                  | A configured type named only some of the parent's parameters                                                                                    | Name every one, empty default for the rest           |
| A chart with no data                                                    | `reportColumnsMapping` absent, or naming a column the report does not produce                                                                   | Write it with the runner's `mappings`                |
| Runner enabled but producing nothing                                    | `schedule_start_date` in the past                                                                                                               | Recreate with a future date                          |
| An update reports the wrong type                                        | Created `REFRESHING`, updated to `SCHEDULED`                                                                                                    | Delete it and recreate                               |
| `reportRunnerId` or `eventStreamId` null though the element is attached | No publish since the element was created                                                                                                        | Refresh the version and publish                      |
| Activity tab reports no report runner configured                        | Runner attached to the integration component or the event-stream view                                                                           | Recreate against `$REPORT_VIEW_COMPONENT_ID`         |
