Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
5f11c34
Support configured order lifecycles and order presentation profiles
shivthakker Sep 28, 2026
e900c46
Merge branch 'main' of github.com:fleetbase/fleetops into feature/con…
roncodes Sep 30, 2026
a4b001d
feat: resource view registries for fleet-ops tables and panels
roncodes Sep 30, 2026
0af64c2
chore(release): v0.6.71
roncodes Oct 3, 2026
3bcaced
fix(order-form): handle a failed custom fields load
roncodes Oct 3, 2026
77aa076
Merge pull request #347 from fleetbase/feature/resource-view-registries
roncodes Oct 3, 2026
adffe39
refactor: resource view registries are named <extension>:<resource>:<…
roncodes Oct 3, 2026
17d4007
Merge remote-tracking branch 'origin/release/v0.6.71' into feature/co…
roncodes Oct 3, 2026
0af9c73
Align order presentation profiles with resource view registry ids
roncodes Oct 3, 2026
1b18f33
test: cover configured lifecycle create, dispatch, strict transitions…
roncodes Oct 3, 2026
9ba5d0d
test: an accepted strict transition returns the order resource
roncodes Oct 3, 2026
8767f29
Merge pull request #346 from fleetbase/feature/configured-order-lifec…
roncodes Oct 5, 2026
296a704
Order config lifecycle management, full config list, live activity ti…
roncodes Oct 5, 2026
ccf5c35
Merge pull request #350 from fleetbase/fix/order-config-lifecycle-man…
roncodes Oct 5, 2026
d619db1
Activity flow: tolerate cyclic flows; Lifecycle tab: inline save button
roncodes Oct 5, 2026
1d64f0b
Lifecycle tab: define the activities of a configured lifecycle
roncodes Oct 5, 2026
708baae
Lifecycle tab: stack the dispatch and strict-transition toggles
roncodes Oct 5, 2026
3092880
Order details: load the assigned vehicle with the order
roncodes Oct 5, 2026
df7b95a
docs(release): registry names are <extension>:<resource>:<surface>:<s…
roncodes Oct 5, 2026
abe407e
chore(deps): @fleetbase/ember-core ^0.3.25 and @fleetbase/ember-ui ^0…
roncodes Oct 5, 2026
b2d0295
test: match ResourceLifecycleEvent::getEventData()'s $channel paramet…
roncodes Oct 5, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 21 additions & 47 deletions RELEASE.md
Original file line number Diff line number Diff line change
@@ -1,58 +1,32 @@
> v0.6.70 ~ "Telematics data retention"
> v0.6.71 ~ "Extensible tables and panels, configured order lifecycles and order presentation profiles"

---
## What's New
- **Telematics Data settings.** A new Fleet-Ops → Settings → Telematics Data page lets each organization decide how long telemetry is kept: device events, raw event payloads, positions, processed and quarantined delivery envelopes, and sync run diagnostics. Administrators set system-wide defaults from the admin console (Fleet-Ops Config → Telematics Data). A value of 0 keeps rows forever.
- **Scheduled retention sweep.** `fleetops:prune-telematics-data` runs every fifteen minutes and applies each organization's policy in bounded batches, so a large backlog is drained gradually without stalling ingestion. Defaults: events 30 days, raw payloads stripped after 7 days, positions 90 days, processed deliveries 24 hours, quarantined deliveries 7 days, sync runs 7 days.
- **Storage usage and on-demand cleanup.** The settings page shows rows, oldest row and estimated size per table for your organization, and "Run cleanup now" queues an immediate retention run.
## Highlights

---
## Fixes
- Device events no longer store the raw provider unit twice. `payload` keeps the raw unit; `meta` holds only the normalized block (speed, heading, odometer, ignition, fuel level, timing). New event rows are roughly half the size.
- Telemetry-driven saves (device, event, position, vehicle, sensors) no longer write to the activity log on every poll. Organizations that want them can enable "Log telemetry activity" in Telematics Data settings. Manual edits are logged as before.
- Delivery inbox and sync run retention moved from the drain command into the retention sweep; `fleetops:drain-telematic-inbox` now only recovers deliveries and finishes interrupted runs.

