github.com/hernad/nomad@v1.6.112/ui/app/serializers/application.js (about) 1 /** 2 * Copyright (c) HashiCorp, Inc. 3 * SPDX-License-Identifier: MPL-2.0 4 */ 5 6 import { copy } from 'ember-copy'; 7 import { get } from '@ember/object'; 8 import { makeArray } from '@ember/array'; 9 import JSONSerializer from '@ember-data/serializer/json'; 10 import { pluralize, singularize } from 'ember-inflector'; 11 import removeRecord from '../utils/remove-record'; 12 import { assign } from '@ember/polyfills'; 13 import classic from 'ember-classic-decorator'; 14 import { camelize, capitalize, dasherize } from '@ember/string'; 15 @classic 16 export default class Application extends JSONSerializer { 17 primaryKey = 'ID'; 18 19 /** 20 A list of keys that are converted to empty arrays if their value is null. 21 22 arrayNullOverrides = ['Array']; 23 { Array: null } => { Array: [] } 24 25 @property arrayNullOverrides 26 @type String[] 27 */ 28 arrayNullOverrides = null; 29 30 /** 31 A list of keys that are converted to empty objects if their value is null. 32 33 objectNullOverrides = ['Object']; 34 { Object: null } => { Object: {} } 35 36 @property objectNullOverrides 37 @type String[] 38 */ 39 objectNullOverrides = null; 40 41 /** 42 A list of keys or objects to convert a map into an array of maps with the original map keys as Name properties. 43 44 mapToArray = ['Map']; 45 { Map: { a: { x: 1 } } } => { Map: [ { Name: 'a', x: 1 }] } 46 47 mapToArray = [{ beforeName: 'M', afterName: 'Map' }]; 48 { M: { a: { x: 1 } } } => { Map: [ { Name: 'a', x: 1 }] } 49 50 @property mapToArray 51 @type (String|Object)[] 52 */ 53 mapToArray = null; 54 55 /** 56 A list of keys for nanosecond timestamps that will be split into two properties: `separateNanos = ['Time']` will 57 produce a `Time` property with a millisecond timestamp and `TimeNanos` with the nanoseconds alone. 58 59 separateNanos = ['Time']; 60 { Time: 1607839992000100000 } => { Time: 1607839992000, TimeNanos: 100096 } 61 62 @property separateNanos 63 @type String[] 64 */ 65 separateNanos = null; 66 67 keyForAttribute(attr) { 68 return capitalize(camelize(attr)); 69 } 70 71 keyForRelationship(attr, relationshipType) { 72 const key = `${capitalize(camelize(singularize(attr)))}ID`; 73 return relationshipType === 'hasMany' ? pluralize(key) : key; 74 } 75 76 // Modeled after the pushPayload for ember-data/serializers/rest 77 pushPayload(store, payload) { 78 const documentHash = { 79 data: [], 80 included: [], 81 }; 82 83 Object.keys(payload).forEach((key) => { 84 const modelName = this.modelNameFromPayloadKey(key); 85 const serializer = store.serializerFor(modelName); 86 const type = store.modelFor(modelName); 87 88 makeArray(payload[key]).forEach((hash) => { 89 const { data, included } = serializer.normalize(type, hash, key); 90 documentHash.data.push(data); 91 if (included) { 92 documentHash.included.push(...included); 93 } 94 }); 95 }); 96 97 store.push(documentHash); 98 } 99 100 normalize(modelClass, hash) { 101 if (hash) { 102 if (this.arrayNullOverrides) { 103 this.arrayNullOverrides.forEach((key) => { 104 if (!hash[key]) { 105 hash[key] = []; 106 } 107 }); 108 } 109 if (this.objectNullOverrides) { 110 this.objectNullOverrides.forEach((key) => { 111 if (!hash[key]) { 112 hash[key] = {}; 113 } 114 }); 115 } 116 117 if (this.mapToArray) { 118 this.mapToArray.forEach((conversion) => { 119 let apiKey, uiKey; 120 121 if (conversion.beforeName) { 122 apiKey = conversion.beforeName; 123 uiKey = conversion.afterName; 124 } else { 125 apiKey = conversion; 126 uiKey = conversion; 127 } 128 129 const map = hash[apiKey] || {}; 130 131 hash[uiKey] = Object.keys(map) 132 .sort() 133 .map((mapKey) => { 134 const propertiesForKey = map[mapKey] || {}; 135 const convertedMap = { Name: mapKey }; 136 137 assign(convertedMap, propertiesForKey); 138 139 return convertedMap; 140 }); 141 }); 142 } 143 144 if (this.separateNanos) { 145 this.separateNanos.forEach((key) => { 146 const timeWithNanos = hash[key]; 147 hash[`${key}Nanos`] = timeWithNanos % 1000000; 148 hash[key] = Math.floor(timeWithNanos / 1000000); 149 }); 150 } 151 } 152 153 return super.normalize(modelClass, hash); 154 } 155 156 normalizeFindAllResponse(store, modelClass) { 157 const result = super.normalizeFindAllResponse(...arguments); 158 this.cullStore(store, modelClass.modelName, result.data); 159 return result; 160 } 161 162 // When records are removed server-side, and therefore don't show up in requests, 163 // the local copies of those records need to be unloaded from the store. 164 cullStore(store, type, records, storeFilter = () => true) { 165 const newRecords = copy(records).filter((record) => get(record, 'id')); 166 const oldRecords = store.peekAll(type); 167 oldRecords 168 .filter((record) => get(record, 'id')) 169 .filter(storeFilter) 170 .forEach((old) => { 171 const newRecord = newRecords.find( 172 (record) => get(record, 'id') === get(old, 'id') 173 ); 174 if (!newRecord) { 175 removeRecord(store, old); 176 } else { 177 newRecords.removeObject(newRecord); 178 } 179 }); 180 } 181 182 modelNameFromPayloadKey(key) { 183 return singularize(dasherize(key)); 184 } 185 }