/** * Copyright (c) HashiCorp, Inc. * SPDX-License-Identifier: MPL-2.0 */ import { expandAttributeMeta } from 'vault/utils/field-to-attrs'; import Model from '@ember-data/model'; import { assert } from '@ember/debug'; /** * sets allByKey properties on model class. These are all the attributes on the model * and any belongsTo models, expanded with attribute metadata. The value returned is an * object where the key is the attribute name, and the value is the expanded attribute * metadata. * This decorator also exposes a helper function `_expandGroups` which, when given groups * as expected in field-to-attrs util, will return a similar object with the expanded * attributes in place of the strings in the array. */ export function withExpandedAttributes() { return function decorator(SuperClass) { if (!Object.prototype.isPrototypeOf.call(Model, SuperClass)) { // eslint-disable-next-line console.error( 'withExpandedAttributes decorator must be used on instance of ember-data Model class. Decorator not applied to returned class' ); return SuperClass; } return class ModelExpandedAttrs extends SuperClass { // Helper method for expanding dynamic groups on model _expandGroups(groups) { if (!Array.isArray(groups)) { throw new Error('_expandGroups expects an array of objects'); } /* Expects group shape to be something like: [ { default: ['ttl', 'maxTtl'] }, { "Method Options": ['other', 'fieldNames'] }, ]*/ return groups.map((obj) => { const [key, stringArray] = Object.entries(obj)[0]; const expanded = stringArray.map((fieldName) => this.allByKey[fieldName]).filter((f) => !!f); assert(`all fields found in allByKey for group ${key}`, expanded.length === stringArray.length); return { [key]: expanded }; }); } _allByKey = null; get allByKey() { // Caching like this ensures allByKey only gets calculated once if (!this._allByKey) { const byKey = {}; // First, get attr names which are on the model directly // By this time, OpenAPI should have populated non-explicit attrs const mainFields = []; this.eachAttribute(function (key) { mainFields.push(key); }); const expanded = expandAttributeMeta(this, mainFields); expanded.forEach((attr) => { // Add expanded attributes from the model byKey[attr.name] = attr; }); // Next, fetch and expand attrs for related models this.eachRelationship(function (name, descriptor) { // We don't worry about getting hasMany relationships if (descriptor.kind !== 'belongsTo') return; const rModel = this[name]; const rAttrNames = []; rModel.eachAttribute(function (key) { rAttrNames.push(key); }); const expanded = expandAttributeMeta(rModel, rAttrNames); expanded.forEach((attr) => { byKey[`${name}.${attr.name}`] = { ...attr, options: { ...attr.options, // This ensures the correct path is updated in FormField fieldValue: `${name}.${attr.fieldValue || attr.name}`, }, }; }); }, this); this._allByKey = byKey; } return this._allByKey; } }; }; }