---
## Upgrade notes
- New indexes on `device_events (company_uuid, created_at)`, `positions (company_uuid, created_at)` and `telematic_sync_runs (telematic_uuid, updated_at)`; on very large tables run `php artisan migrate` in a maintenance window.
- Retention is enabled by default. Existing rows older than the defaults are removed over successive runs after upgrading; set the values to 0 before upgrading if you need to keep history indefinitely.
- Deleting rows frees space inside the database files, not on disk. Run `OPTIMIZE TABLE` off-peak to return space to the operating system.
- Run `php artisan fleetbase:create-permissions` to register the `telematics-settings` permission and the Telematics Settings Manager policy and role.
- **Resource view registries.** Extensions can add the following through `fleet-ops:<resource>:table:<slot>` and `fleet-ops:<resource>:details:<slot>`:
- columns, row actions, bulk actions and toolbar buttons on every top-level Fleet-Ops table;
- header buttons and "…" menu items on its details panels and side panels.

---
## Testing
- Backend tests cover the retention policy layering and clamping, the prune command (per-company policies, compaction, inbox tables via connections, orphans, batch caps, dry runs, filters, locking), the settings and storage usage endpoints, the index migrations, the on-demand job, and the meta and activity-log changes to ingestion.
- Ember tests cover the settings route guard, the settings controller, and the admin defaults component.
> v0.6.70 ~ "Order reporting and a cleaner default dashboard"

---
## What's New
- **Report on orders, their items and tracking.** The report builder can answer questions like "Which products sold the most this month?" and "What did orders total this month?".
- Orders expose their ID and Internal ID, and relationships for tracking (tracking number, region, latest status), Order Config, Customer Vendor, Created By, Purchase Rate › Service Quote and Payload Return.
- `Payload` exposes its items with name, SKU, price, dimensions, metadata and destination. Select an item column for one row per item, or group by one to summarise per product.
- Summary columns count orders distinctly, so they stay correct when item rows are selected. Distance, duration and transaction amount have totals and averages.
- `meta` has no fixed shape, so no column assumes a key in it. Read keys with a computed column, for example `CAST(JSON_UNQUOTE(JSON_EXTRACT(payload.entities.meta, '$.quantity')) AS DECIMAL(15,2))`.
- Column labels drop the table name ("Type", not "Order Type"). Distance and duration are labelled in meters and seconds, and money columns are marked "(minor units)".
- **A cleaner default dashboard.** Fleet-Ops widgets declare their place on the default dashboard: Radar first, then Active Orders and Drivers Online, a full-width Live Fleet map, and Revenue Trend, Top Drivers and Maintenance side by side.
- **Live Fleet widget cards match the live map.** Clicking a driver or vehicle on the widget map shows the same card as **Operations › Live Map**.
Built-in items carry stable ids, so registered items can be placed before or after them.
- **Configured order lifecycles.** An order config can define its own lifecycle in `meta.lifecycle`: `initial`, `completed`, `canceled` and `terminal` activities, `dispatch: false` and `strict_transitions`. The internal API starts orders at the initial activity, refuses to dispatch when dispatch is off, accepts only the flow's transitions when strict, and cancels to the configured activity. Configs without a lifecycle behave as before.
- **Lifecycle tab** in Order Configuration, to view and edit a config's lifecycle.
- **The board** shows a configured lifecycle's activities as its columns.
- **The activity flow editor** roots at the configured initial activity, and handles flows with cycles.
- **Order presentation profiles.** An extension can give its order type its own order form sections, extra and hidden fields, details view sections and hidden actions, registered in `fleet-ops:order-presentation`. **Edit details** opens the profiled form. Other order types are unchanged.
- **The Order Configuration manager lists every config**, including configs created while the console is open.
- **An order's activity timeline follows status changes made on the server.**
- **Fix: the Activity Flow crashed on a flow with a cycle.**
- **Fix: order details now load the assigned vehicle with the order.**
- **Fix: the order form failed when its custom fields couldn't load.**
- **Fix: registered details tabs.** Opening a driver, vehicle or trailer side panel removed the route from tabs other extensions had registered, which broke them in the full details view. Side panels also left out the tabs they could render.
- **Fix: the sensor details view showed the tabs registered for places.**

