Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
21 changes: 20 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,26 @@ ember install @fleetbase/fleetops-data

## Usage

[Longer description of how to use the addon in apps.]
Install the addon and the Fleet-Ops models, adapters, serializers and transforms are available to the host application and to every engine that lists it as a dependency.

### Shared resource descriptors

The addon also ships the resource identity descriptors for the resources every extension can see through its models: `driver`, `vehicle`, `customer`, `contact`, `place`, `order`, `vendor` and `fleet`, along with the thin `<Key::Pill>`, `<Key::Summary>`, `<Cell::KeyIdentity>` and `<SelectOption::Key>` wrappers that name the resource type for the generic components in `@fleetbase/ember-ui`.

An instance initializer registers the descriptors with the `resource-registry` service whenever the host or an engine boots, filling in only the keys nobody has registered yet. That means a Storefront or Ledger table can render a driver as an identity cell before the Fleet-Ops engine has loaded:

```js
{
id: 'driver-assigned',
cellComponent: 'table/cell/identity',
resourceType: 'driver',
resourcePath: 'driver_assigned',
}
```

Opening a resource loads the Fleet-Ops engine on demand and hands the record to its action service, so the engine bundle is only fetched when someone clicks. When Fleet-Ops itself boots it replaces these descriptors with its own, which open the same panels directly.

The helpers behind the descriptors and the styled placeholder images are exported from `@fleetbase/fleetops-data/utils/resource-descriptors` and `@fleetbase/fleetops-data/utils/placeholder-images` for other packages that build descriptors. `@fleetbase/fleetops-data/utils/identity-stub` builds a stand-in record for a relation a row only knows by name (`driver_name`, `pickupName`), so an identity cell can render it and load the real record on demand.


## Contributing
Expand Down
13 changes: 10 additions & 3 deletions RELEASE.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,19 @@
> v0.2.2 ~ "Managed login state"
> v0.2.3 ~ "Shared resource identity descriptors, and customer places that load"

---
## Highlights

