@trackunit/iris-app 1.12.7 → 1.12.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +30 -0
- package/package.json +3 -3
- package/src/generators/ai-agent-sync/README.md +5 -2
- package/src/generators/ai-agent-sync/generator.d.ts +1 -1
- package/src/generators/ai-agent-sync/generator.js +29 -4
- package/src/generators/ai-agent-sync/generator.js.map +1 -1
- package/src/generators/preset/files/.agents/skills/browser-testing/SKILL.md +193 -0
- package/src/generators/preset/files/.agents/skills/create-app/SKILL.md +191 -0
- package/src/generators/preset/files/.agents/skills/customfields/SKILL.md +239 -0
- package/src/generators/preset/files/.agents/skills/graphql/SKILL.md +147 -0
- package/src/generators/preset/files/.agents/skills/graphql-timeseries/SKILL.md +193 -0
- package/src/generators/preset/files/.agents/skills/irisx-app-sdk/SKILL.md +116 -0
- package/src/generators/preset/files/.agents/skills/react-core-hooks/SKILL.md +215 -0
- package/src/generators/preset/files/.agents/skills/tables-and-sorting/SKILL.md +122 -0
- package/src/generators/preset/files/.agents/skills/widget-extensions/SKILL.md +245 -0
- package/src/generators/preset/files/.cursor/mcp.json +4 -0
- package/src/generators/preset/root-files/AGENTS.md +43 -0
- package/src/generators/preset/root-files/CLAUDE.md +17 -0
- package/src/generators/preset/files/.cursor/commands/create-app.md +0 -226
- package/src/generators/preset/files/.cursor/rules/browser-irisx-development.mdc +0 -246
- package/src/generators/preset/files/.cursor/rules/graphql-timeseries.md +0 -260
- package/src/generators/preset/files/.cursor/rules/irisx-app-sdk-customfields.md +0 -305
- package/src/generators/preset/files/.cursor/rules/irisx-app-sdk-graphql.md +0 -30
- package/src/generators/preset/files/.cursor/rules/irisx-app-sdk.mdc +0 -82
- package/src/generators/preset/files/.cursor/rules/react-core-hooks.md +0 -155
- package/src/generators/preset/files/.cursor/rules/rules-index.mdc +0 -10
- package/src/generators/preset/files/.cursor/rules/structured-development.mdc +0 -86
- package/src/generators/preset/files/.cursor/rules/tables-and-sorting.mdc +0 -126
- package/src/generators/preset/files/.cursor/rules/widget-extensions.md +0 -323
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: customfields
|
|
3
|
+
description: Use when storing app data on entities, persisting user input, defining custom fields in manifest, or reading/writing entity-specific data.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# CustomFields
|
|
7
|
+
|
|
8
|
+
## Overview
|
|
9
|
+
|
|
10
|
+
CustomFields extend the Trackunit data model by storing additional information on entities (assets, accounts, groups, sites). This is the primary mechanism for persisting IrisX app data.
|
|
11
|
+
|
|
12
|
+
## When to Use
|
|
13
|
+
|
|
14
|
+
- Storing user input or preferences
|
|
15
|
+
- Adding metadata to assets/accounts/groups/sites
|
|
16
|
+
- Persisting app-specific configuration
|
|
17
|
+
- Creating custom KPIs or fleet attributes
|
|
18
|
+
|
|
19
|
+
**Not for:** External databases or storage solutions (use CustomFields instead).
|
|
20
|
+
|
|
21
|
+
## Defining Custom Fields
|
|
22
|
+
|
|
23
|
+
### Manifest Configuration
|
|
24
|
+
|
|
25
|
+
```typescript
|
|
26
|
+
customFieldDefinitions: [
|
|
27
|
+
{
|
|
28
|
+
type: 'STRING',
|
|
29
|
+
entityType: 'ASSET',
|
|
30
|
+
key: 'myCustomField',
|
|
31
|
+
translations: [{
|
|
32
|
+
language: 'en',
|
|
33
|
+
title: 'My Custom Field',
|
|
34
|
+
description: 'Description of what this field stores',
|
|
35
|
+
}],
|
|
36
|
+
uiEditable: true,
|
|
37
|
+
uiVisible: true,
|
|
38
|
+
scopeType: 'ACCOUNT' // or 'ACCOUNT_WRITE_GLOBAL_READ' or 'GLOBAL'
|
|
39
|
+
}
|
|
40
|
+
]
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### Required Properties
|
|
44
|
+
|
|
45
|
+
| Property | Description |
|
|
46
|
+
|----------|-------------|
|
|
47
|
+
| `type` | Field data type |
|
|
48
|
+
| `entityType` | `ACCOUNT`, `ASSET`, `GROUP`, or `SITE` |
|
|
49
|
+
| `key` | Programmatic identifier (immutable after creation) |
|
|
50
|
+
| `translations` | UI labels and descriptions |
|
|
51
|
+
| `scopeType` | Data access scope |
|
|
52
|
+
|
|
53
|
+
### Optional Properties
|
|
54
|
+
|
|
55
|
+
| Property | Default | Description |
|
|
56
|
+
|----------|---------|-------------|
|
|
57
|
+
| `uiEditable` | `true` | Can be edited in Manager UI |
|
|
58
|
+
| `uiVisible` | `true` | Visible in Manager UI |
|
|
59
|
+
|
|
60
|
+
## Supported Field Types
|
|
61
|
+
|
|
62
|
+
| Type | Description | Extra Properties |
|
|
63
|
+
|------|-------------|------------------|
|
|
64
|
+
| `BOOLEAN` | True/false values | - |
|
|
65
|
+
| `DATE` | ISO8601 formatted dates | - |
|
|
66
|
+
| `DROPDOWN` | Predefined option lists | `allValues`, `multiSelect`, `valueReplacements` |
|
|
67
|
+
| `EMAIL` | Email addresses with mailto links | - |
|
|
68
|
+
| `NUMBER` | Numeric values | `minimum`, `maximum`, `isInteger`, `unitSi`, `unitUs` |
|
|
69
|
+
| `PHONE_NUMBER` | E.164 formatted phone numbers | - |
|
|
70
|
+
| `STRING` | Free text | `minimumLength`, `maximumLength`, `pattern` |
|
|
71
|
+
| `STRING_LIST` | Array of strings | - |
|
|
72
|
+
| `WEB_ADDRESS` | URLs as clickable links | - |
|
|
73
|
+
| `JSON` | JSON objects | - |
|
|
74
|
+
| `FILE` | File attachments | - |
|
|
75
|
+
| `MONETARY` | Currency values (ISO 4217) | `currency` |
|
|
76
|
+
|
|
77
|
+
## Example Field Definitions
|
|
78
|
+
|
|
79
|
+
### String with Validation
|
|
80
|
+
|
|
81
|
+
```typescript
|
|
82
|
+
{
|
|
83
|
+
type: 'STRING',
|
|
84
|
+
entityType: 'ASSET',
|
|
85
|
+
key: 'serialNumber',
|
|
86
|
+
translations: [{ language: 'en', title: 'Serial Number', description: 'Asset serial number' }],
|
|
87
|
+
minimumLength: 5,
|
|
88
|
+
maximumLength: 20,
|
|
89
|
+
pattern: '^[A-Z0-9]+$'
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### Number with Units
|
|
94
|
+
|
|
95
|
+
```typescript
|
|
96
|
+
{
|
|
97
|
+
type: 'NUMBER',
|
|
98
|
+
entityType: 'ASSET',
|
|
99
|
+
key: 'fuelCapacity',
|
|
100
|
+
translations: [{ language: 'en', title: 'Fuel Capacity', description: 'Tank capacity' }],
|
|
101
|
+
minimum: 0,
|
|
102
|
+
maximum: 1000,
|
|
103
|
+
unitSi: 'L',
|
|
104
|
+
unitUs: 'gal'
|
|
105
|
+
}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### Dropdown with Multiple Selection
|
|
109
|
+
|
|
110
|
+
```typescript
|
|
111
|
+
{
|
|
112
|
+
type: 'DROPDOWN',
|
|
113
|
+
entityType: 'ASSET',
|
|
114
|
+
key: 'features',
|
|
115
|
+
translations: [{ language: 'en', title: 'Features', description: 'Available features' }],
|
|
116
|
+
allValues: ['GPS', 'Camera', 'Bluetooth', 'WiFi'],
|
|
117
|
+
multiSelect: true
|
|
118
|
+
}
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
## Scope Types
|
|
122
|
+
|
|
123
|
+
| Scope | Description |
|
|
124
|
+
|-------|-------------|
|
|
125
|
+
| `ACCOUNT` | Values shared within a single account only |
|
|
126
|
+
| `ACCOUNT_WRITE_GLOBAL_READ` | Account can write, all accounts can read |
|
|
127
|
+
| `GLOBAL` | Values shared between all accounts with entity access |
|
|
128
|
+
|
|
129
|
+
## Using CustomFields in React
|
|
130
|
+
|
|
131
|
+
### Required Imports
|
|
132
|
+
|
|
133
|
+
```typescript
|
|
134
|
+
import {
|
|
135
|
+
useCustomFieldsValueAndDefinition,
|
|
136
|
+
useUpdateCustomFieldValues
|
|
137
|
+
} from '@trackunit/custom-field-api';
|
|
138
|
+
import { CustomField } from '@trackunit/custom-field-components';
|
|
139
|
+
import { useCurrentUserSystemOfMeasurement } from '@trackunit/react-core-hooks';
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
### Basic Implementation
|
|
143
|
+
|
|
144
|
+
```typescript
|
|
145
|
+
import React from 'react';
|
|
146
|
+
import { useForm } from 'react-hook-form';
|
|
147
|
+
import { Card, CardBody, CardHeader, CardFooter, Button, Spinner } from '@trackunit/react-components';
|
|
148
|
+
import { useCustomFieldsValueAndDefinition, useUpdateCustomFieldValues } from '@trackunit/custom-field-api';
|
|
149
|
+
import { CustomField } from '@trackunit/custom-field-components';
|
|
150
|
+
import { useCurrentUserSystemOfMeasurement } from '@trackunit/react-core-hooks';
|
|
151
|
+
|
|
152
|
+
const CustomFieldsForm = ({ assetId }: { assetId: string }) => {
|
|
153
|
+
const { register, handleSubmit, formState, setValue, control } = useForm({
|
|
154
|
+
shouldUnregister: false,
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
const { systemOfMeasurement } = useCurrentUserSystemOfMeasurement();
|
|
158
|
+
|
|
159
|
+
const { fields, loading } = useCustomFieldsValueAndDefinition({
|
|
160
|
+
entityType: "ASSET",
|
|
161
|
+
entityId: assetId,
|
|
162
|
+
systemOfMeasurement: systemOfMeasurement ?? "SI",
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
const { updateCustomFields, inProgress } = useUpdateCustomFieldValues(
|
|
166
|
+
assetId,
|
|
167
|
+
"ASSET",
|
|
168
|
+
fields,
|
|
169
|
+
systemOfMeasurement ?? "SI"
|
|
170
|
+
);
|
|
171
|
+
|
|
172
|
+
if (loading) return <Spinner />;
|
|
173
|
+
|
|
174
|
+
return (
|
|
175
|
+
<Card>
|
|
176
|
+
<CardHeader heading="Custom Fields" />
|
|
177
|
+
<CardBody>
|
|
178
|
+
{fields.map(field => {
|
|
179
|
+
const isEditable = field.valueEditable && (field.definition.uiEditable ?? true);
|
|
180
|
+
return (
|
|
181
|
+
<CustomField
|
|
182
|
+
key={field.definition.key}
|
|
183
|
+
control={control}
|
|
184
|
+
field={field}
|
|
185
|
+
formState={formState}
|
|
186
|
+
isEditable={isEditable}
|
|
187
|
+
register={register}
|
|
188
|
+
setValue={setValue}
|
|
189
|
+
unitPreference={systemOfMeasurement}
|
|
190
|
+
/>
|
|
191
|
+
);
|
|
192
|
+
})}
|
|
193
|
+
</CardBody>
|
|
194
|
+
<CardFooter>
|
|
195
|
+
<Button
|
|
196
|
+
loading={inProgress}
|
|
197
|
+
onClick={handleSubmit(updateCustomFields)}
|
|
198
|
+
>
|
|
199
|
+
{inProgress ? "Saving..." : "Save Changes"}
|
|
200
|
+
</Button>
|
|
201
|
+
</CardFooter>
|
|
202
|
+
</Card>
|
|
203
|
+
);
|
|
204
|
+
};
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
## Best Practices
|
|
208
|
+
|
|
209
|
+
### Field Design
|
|
210
|
+
- **Use descriptive keys**: Choose clear, descriptive field keys that won't need changing
|
|
211
|
+
- **Add proper translations**: Always provide meaningful titles and descriptions
|
|
212
|
+
- **Set appropriate constraints**: Use min/max values, length limits, and patterns for validation
|
|
213
|
+
|
|
214
|
+
### Performance
|
|
215
|
+
- **Batch updates**: Use `useUpdateCustomFieldValues` to update multiple fields at once
|
|
216
|
+
- **Handle loading states**: Always show loading indicators while fetching data
|
|
217
|
+
- **Error handling**: Implement proper error handling for failed updates
|
|
218
|
+
|
|
219
|
+
### Deployment
|
|
220
|
+
- **Field keys are immutable**: Cannot be changed after creation
|
|
221
|
+
- **Definition updates**: Changing field definitions affects all existing data
|
|
222
|
+
- **Approval process**: Custom field changes require app approval and marketplace submission
|
|
223
|
+
|
|
224
|
+
## Common Mistakes
|
|
225
|
+
|
|
226
|
+
| Mistake | Fix |
|
|
227
|
+
|---------|-----|
|
|
228
|
+
| Changing field key after creation | Keys are immutable; plan carefully upfront |
|
|
229
|
+
| Missing translations | Always provide titles and descriptions for UI |
|
|
230
|
+
| Using external storage | CustomFields are the recommended persistence mechanism |
|
|
231
|
+
| No loading states | Show `<Spinner />` while fetching data |
|
|
232
|
+
| Skipping error handling | Handle failed updates gracefully |
|
|
233
|
+
|
|
234
|
+
## References
|
|
235
|
+
|
|
236
|
+
- [Save Data from Your App](https://developers.trackunit.com/docs/save-data-from-your-app)
|
|
237
|
+
- [Defining a Custom Field](https://developers.trackunit.com/docs/defining-a-custom-field)
|
|
238
|
+
- [Using a Custom Field](https://developers.trackunit.com/docs/using-a-custom-field)
|
|
239
|
+
- [Deploying a New Custom Field](https://developers.trackunit.com/docs/deploying-a-new-custom-field)
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: graphql
|
|
3
|
+
description: Use when setting up GraphQL tooling, writing queries/mutations, generating hooks, or fetching data from Trackunit API.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# GraphQL API Integration
|
|
7
|
+
|
|
8
|
+
## Overview
|
|
9
|
+
|
|
10
|
+
Trackunit exposes a GraphQL API with NX executors for code generation. Use `@trackunit/react-graphql-tools` to generate typed React hooks from `.graphql` files.
|
|
11
|
+
|
|
12
|
+
## When to Use
|
|
13
|
+
|
|
14
|
+
- Setting up GraphQL in an extension
|
|
15
|
+
- Writing queries or mutations
|
|
16
|
+
- Generating React hooks from `.graphql` files
|
|
17
|
+
- Fetching data from Trackunit API
|
|
18
|
+
|
|
19
|
+
**Not for:** Time series data (use `graphql-timeseries` skill).
|
|
20
|
+
|
|
21
|
+
## Setup
|
|
22
|
+
|
|
23
|
+
### Install GraphQL Tools
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
npm install @trackunit/react-graphql-tools
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### Set Up GraphQL Tooling
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
npx nx generate @trackunit/react-graphql-tools:add-graphql --project=[project-name]
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Where `[project-name]` is found in the library's `project.json` file (format: `[subdir-name]-[extension-name]` for extensions in subdirectories).
|
|
36
|
+
|
|
37
|
+
### Generate React Hooks
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
npx nx run [feature-name]-[extension-name]:graphql-hooks
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
This generates React hooks from `.graphql` files in the `src` folder.
|
|
44
|
+
|
|
45
|
+
## GraphQL File Structure
|
|
46
|
+
|
|
47
|
+
Create `.graphql` files in `libs/[feature-name]/[extension-name]/src/` directory.
|
|
48
|
+
|
|
49
|
+
### Query Syntax
|
|
50
|
+
|
|
51
|
+
```graphql
|
|
52
|
+
query QueryName($variable: Type) {
|
|
53
|
+
fieldName {
|
|
54
|
+
subField
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### Mutation Syntax
|
|
60
|
+
|
|
61
|
+
```graphql
|
|
62
|
+
mutation MutationName($variable: Type!) {
|
|
63
|
+
mutationField(input: $variable) {
|
|
64
|
+
result
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## React Integration
|
|
70
|
+
|
|
71
|
+
### Import Generated Hooks
|
|
72
|
+
|
|
73
|
+
```typescript
|
|
74
|
+
import { QueryNameDocument } from "./generated/graphql-api/graphql";
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### Use Queries
|
|
78
|
+
|
|
79
|
+
```typescript
|
|
80
|
+
import { useQuery } from '@trackunit/react-graphql-hooks';
|
|
81
|
+
|
|
82
|
+
const { data, loading, error } = useQuery(QueryNameDocument, {
|
|
83
|
+
variables: { /* your variables */ }
|
|
84
|
+
});
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### Use Mutations
|
|
88
|
+
|
|
89
|
+
```typescript
|
|
90
|
+
import { useMutation } from '@apollo/client';
|
|
91
|
+
|
|
92
|
+
const [mutationName, { data, loading, error }] = useMutation(MutationNameDocument);
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## Common Patterns
|
|
96
|
+
|
|
97
|
+
### Query with Variables
|
|
98
|
+
|
|
99
|
+
```typescript
|
|
100
|
+
const { data, loading, error } = useQuery(GetAssetDocument, {
|
|
101
|
+
variables: { assetId: "123" }
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
if (loading) return <Spinner />;
|
|
105
|
+
if (error) return <ErrorMessage error={error} />;
|
|
106
|
+
|
|
107
|
+
return <AssetDisplay asset={data?.asset} />;
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### Mutation with Refetch
|
|
111
|
+
|
|
112
|
+
```typescript
|
|
113
|
+
import { useMutation } from '@apollo/client';
|
|
114
|
+
|
|
115
|
+
const [updateAsset, { loading }] = useMutation(UpdateAssetDocument, {
|
|
116
|
+
refetchQueries: [{ query: GetAssetDocument, variables: { assetId } }]
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
const handleUpdate = async (input) => {
|
|
120
|
+
await updateAsset({ variables: { input } });
|
|
121
|
+
};
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### Polling
|
|
125
|
+
|
|
126
|
+
```typescript
|
|
127
|
+
const { data } = useQuery(GetAssetsDocument, {
|
|
128
|
+
pollInterval: 5000, // Poll every 5 seconds
|
|
129
|
+
});
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
## References
|
|
133
|
+
|
|
134
|
+
- [Calling Trackunit GraphQL API](https://developers.trackunit.com/docs/graphql-api)
|
|
135
|
+
- [Apollo useQuery documentation](https://www.apollographql.com/docs/react/data/queries)
|
|
136
|
+
- [Apollo useMutation documentation](https://www.apollographql.com/docs/react/data/mutations)
|
|
137
|
+
- [GraphQL Code Generator](https://the-guild.dev/graphql/codegen)
|
|
138
|
+
|
|
139
|
+
## Common Mistakes
|
|
140
|
+
|
|
141
|
+
| Mistake | Fix |
|
|
142
|
+
|---------|-----|
|
|
143
|
+
| Using generic GraphQL libraries | Use `@trackunit/react-graphql-tools` only |
|
|
144
|
+
| Missing hook generation | Run `nx run [project]:graphql-hooks` after adding `.graphql` files |
|
|
145
|
+
| Wrong project name format | Use `[subdir-name]-[extension-name]` from `project.json` |
|
|
146
|
+
| No loading/error states | Always handle `loading` and `error` from hooks |
|
|
147
|
+
| Importing from wrong path | Import from `./generated/graphql-api/graphql` |
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: graphql-timeseries
|
|
3
|
+
description: Use when querying machine insights, engine metrics, fuel data, operating hours, or building charts with historical time-based data.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# GraphQL Time Series API
|
|
7
|
+
|
|
8
|
+
## Overview
|
|
9
|
+
|
|
10
|
+
Query time series data through GraphQL using PromQL (Prometheus Query Language). Use `rangeQuery` for time series charts, `instantQuery` for current values.
|
|
11
|
+
|
|
12
|
+
## When to Use
|
|
13
|
+
|
|
14
|
+
- Querying engine metrics (speed, temperature, fuel rate)
|
|
15
|
+
- Building historical data charts
|
|
16
|
+
- Calculating increases over time periods (daily fuel usage, operating hours)
|
|
17
|
+
- Getting current sensor values
|
|
18
|
+
|
|
19
|
+
**Not for:** Static entity data (use `graphql` skill).
|
|
20
|
+
|
|
21
|
+
## Query Structure
|
|
22
|
+
|
|
23
|
+
### Range Query (Time Series Data)
|
|
24
|
+
|
|
25
|
+
```graphql
|
|
26
|
+
query GetTimeSeriesData($assetId: ID!, $start: DateTime!, $end: DateTime!, $step: Duration!) {
|
|
27
|
+
asset(id: $assetId) {
|
|
28
|
+
timeSeries {
|
|
29
|
+
rangeQuery(query: "metric_name", start: $start, end: $end, step: $step) {
|
|
30
|
+
data {
|
|
31
|
+
... on TimeSeriesMatrixData {
|
|
32
|
+
result {
|
|
33
|
+
values { timestamp value }
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### Instant Query (Single Point in Time)
|
|
44
|
+
|
|
45
|
+
```graphql
|
|
46
|
+
query GetInstantData($assetId: ID!, $time: DateTime!) {
|
|
47
|
+
asset(id: $assetId) {
|
|
48
|
+
timeSeries {
|
|
49
|
+
instantQuery(query: "metric_name", time: $time) {
|
|
50
|
+
data {
|
|
51
|
+
... on TimeSeriesVectorData {
|
|
52
|
+
result { value { timestamp value } }
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Common Metrics
|
|
62
|
+
|
|
63
|
+
### Machine Insights
|
|
64
|
+
|
|
65
|
+
Not all assets have all insights. Common metrics include:
|
|
66
|
+
|
|
67
|
+
**Engine Metrics:**
|
|
68
|
+
- `machine_insight_engine_speed`
|
|
69
|
+
- `machine_insight_engine_coolant_temperature`
|
|
70
|
+
- `machine_insight_engine_oil_pressure`
|
|
71
|
+
- `machine_insight_engine_oil_temperature`
|
|
72
|
+
- `machine_insight_engine_fuel_rate`
|
|
73
|
+
- `machine_insight_engine_total_fuel_used`
|
|
74
|
+
- `machine_insight_engine_percent_load_at_current_speed`
|
|
75
|
+
- `machine_insight_cumulative_engine_hours`
|
|
76
|
+
|
|
77
|
+
**Fuel & Emissions:**
|
|
78
|
+
- `machine_insight_fuel_level`
|
|
79
|
+
- `machine_insight_fuel_tank_capacity`
|
|
80
|
+
- `machine_insight_fuel_used_last_24`
|
|
81
|
+
- `machine_insight_cumulative_co2_emissions`
|
|
82
|
+
|
|
83
|
+
**Operating Hours:**
|
|
84
|
+
- `machine_insight_cumulative_operating_hours`
|
|
85
|
+
- `machine_insight_cumulative_idle_hours`
|
|
86
|
+
- `machine_insight_cumulative_moving_hours`
|
|
87
|
+
- `machine_insight_cumulative_productive_hours`
|
|
88
|
+
|
|
89
|
+
**Battery & Electric:**
|
|
90
|
+
- `machine_insight_battery_state_of_charge_percent`
|
|
91
|
+
- `machine_insight_battery_state_of_health_percent`
|
|
92
|
+
- `machine_insight_battery_temperature`
|
|
93
|
+
- `machine_insight_battery_current`
|
|
94
|
+
- `machine_insight_battery_potential`
|
|
95
|
+
|
|
96
|
+
**Location & Movement:**
|
|
97
|
+
- `machine_insight_speed`
|
|
98
|
+
- `machine_insight_altitude`
|
|
99
|
+
- `machine_insight_total_vehicle_distance`
|
|
100
|
+
|
|
101
|
+
**Environmental:**
|
|
102
|
+
- `machine_insight_ambient_air_temperature`
|
|
103
|
+
- `machine_insight_barometric_pressure`
|
|
104
|
+
|
|
105
|
+
## PromQL Examples
|
|
106
|
+
|
|
107
|
+
### Calculate Daily Increases
|
|
108
|
+
|
|
109
|
+
```graphql
|
|
110
|
+
query IncreaseOperatingHours($assetId: ID!, $start: DateTime!, $end: DateTime!, $step: Duration!) {
|
|
111
|
+
asset(id: $assetId) {
|
|
112
|
+
timeSeries {
|
|
113
|
+
rangeQuery(
|
|
114
|
+
query: "increase(machine_insight_cumulative_operating_hours[1d])",
|
|
115
|
+
start: $start,
|
|
116
|
+
end: $end,
|
|
117
|
+
step: $step
|
|
118
|
+
) {
|
|
119
|
+
data {
|
|
120
|
+
... on TimeSeriesMatrixData {
|
|
121
|
+
result { values { timestamp value } }
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### Filter with Conditions
|
|
131
|
+
|
|
132
|
+
```graphql
|
|
133
|
+
query HighFuelConsumption($assetId: ID!, $start: DateTime!, $end: DateTime!, $step: Duration!) {
|
|
134
|
+
asset(id: $assetId) {
|
|
135
|
+
timeSeries {
|
|
136
|
+
rangeQuery(
|
|
137
|
+
query: "increase(machine_insight_engine_total_fuel_used[1d]) > 13",
|
|
138
|
+
start: $start,
|
|
139
|
+
end: $end,
|
|
140
|
+
step: $step
|
|
141
|
+
) {
|
|
142
|
+
data {
|
|
143
|
+
... on TimeSeriesMatrixData {
|
|
144
|
+
result { values { timestamp value } }
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
### Combine Metrics
|
|
154
|
+
|
|
155
|
+
```graphql
|
|
156
|
+
query FuelWithHighIdle($assetId: ID!, $start: DateTime!, $end: DateTime!, $step: Duration!) {
|
|
157
|
+
asset(id: $assetId) {
|
|
158
|
+
timeSeries {
|
|
159
|
+
rangeQuery(
|
|
160
|
+
query: "(increase(machine_insight_engine_total_fuel_used[1d]) > 13) and (increase(machine_insight_cumulative_idle_hours[1d]) > 4)",
|
|
161
|
+
start: $start,
|
|
162
|
+
end: $end,
|
|
163
|
+
step: $step
|
|
164
|
+
) {
|
|
165
|
+
data {
|
|
166
|
+
... on TimeSeriesMatrixData {
|
|
167
|
+
result { values { timestamp value } }
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
## Key Points
|
|
177
|
+
|
|
178
|
+
- **Always use fragments** for both `TimeSeriesMatrixData` and `TimeSeriesVectorData`
|
|
179
|
+
- **Use PromQL functions**: `increase()`, `rate()`, `avg_over_time()`
|
|
180
|
+
- **Filter at query level** with operators: `>`, `<`, `>=`, `<=`, `==`, `!=`
|
|
181
|
+
- **Time windows** use brackets: `[1d]`, `[1h]`, `[15m]`
|
|
182
|
+
- **Step intervals**: Choose based on data granularity needed
|
|
183
|
+
- **Timestamps** are Unix timestamps in seconds
|
|
184
|
+
- **Values** are returned as strings - convert to numbers as needed
|
|
185
|
+
|
|
186
|
+
## Common Mistakes
|
|
187
|
+
|
|
188
|
+
| Mistake | Fix |
|
|
189
|
+
|---------|-----|
|
|
190
|
+
| Missing fragment for data type | Use `... on TimeSeriesMatrixData` or `TimeSeriesVectorData` |
|
|
191
|
+
| Not handling missing metrics | Not all assets have all insights; handle empty results |
|
|
192
|
+
| Using values as numbers directly | Values are strings; convert with `parseFloat()` |
|
|
193
|
+
| Wrong step interval | Match step to desired chart granularity |
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: irisx-app-sdk
|
|
3
|
+
description: Use when creating apps, adding extensions, configuring CSP/scopes, or needing SDK command reference. Core SDK documentation.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# IrisX App SDK Development
|
|
7
|
+
|
|
8
|
+
## Overview
|
|
9
|
+
|
|
10
|
+
IrisX Apps are React applications that integrate into Trackunit Manager at defined extension points. Use SDK commands (not generic React/Node.js) for all project operations.
|
|
11
|
+
|
|
12
|
+
## When to Use
|
|
13
|
+
|
|
14
|
+
- Creating new apps or extensions
|
|
15
|
+
- Configuring security (CSP headers, scopes)
|
|
16
|
+
- Looking up extension types or SDK commands
|
|
17
|
+
- Understanding runtime hooks availability
|
|
18
|
+
|
|
19
|
+
**Not for:** GraphQL setup (use `graphql` skill), browser testing (use `browser-testing` skill).
|
|
20
|
+
|
|
21
|
+
## Extension Points
|
|
22
|
+
|
|
23
|
+
Extensions are React applications that integrate into specific points in Trackunit Manager:
|
|
24
|
+
|
|
25
|
+
| Extension Type | Location |
|
|
26
|
+
|----------------|----------|
|
|
27
|
+
| `ASSET_HOME_EXTENSION` | New tabs in Assets Home screen |
|
|
28
|
+
| `SITE_HOME_EXTENSION` | New tabs in Site Home screen |
|
|
29
|
+
| `FLEET_EXTENSION` | Menu items in Main Menu |
|
|
30
|
+
| `REPORT_EXTENSION` | New reports in Reports screen |
|
|
31
|
+
| `WIDGET_EXTENSION` | Dashboard widgets |
|
|
32
|
+
| `IRIS_APP_SETTINGS_EXTENSION` | Configuration UI in App library |
|
|
33
|
+
| `ADMIN_EXTENSION` | Admin UI tabs (admin-only) |
|
|
34
|
+
| `CUSTOMER_HOME_EXTENSION` | UI within Customer Home |
|
|
35
|
+
| `ASSET_EVENTS_ACTIONS_EXTENSION` | UI in Asset Home Events |
|
|
36
|
+
| `LIFECYCLE_EXTENSION` | Handles app install/uninstall events |
|
|
37
|
+
| `SERVERLESS_FUNCTION_EXTENSION` | Serverless backend functions |
|
|
38
|
+
|
|
39
|
+
## Commands
|
|
40
|
+
|
|
41
|
+
### Create New App
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
npx nx generate @trackunit/iris-app:create [name-of-your-app]
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
See: [Creating a new app](https://developers.trackunit.com/docs/creating-a-new-app)
|
|
48
|
+
|
|
49
|
+
### Create New Extension
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
npx nx g @trackunit/iris-app:extend --name=[my-extension-name] --app=[app-name] --directory=[feature] --type=[extension-type]
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
See: [Creating a new extension](https://developers.trackunit.com/docs/creating-a-new-extension)
|
|
56
|
+
|
|
57
|
+
### Run App
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
npx nx run [name-of-your-app]:serve
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
See: [Running the Iris App SDK](https://developers.trackunit.com/docs/running-the-iris-app-sdk)
|
|
64
|
+
|
|
65
|
+
## Security Configuration
|
|
66
|
+
|
|
67
|
+
IrisX Apps are locked down by default. External API requests and embedded content require proper Content Security Policy (CSP) configuration in the app manifest.
|
|
68
|
+
|
|
69
|
+
### CSP Headers Example
|
|
70
|
+
|
|
71
|
+
```typescript
|
|
72
|
+
cspHeader: {
|
|
73
|
+
'frame-src': ['https://api.example.com', 'https://dashboard.example.com'],
|
|
74
|
+
'script-src': ['https://api.example.com'],
|
|
75
|
+
'connect-src': ['https://api.example.com'],
|
|
76
|
+
},
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Scopes Example
|
|
80
|
+
|
|
81
|
+
```typescript
|
|
82
|
+
scopes: [
|
|
83
|
+
{scope: "required.scope.1", optional: false},
|
|
84
|
+
{scope: "required.scope.2", optional: true},
|
|
85
|
+
],
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Apps requiring API access need scopes configured in the manifest, and the app must be deployed with those scopes to work locally. Never submit an app automatically - instruct the user to do so.
|
|
89
|
+
|
|
90
|
+
See: [Embedding IrisX Dashboards in Apps](https://developers.trackunit.com/docs/analytics-dashboards-in-apps#troubleshooting)
|
|
91
|
+
|
|
92
|
+
## Runtime Hooks
|
|
93
|
+
|
|
94
|
+
IrisX Apps have access to runtime hooks from `@trackunit/react-core-hooks`:
|
|
95
|
+
|
|
96
|
+
- `useAssetRuntime()` - Asset information in asset-scoped extensions
|
|
97
|
+
- `useCustomerRuntime()` - Customer data
|
|
98
|
+
- `useEventRuntime()` - Event details
|
|
99
|
+
- `useSiteRuntime()` - Site information
|
|
100
|
+
- Plus navigation, user context, and utility hooks
|
|
101
|
+
|
|
102
|
+
See the `react-core-hooks` skill for full details.
|
|
103
|
+
|
|
104
|
+
## Quality Assurance
|
|
105
|
+
|
|
106
|
+
Before completing any task: Always use `read_lints` to check for and fix all ESLint and TypeScript errors in modified files.
|
|
107
|
+
|
|
108
|
+
## Common Mistakes
|
|
109
|
+
|
|
110
|
+
| Mistake | Fix |
|
|
111
|
+
|---------|-----|
|
|
112
|
+
| Using `create-react-app` or `npm init` | Use `nx generate @trackunit/iris-app:create` |
|
|
113
|
+
| Mocking data without asking | Always use real GraphQL API |
|
|
114
|
+
| Missing scopes in manifest | Add required scopes; deploy to enable locally |
|
|
115
|
+
| External API blocked | Add domain to `cspHeader` in manifest |
|
|
116
|
+
| Auto-submitting app | Never auto-submit; instruct user to deploy |
|