---
## Fixes
- Custom field values on fuel reports and service areas are saved. They were dropped because the save hooks read the snake_case payload root, while the console sends `fuelReport` and `serviceArea`.
- The report schema no longer references 13 columns and join keys that don't exist: driver name, email and phone and the driver's vehicle, the vehicle's driver, and the fuel report cost, odometer and date. Transaction summaries sum `transaction.amount`.
- Every report table, and the item join, leaves soft-deleted rows out on core-api v1.6.64 and later.

---
## Dependencies
- Order items, JSON totals and soft deletes in reports need `fleetbase/core-api` v1.6.64. The schema still registers on older versions.
- The report builder and the default dashboard order ship in `@fleetbase/ember-ui` v0.4.4.

---
## Testing
- Every declared report column and join key was checked against the database schema, and order reports were run against real storefront orders on MySQL in strict mode.
- Tests cover the report schema at 100% line coverage, the Live Fleet map cards and the default dashboard order.
## Upgrading
Needs fleetbase/ember-core v0.3.25 and fleetbase/ember-ui v0.4.5.

---
## Need help?
- [GitHub Discussions](https://github.com/fleetbase/fleetbase/discussions)
- [Discord](https://discord.gg/HnTqQ6zAVn)
---
6 changes: 5 additions & 1 deletion addon/components/order-config-manager.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import applyContextComponentArguments from '@fleetbase/ember-core/utils/apply-co
import OrderConfigManagerDetailsComponent from './order-config-manager/details';
import OrderConfigManagerCustomFieldsComponent from './order-config-manager/custom-fields';
import OrderConfigManagerActivityFlowComponent from './order-config-manager/activity-flow';
import OrderConfigManagerLifecycleComponent from './order-config-manager/lifecycle';
import OrderConfigManagerEntitiesComponent from './order-config-manager/entities';
import findActiveTab from '../utils/find-active-tab';

Expand Down Expand Up @@ -45,6 +46,7 @@ export default class OrderConfigManagerComponent extends Component {
this.universe._createMenuItem(this.intl.t('order-config-manager.tabs.details'), null, { icon: 'circle-info', component: OrderConfigManagerDetailsComponent }),
this.universe._createMenuItem(this.intl.t('order-config-manager.tabs.custom-fields'), null, { icon: 'rectangle-list', component: OrderConfigManagerCustomFieldsComponent }),
this.universe._createMenuItem(this.intl.t('order-config-manager.tabs.activity-flow'), null, { icon: 'diagram-project', component: OrderConfigManagerActivityFlowComponent }),
this.universe._createMenuItem(this.intl.t('order-config-manager.tabs.lifecycle'), null, { icon: 'arrows-spin', component: OrderConfigManagerLifecycleComponent }),
this.universe._createMenuItem(this.intl.t('order-config-manager.tabs.entities'), null, { icon: 'boxes-packing', component: OrderConfigManagerEntitiesComponent }),
];

Expand Down Expand Up @@ -106,7 +108,9 @@ export default class OrderConfigManagerComponent extends Component {
*/
@task *loadOrderConfigs(options = {}) {
try {
const configs = yield this.store.findAll('order-config').then(Array.from);
// Always fetch: `findAll` otherwise resolves with whatever subset of configs the store
// already holds (e.g. from an order's embedded config) and only refreshes in the background.
const configs = yield this.store.findAll('order-config', { reload: true }).then(Array.from);
let currentConfig;
if (isArray(configs) && configs.length > 0) {
currentConfig = configs.find((c) => c.id === this.args.orderConfig);
Expand Down
55 changes: 45 additions & 10 deletions addon/components/order-config-manager/activity-flow.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,17 @@ export default class OrderConfigManagerActivityFlowComponent extends Component {
* An array of activity codes that should not be modified.
* @type {Array.<string>}
*/
@tracked immutableActivities = ['created', 'dispatched', 'started'];
/**
* Activities that cannot be edited or removed. Configs with their own lifecycle
* (`meta.lifecycle`) are not bound to the default dispatch activities.
*/
get immutableActivities() {
return this.lifecycle ? [] : ['created', 'dispatched', 'started'];
}

get lifecycle() {
return this.config?.meta?.lifecycle ?? null;
}

/**
* The configuration data for the activity flow.
Expand Down Expand Up @@ -180,6 +190,12 @@ export default class OrderConfigManagerActivityFlowComponent extends Component {
this.clearGraph({
onAfter: () => {
this.flow = {};
// A configured lifecycle resets to its saved flow, never to the default dispatch flow.
if (this.lifecycle && this.config?.flow && Object.keys(this.config.flow).length) {
this.deserializeFlow(this.config.flow);
return;
}

const defaultActivities = this.getDefaultActivities();
this.addActivityToGraph(defaultActivities);
},
Expand Down Expand Up @@ -361,7 +377,7 @@ export default class OrderConfigManagerActivityFlowComponent extends Component {
}
});

const rootCode = getOrderConfigFlowRootCode(deserializedFlow);
const rootCode = getOrderConfigFlowRootCode(deserializedFlow, this.lifecycle?.initial);
if (rootCode && deserializedFlow[rootCode]) {
this.addDeserializedActivityToGraph(deserializedFlow[rootCode]);
}
Expand Down Expand Up @@ -439,6 +455,10 @@ export default class OrderConfigManagerActivityFlowComponent extends Component {
* and creates links from the parent to each child. It then calls itself to add any children of the current child activities.
* Finally, it repositions all child activities to maintain visual consistency.
*
* A flow is a graph, not a tree: an activity may be reachable from several parents, and a
* configured lifecycle can loop back (e.g. active -> extended -> active). An activity that is
* already on the graph only gets a link from this parent; it is never placed or walked twice.
*
* @param {Object} parentActivity - The parent activity to which child activities will be added.
* @param {Array} [childActivities=[]] - An array of child activity objects to be added. Defaults to an empty array.
*/
Expand All @@ -447,6 +467,11 @@ export default class OrderConfigManagerActivityFlowComponent extends Component {
return;
}
childActivities.forEach((childActivityObject) => {
if (childActivityObject.get('node')) {
this.addLinkToGraph(parentActivity, childActivityObject);
return;
}

const childPositionals = this.getActivityPositioning(childActivityObject, parentActivity);
const childActivity = this.createActivityNode(childActivityObject, childPositionals);
childActivity.set('parentId', parentActivity.get('id'));
Expand Down Expand Up @@ -823,16 +848,26 @@ export default class OrderConfigManagerActivityFlowComponent extends Component {
const addButton = this.createAddActivityButton(activity, { x: width });
let tools = [];

if (activity.get('code') === 'created' || activity.get('code') === 'dispatched') {
tools = [];
}
const code = activity.get('code');
const canUpdate = this.abilities.can('fleet-ops update order-config');

if (activity.get('code') === 'started' && this.abilities.can('fleet-ops update order-config')) {
tools = [addButton];
}
if (this.lifecycle) {
// The configured initial activity is the root: it can gain children but not be removed.
if (canUpdate) {
tools = code === this.lifecycle.initial ? [addButton] : [removeButton, addButton];
}
} else {
if (code === 'created' || code === 'dispatched') {
tools = [];
}

if (code === 'started' && canUpdate) {
tools = [addButton];
}

if (!this.immutableActivities.includes(activity.get('code')) && this.abilities.can('fleet-ops update order-config')) {
tools = [removeButton, addButton];
if (!this.immutableActivities.includes(code) && canUpdate) {
tools = [removeButton, addButton];
}
}

const activityNodeView = activityNode.findView(this.paper);
Expand Down
Loading
Loading