open-consul/ui/packages/consul-ui/app/serializers/application.js
John Cowen eeb04ae436
ui: Partitions Application Layer (#11017)
* Add Partition to all our models

* Add partitions into our serializers/fingerprinting

* Make some amends to a few adapters ready for partitions

* Amend blueprints to avoid linting error

* Update all  our repositories to include partitions, also

Remove enabled/disable nspace repo and just use a nspace with
conditionals

* Ensure nspace and parition parameters always return '' no matter what

* Ensure data-sink finds the model properly

This will later be replaced by a @dataSink decorator but we are find
kicking that can down the road a little more

* Add all the new partition data layer

* Add a way to set the title of the page from inside the route

and make it accessibile via a route announcer

* Make the Consul Route the default/basic one

* Tweak nspace and partition abilities not to check the length

* Thread partition through all the components that need it

* Some ACL tweaks

* Move the entire app to use partitions

* Delete all the tests we no longer need

* Update some Unit tests to use partition

* Fix up KV title tests

* Fix up a few more acceptance tests

* Fixup and temporarily ignore some acceptance tests

* Stop using ember-cli-page-objects fillable as it doesn't seem to work

* Fix lint error

* Remove old ACL related test

* Add a tick after filling out forms

* Fix token warning modal

* Found some more places where we need a partition var

* Fixup some more acceptance tests

* Tokens still needs a repo service for CRUD

* Remove acceptance tests we no longer need

* Fixup and "FIXME ignore" a few tests

* Remove an s

* Disable blocking queries for KV to revert to previous release for now

* Fixup adapter tests to follow async/function resolving interface

* Fixup all the serializer integration tests

* Fixup service/repo integration tests

* Fixup deleting acceptance test

* Fixup some ent tests

* Make sure nspaces passes the dc through for when thats important

* ...aaaand acceptance nspaces with the extra dc param
2021-09-15 19:50:11 +01:00

214 lines
6.7 KiB
JavaScript

import Serializer from './http';
import { set } from '@ember/object';
import {
HEADERS_SYMBOL as HTTP_HEADERS_SYMBOL,
HEADERS_INDEX as HTTP_HEADERS_INDEX,
HEADERS_DATACENTER as HTTP_HEADERS_DATACENTER,
HEADERS_NAMESPACE as HTTP_HEADERS_NAMESPACE,
HEADERS_PARTITION as HTTP_HEADERS_PARTITION,
} from 'consul-ui/utils/http/consul';
import { CACHE_CONTROL as HTTP_HEADERS_CACHE_CONTROL } from 'consul-ui/utils/http/headers';
import { FOREIGN_KEY as DATACENTER_KEY } from 'consul-ui/models/dc';
import { NSPACE_KEY } from 'consul-ui/models/nspace';
import { PARTITION_KEY } from 'consul-ui/models/partition';
import createFingerprinter from 'consul-ui/utils/create-fingerprinter';
const map = function(obj, cb) {
if (!Array.isArray(obj)) {
return [obj].map(cb)[0];
}
return obj.map(cb);
};
const attachHeaders = function(headers, body, query = {}) {
// lowercase everything incase we get browser inconsistencies
const lower = {};
Object.keys(headers).forEach(function(key) {
lower[key.toLowerCase()] = headers[key];
});
//
body[HTTP_HEADERS_SYMBOL] = lower;
return body;
};
export default class ApplicationSerializer extends Serializer {
attachHeaders = attachHeaders;
fingerprint = createFingerprinter(DATACENTER_KEY, NSPACE_KEY, PARTITION_KEY);
respondForQuery(respond, query) {
return respond((headers, body) =>
attachHeaders(
headers,
map(
body,
this.fingerprint(
this.primaryKey,
this.slugKey,
query.dc,
headers[HTTP_HEADERS_NAMESPACE],
headers[HTTP_HEADERS_PARTITION]
)
),
query
)
);
}
respondForQueryRecord(respond, query) {
return respond((headers, body) =>
attachHeaders(
headers,
this.fingerprint(
this.primaryKey,
this.slugKey,
query.dc,
headers[HTTP_HEADERS_NAMESPACE],
headers[HTTP_HEADERS_PARTITION]
)(body),
query
)
);
}
respondForCreateRecord(respond, serialized, data) {
const slugKey = this.slugKey;
const primaryKey = this.primaryKey;
return respond((headers, body) => {
// If creates are true use the info we already have
if (body === true) {
body = data;
}
// Creates need a primaryKey adding
return this.fingerprint(
primaryKey,
slugKey,
data[DATACENTER_KEY],
headers[HTTP_HEADERS_NAMESPACE],
data.Partition
)(body);
});
}
respondForUpdateRecord(respond, serialized, data) {
const slugKey = this.slugKey;
const primaryKey = this.primaryKey;
return respond((headers, body) => {
// If updates are true use the info we already have
// TODO: We may aswell avoid re-fingerprinting here if we are just going
// to reuse data then its already fingerprinted and as the response is
// true we don't have anything changed so the old fingerprint stays the
// same as long as nothing in the fingerprint has been edited (the
// namespace?)
if (body === true) {
body = data;
}
return this.fingerprint(
primaryKey,
slugKey,
data[DATACENTER_KEY],
headers[HTTP_HEADERS_NAMESPACE],
headers[HTTP_HEADERS_PARTITION]
)(body);
});
}
respondForDeleteRecord(respond, serialized, data) {
const slugKey = this.slugKey;
const primaryKey = this.primaryKey;
return respond((headers, body) => {
// Deletes only need the primaryKey/uid returning and they need the slug
// key AND potential namespace in order to create the correct
// uid/fingerprint
return {
[primaryKey]: this.fingerprint(
primaryKey,
slugKey,
data[DATACENTER_KEY],
headers[HTTP_HEADERS_NAMESPACE],
headers[HTTP_HEADERS_PARTITION]
)({
[slugKey]: data[slugKey],
[NSPACE_KEY]: data[NSPACE_KEY],
[PARTITION_KEY]: data[PARTITION_KEY],
})[primaryKey],
};
});
}
// this could get confusing if you tried to override say
// `normalizeQueryResponse`
// TODO: consider creating a method for each one of the
// `normalize...Response` family
normalizeResponse(store, modelClass, payload, id, requestType) {
const normalizedPayload = this.normalizePayload(payload, id, requestType);
// put the meta onto the response, here this is ok as JSON-API allows this
// and our specific data is now in response[primaryModelClass.modelName]
// so we aren't in danger of overwriting anything (which was the reason
// for the Symbol-like property earlier) use a method modelled on
// ember-data methods so we have the opportunity to do this on a per-model
// level
const meta = this.normalizeMeta(store, modelClass, normalizedPayload, id, requestType);
if (requestType !== 'query') {
normalizedPayload.meta = meta;
}
const res = super.normalizeResponse(
store,
modelClass,
{
meta: meta,
[modelClass.modelName]: normalizedPayload,
},
id,
requestType
);
// If the result of the super normalizeResponse is undefined its because
// the JSONSerializer (which REST inherits from) doesn't recognise the
// requestType, in this case its likely to be an 'action' request rather
// than a specific 'load me some data' one. Therefore its ok to bypass the
// store here for the moment we currently use this for self, but it also
// would affect any custom methods that use a serializer in our custom
// service/store
if (typeof res === 'undefined') {
return payload;
}
return res;
}
timestamp() {
return new Date().getTime();
}
normalizeMeta(store, modelClass, payload, id, requestType) {
// Pick the meta/headers back off the payload and cleanup
const headers = payload[HTTP_HEADERS_SYMBOL] || {};
delete payload[HTTP_HEADERS_SYMBOL];
const meta = {
cacheControl: headers[HTTP_HEADERS_CACHE_CONTROL.toLowerCase()],
cursor: headers[HTTP_HEADERS_INDEX.toLowerCase()],
dc: headers[HTTP_HEADERS_DATACENTER.toLowerCase()],
nspace: headers[HTTP_HEADERS_NAMESPACE.toLowerCase()],
partition: headers[HTTP_HEADERS_PARTITION.toLowerCase()],
};
if (typeof headers['x-range'] !== 'undefined') {
meta.range = headers['x-range'];
}
if (typeof headers['refresh'] !== 'undefined') {
meta.interval = headers['refresh'] * 1000;
}
if (requestType === 'query') {
meta.date = this.timestamp();
payload.forEach(function(item) {
set(item, 'SyncTime', meta.date);
});
}
return meta;
}
normalizePayload(payload, id, requestType) {
return payload;
}
}