- **Driver and contact models expose their login state.** New read-only `is_staff_linked` and `login_status` attributes let the console show Reset Password, Send Credentials and Deactivate/Reactivate Login. They also lock email and phone on a profile linked to a team member's account.
- **The login account is never sent back.** Driver and contact login accounts are now managed by the server from the profile's name, email and phone (fleetbase/fleetops#338). The driver serializer no longer sends `user_uuid` or the login fields. The contact serializer no longer sends `user`, `user_uuid` or the login fields.
- **Shared resource identity descriptors.** The identity descriptors for `driver`, `vehicle`, `customer`, `contact`, `place`, `order`, `vendor` and `fleet` now live in this package, so any extension that depends on it renders those records as identity cells, pills and summaries without loading the Fleet-Ops engine. `utils/resource-descriptors/shared` builds them, `utils/resource-descriptors/helpers` holds the building blocks, and `utils/placeholder-images` is the styled placeholder set. The thin `<Key::Pill>`, `<Key::Summary>`, `<Cell::KeyIdentity>` and `<SelectOption::Key>` wrappers for those eight keys ship as app-tree templates, so they resolve inside every consumer. An instance initializer registers the descriptors with ember-ui's resource registry on every host and engine boot, filling in only keys nobody has registered; Fleet-Ops replaces them with its own openers when it boots. ([#86](https://github.com/fleetbase/fleetops-data/pull/86))
- **Records open on their Fleet-Ops page outside Fleet-Ops.** Opening a resource loads the Fleet-Ops engine on demand. Inside its routes the record's context panel opens; from any other engine the record opens on its Fleet-Ops page, since the panel's components only Fleet-Ops can render. ([#88](https://github.com/fleetbase/fleetops-data/pull/88))
- **Identity stub builder.** `utils/identity-stub` (`buildIdentityStub`) moves here from Fleet-Ops, so an extension can build a stand-in record for a relation a row only knows by name. `typeLabel('fleet-ops:contact')` reads "Contact", and a host without an extension manager no longer throws from `canOpen`. ([#87](https://github.com/fleetbase/fleetops-data/pull/87))
- **An order's customer loads with its saved places.** Customer contacts and customer vendors now have their own serializers, so their embedded `place` and `places` load as `place` records. Before, a place with a type such as "apartment" or "house" was read as a model name, and the order failed to load with "No model was found for 'apartment'".

---
## Upgrading
The shared descriptors register with the `resource-registry` service from fleetbase/ember-ui v0.4.5 or later. fleetbase/fleetops v0.6.72 builds its own driver, vehicle, customer, contact, place, order, vendor and fleet descriptors from these and re-exports the moved helpers, so an older Fleet-Ops keeps working on its own copies.

---
## Need help?
- [GitHub Discussions](https://github.com/fleetbase/fleetbase/discussions)
- [Discord](https://discord.gg/HnTqQ6zAVn)
---
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { buildSharedResourceDescriptors } from '../utils/resource-descriptors/shared';

/**
* Registers the shared FleetOps resource descriptors with the resource
* registry the UI package provides, so a driver, customer or place renders
* as an identity cell, pill or summary in any extension without that
* extension loading the FleetOps engine first.
*
* It runs for the host application and again for every engine that depends
* on this package, so it only fills in descriptors nobody has registered:
* FleetOps registers richer versions of the same keys when it boots, and
* a later engine must not replace them. A host without the registry service
* simply keeps plain text.
*/
export function initialize(owner) {
let registry;

try {
registry = owner.lookup('service:resource-registry');
} catch {
registry = null;
}

if (!registry || typeof registry.registerDescriptors !== 'function') {
return;
}

const missing = buildSharedResourceDescriptors(owner).filter((descriptor) => !isRegistered(registry, descriptor.key));

if (missing.length) {
registry.registerDescriptors(missing);
}
}

function isRegistered(registry, key) {
try {
return Boolean(typeof registry.getDescriptor === 'function' ? registry.getDescriptor(key) : registry.descriptors?.some?.((descriptor) => descriptor.key === key));
} catch {
return false;
}
}

export default {
name: 'register-shared-resource-descriptors',
initialize,
};
8 changes: 8 additions & 0 deletions addon/serializers/customer-contact.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import CustomerSerializer from './customer';

/**
* A contact loaded as an order's customer. It shares the contact serializer so
* its embedded `place` and `places` resolve to the declared `place` model rather
* than being looked up by each place's own `type` attribute (apartment, house, office...).
*/
export default class CustomerContactSerializer extends CustomerSerializer {}
7 changes: 7 additions & 0 deletions addon/serializers/customer-vendor.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import CustomerSerializer from './customer';

/**
* A vendor loaded as an order's customer. Like customer contacts, its embedded
* `place` and `places` are normalized as the declared `place` model.
*/
export default class CustomerVendorSerializer extends CustomerSerializer {}
56 changes: 56 additions & 0 deletions addon/utils/identity-stub.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { get } from '@ember/object';

const inflight = new WeakMap();

/**
* A stand-in for a related record a row only knows by name: enough for an
* identity cell, pill or summary to render (`name`, `resourceType`) and a
* `loadResource()` they call when the real record is needed. The stub never
* carries a UUID in an identifier field, so nothing renders one as a plate
* or an id, and concurrent loads of the same row share one request.
*
* `load` receives the row and returns the record (or a promise of it); with
* no `load`, `loadResource()` resolves to null and the stub stays static.
*/
export function buildIdentityStub(row, { type, nameKey = `${type}_name`, name, load, extra = {} } = {}) {
const label = name ?? (row ? get(row, nameKey) : null);

if (!label) {
return null;
}

return {
...extra,
name: label,
display_name: label,
displayName: label,
resourceType: type,
isIdentityStub: true,
loadResource: async () => {
if (typeof load !== 'function') {
return null;
}

let loads = inflight.get(row);

if (!loads) {
loads = new Map();
inflight.set(row, loads);
}

if (!loads.has(type)) {
loads.set(
type,
Promise.resolve()
.then(() => load(row))
.catch(() => null)
.finally(() => loads.delete(type))
);
}

return loads.get(type);
},
};
}

export default buildIdentityStub;
Loading
Loading