@trackunit/iris-app 1.12.9 → 1.12.11
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 +19 -0
- package/package.json +2 -2
- package/src/executors/submit/executor.js.map +1 -0
- package/src/executors/unpublish/executor.js.map +1 -0
- package/src/executors/utils/authentication.js.map +1 -0
- package/src/executors/utils/irisAppServerSettings.js.map +1 -0
- package/src/executors/utils/src/index.js.map +1 -0
- 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 -0
- package/src/generators/create/generator.js.map +1 -0
- package/src/generators/extend/dependencies.js.map +1 -0
- package/src/generators/extend/generator.js.map +1 -0
- 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/generator.js.map +1 -0
- package/src/generators/preset/root-files/AGENTS.md +43 -0
- package/src/generators/preset/root-files/CLAUDE.md +17 -0
- package/src/index.js.map +1 -0
- package/src/utils/ast/astUtils.js.map +1 -0
- package/src/utils/fileUpdater.js.map +1 -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,191 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: create-app
|
|
3
|
+
description: Use when building a new IrisX App from scratch, user asks to create an app, or starting a new extension project.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Create IrisX App
|
|
7
|
+
|
|
8
|
+
## Overview
|
|
9
|
+
|
|
10
|
+
Create IrisX Apps using browser-driven iterative development. Real data first (GraphQL API), verify each feature in browser before proceeding.
|
|
11
|
+
|
|
12
|
+
## When to Use
|
|
13
|
+
|
|
14
|
+
- User asks to create a new IrisX App
|
|
15
|
+
- Starting a new extension project
|
|
16
|
+
- Building a complete app from requirements
|
|
17
|
+
|
|
18
|
+
**Not for:** Modifying existing extensions (use domain-specific skills).
|
|
19
|
+
|
|
20
|
+
## Core Principles
|
|
21
|
+
|
|
22
|
+
| Principle | Implementation |
|
|
23
|
+
|-----------|----------------|
|
|
24
|
+
| Real Data First | Use GraphQL API, never mock unless requested |
|
|
25
|
+
| Browser Testing | Verify each change at `manager.trackunit.com/goto/iris-app-dev` |
|
|
26
|
+
| Chrome DevTools MCP | Required for iFrame-based extension testing |
|
|
27
|
+
| One Feature at a Time | Complete and verify before moving on |
|
|
28
|
+
| Zero Tolerance for Errors | Fix all lint/TypeScript errors immediately |
|
|
29
|
+
|
|
30
|
+
## Chrome DevTools MCP Requirement
|
|
31
|
+
|
|
32
|
+
**IrisX Apps render inside iFrames.** Standard browser automation cannot interact with iFrame content.
|
|
33
|
+
|
|
34
|
+
Before starting browser testing:
|
|
35
|
+
1. Verify Chrome DevTools MCP is available (check for `user-chrome-devtools` server)
|
|
36
|
+
2. If not available, see `browser-testing` skill for setup instructions
|
|
37
|
+
3. Use Chrome DevTools tools (`take_screenshot`, `click`, `evaluate_script`, etc.) for all browser interactions
|
|
38
|
+
|
|
39
|
+
## Development Workflow
|
|
40
|
+
|
|
41
|
+
### Phase 1: Planning
|
|
42
|
+
|
|
43
|
+
**1. Analyze Requirements**
|
|
44
|
+
|
|
45
|
+
If not already described, ask: "What functionality do you want to build?"
|
|
46
|
+
|
|
47
|
+
**2. Create Implementation Plan**
|
|
48
|
+
|
|
49
|
+
Structure the plan as:
|
|
50
|
+
|
|
51
|
+
- **Phase 1: Project Setup**
|
|
52
|
+
- Create app with `@trackunit/iris-app:create`
|
|
53
|
+
- Create extensions with `@trackunit/iris-app:extend`
|
|
54
|
+
- Start dev server in background
|
|
55
|
+
|
|
56
|
+
- **Phase 2: Browser Verification (CRITICAL GATE)**
|
|
57
|
+
- Verify Chrome DevTools MCP is available (required for iFrame testing)
|
|
58
|
+
- Open browser to `https://new.manager.trackunit.com/goto/iris-app-dev`
|
|
59
|
+
- Wait for user login (2-4 minutes)
|
|
60
|
+
- Enable "Use Local Apps" toggle
|
|
61
|
+
- Navigate to extension location
|
|
62
|
+
- Use Chrome DevTools `take_screenshot` to verify extension renders
|
|
63
|
+
- **STOP - Must confirm extension working before Phase 3**
|
|
64
|
+
|
|
65
|
+
- **Phase 3: Feature Implementation (One at a Time)**
|
|
66
|
+
- For each feature:
|
|
67
|
+
- Prerequisites (GraphQL setup/scopes needed)
|
|
68
|
+
- Implementation
|
|
69
|
+
- Validation: Check lints, browser test
|
|
70
|
+
- Browser test: What to verify
|
|
71
|
+
|
|
72
|
+
- **Phase 4: Final Verification**
|
|
73
|
+
- Zero linter/TypeScript errors
|
|
74
|
+
- End-to-end browser testing
|
|
75
|
+
- Documentation completion
|
|
76
|
+
|
|
77
|
+
**3. Present Plan to User**
|
|
78
|
+
|
|
79
|
+
Confirm before proceeding.
|
|
80
|
+
|
|
81
|
+
### Phase 2: Project Setup
|
|
82
|
+
|
|
83
|
+
**4. Create App and Extensions**
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
npx nx generate @trackunit/iris-app:create [app-name]
|
|
87
|
+
npx nx g @trackunit/iris-app:extend --name=[extension-name] --app=[app-name] --directory=[feature] --type=[EXTENSION_TYPE]
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
**5. Start Development Server**
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
npx nx run [app-name]:serve
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
**6. Open Browser and Verify**
|
|
97
|
+
|
|
98
|
+
**CRITICAL: Do this BEFORE any GraphQL setup or UI implementation**
|
|
99
|
+
|
|
100
|
+
1. Navigate to `https://new.manager.trackunit.com/goto/iris-app-dev`
|
|
101
|
+
2. Wait for user login
|
|
102
|
+
3. Enable "Use Local Apps" toggle
|
|
103
|
+
4. Navigate to extension based on type
|
|
104
|
+
5. Take snapshot to verify extension loads
|
|
105
|
+
6. Check console for errors
|
|
106
|
+
|
|
107
|
+
**STOP HERE: Do not proceed until extension is confirmed working**
|
|
108
|
+
|
|
109
|
+
### Phase 3: Iterative Feature Development
|
|
110
|
+
|
|
111
|
+
**7. Create Documentation**
|
|
112
|
+
|
|
113
|
+
Create `/docs/[app-name].md` with the plan and architecture.
|
|
114
|
+
|
|
115
|
+
**8. For Each Feature (ONE AT A TIME)**
|
|
116
|
+
|
|
117
|
+
**a) Setup prerequisites for this feature ONLY:**
|
|
118
|
+
```bash
|
|
119
|
+
# If feature needs GraphQL:
|
|
120
|
+
npm install @trackunit/react-graphql-tools
|
|
121
|
+
npx nx generate @trackunit/react-graphql-tools:add-graphql --project=[project-name]
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
**b) Implement the feature:**
|
|
125
|
+
- Write code changes for ONE feature only
|
|
126
|
+
- Keep changes small and focused
|
|
127
|
+
|
|
128
|
+
**c) Check for lint and TypeScript errors:**
|
|
129
|
+
- Run `read_lints` on modified files
|
|
130
|
+
- Fix ALL errors immediately
|
|
131
|
+
- Zero tolerance: Do not proceed with any errors
|
|
132
|
+
|
|
133
|
+
**d) Reload browser to test:**
|
|
134
|
+
- Check browser console and network tab for errors
|
|
135
|
+
- Verify the feature renders correctly
|
|
136
|
+
- Take screenshot if visual changes
|
|
137
|
+
|
|
138
|
+
**e) Debug if needed:**
|
|
139
|
+
- Read console errors
|
|
140
|
+
- Check network requests
|
|
141
|
+
- Adjust code and repeat
|
|
142
|
+
- **DO NOT** move forward until feature works
|
|
143
|
+
|
|
144
|
+
**f) Update documentation:**
|
|
145
|
+
- Mark feature as completed
|
|
146
|
+
|
|
147
|
+
**g) Move to next feature:**
|
|
148
|
+
- Only proceed when current feature is verified working
|
|
149
|
+
|
|
150
|
+
### Phase 4: Completion
|
|
151
|
+
|
|
152
|
+
**9. Final Verification**
|
|
153
|
+
|
|
154
|
+
- Run `read_lints` on all modified files
|
|
155
|
+
- Test all features end-to-end in browser
|
|
156
|
+
- Verify data loads correctly
|
|
157
|
+
|
|
158
|
+
**10. Summary**
|
|
159
|
+
|
|
160
|
+
Inform the user:
|
|
161
|
+
- All features implemented and verified
|
|
162
|
+
- No linter or TypeScript errors remaining
|
|
163
|
+
- Documentation complete
|
|
164
|
+
- **Important**: User must deploy via Trackunit marketplace (never auto-submit)
|
|
165
|
+
|
|
166
|
+
## Critical Rules
|
|
167
|
+
|
|
168
|
+
### Feature Implementation
|
|
169
|
+
- **One feature at a time** - even for similar components
|
|
170
|
+
- **Prerequisites on-demand** - setup GraphQL/scopes only when needed
|
|
171
|
+
- **Zero tolerance for errors** - fix all lint/TypeScript errors immediately
|
|
172
|
+
- **Test completely** - verify working in browser before moving on
|
|
173
|
+
|
|
174
|
+
### Validation Checklist (After Every Code Change)
|
|
175
|
+
1. Run `read_lints` on modified files
|
|
176
|
+
2. Fix ALL linter and TypeScript errors
|
|
177
|
+
3. Reload browser
|
|
178
|
+
4. Check browser console and network tab
|
|
179
|
+
5. Verify UI renders correctly
|
|
180
|
+
6. Fix any issues before proceeding
|
|
181
|
+
|
|
182
|
+
## Common Mistakes
|
|
183
|
+
|
|
184
|
+
| Mistake | Fix |
|
|
185
|
+
|---------|-----|
|
|
186
|
+
| Using default browser automation | Use Chrome DevTools MCP (iFrames require it) |
|
|
187
|
+
| Starting implementation before browser verification | Complete Phase 2 (browser gate) first |
|
|
188
|
+
| Implementing multiple features at once | One feature, fully verified, then next |
|
|
189
|
+
| Using mock data without asking | Always use real GraphQL API |
|
|
190
|
+
| Skipping lint check | Zero tolerance - run `read_lints` after every change |
|
|
191
|
+
| Auto-submitting to marketplace | Never auto-submit; instruct user to do so |
|
|
@@ -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` |
|