ui: Discovery Chain Improvements (#7036)

* Reorganize resolvers so its clearer what's happening

* Use entire route definition for id

* Clean up a tiny bit more, use guid for ids instead of JSON

* ui: Externalize disco-chain utils and add initial unit testing

* Add some click outside-ness for de-highlighting things
This commit is contained in:
John Cowen 2020-01-16 16:31:09 +00:00 committed by GitHub
parent 13eb536e24
commit e15d1466aa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 376 additions and 191 deletions

View File

@ -3,93 +3,12 @@ import { inject as service } from '@ember/service';
import { set, get, computed } from '@ember/object';
import { next } from '@ember/runloop';
const getNodesByType = function(nodes = {}, type) {
return Object.values(nodes).filter(item => item.Type === type);
};
const targetsToFailover = function(targets, a) {
let type;
const Targets = targets.map(function(b) {
// TODO: this isn't going to work past namespace for services
// with dots in the name
const [aRev, bRev] = [a, b].map(item => item.split('.').reverse());
const types = ['Datacenter', 'Namespace', 'Service', 'Subset'];
return bRev.find(function(item, i) {
const res = item !== aRev[i];
if (res) {
type = types[i];
}
return res;
});
});
return {
Type: type,
Targets: Targets,
};
};
const getNodeResolvers = function(nodes = {}) {
const failovers = getFailovers(nodes);
const resolvers = {};
Object.keys(nodes).forEach(function(key) {
const node = nodes[key];
if (node.Type === 'resolver' && !failovers.includes(key.split(':').pop())) {
resolvers[node.Name] = node;
}
});
return resolvers;
};
const getTargetResolvers = function(dc, nspace = 'default', targets = [], nodes = {}) {
const resolvers = {};
Object.values(targets).forEach(item => {
let node = nodes[item.ID];
if (node) {
if (typeof resolvers[item.Service] === 'undefined') {
resolvers[item.Service] = {
ID: item.ID,
Name: item.Service,
Children: [],
Failover: null,
Redirect: null,
};
}
const resolver = resolvers[item.Service];
let failoverable = resolver;
if (item.ServiceSubset) {
failoverable = item;
// TODO: Sometimes we have set the resolvers ID to the ID of the
// subset this just shifts the subset of the front of the URL for the moment
const temp = item.ID.split('.');
temp.shift();
resolver.ID = temp.join('.');
resolver.Children.push(item);
}
if (typeof node.Resolver.Failover !== 'undefined') {
// TODO: Figure out if we can get rid of this
/* eslint ember/no-side-effects: "warn" */
set(failoverable, 'Failover', targetsToFailover(node.Resolver.Failover.Targets, item.ID));
} else {
const res = targetsToFailover([node.Resolver.Target], `service.${nspace}.${dc}`);
if (res.Type === 'Datacenter' || res.Type === 'Namespace') {
resolver.Children.push(item);
set(failoverable, 'Redirect', true);
}
}
}
});
return Object.values(resolvers);
};
const getFailovers = function(nodes = {}) {
const failovers = [];
Object.values(nodes)
.filter(item => item.Type === 'resolver')
.forEach(function(item) {
(get(item, 'Resolver.Failover.Targets') || []).forEach(failover => {
failovers.push(failover);
});
});
return failovers;
};
import {
createRoute,
getSplitters,
getRoutes,
getResolvers,
} from 'consul-ui/utils/components/discovery-chain/index';
export default Component.extend({
dom: service('dom'),
@ -134,74 +53,33 @@ export default Component.extend({
this.ticker.destroy(this);
},
splitters: computed('chain.Nodes', function() {
return getNodesByType(get(this, 'chain.Nodes'), 'splitter').map(function(item) {
set(item, 'ID', `splitter:${item.Name}`);
return item;
});
return getSplitters(get(this, 'chain.Nodes'));
}),
routers: computed('chain.Nodes', function() {
// Right now there should only ever be one 'Router'.
return getNodesByType(get(this, 'chain.Nodes'), 'router');
routes: computed('chain.Nodes', function() {
return getRoutes(get(this, 'chain.Nodes'), this.dom.guid);
}),
routes: computed('chain', 'routers', function() {
const routes = get(this, 'routers').reduce(function(prev, item) {
return prev.concat(
item.Routes.map(function(route, i) {
return {
...route,
ID: `route:${item.Name}-${JSON.stringify(route.Definition.Match.HTTP)}`,
};
})
);
}, []);
if (routes.length === 0) {
let nextNode = `resolver:${this.chain.ServiceName}.${this.chain.Namespace}.${this.chain.Datacenter}`;
const splitterID = `splitter:${this.chain.ServiceName}`;
if (typeof this.chain.Nodes[splitterID] !== 'undefined') {
nextNode = splitterID;
}
routes.push({
Default: true,
ID: `route:${this.chain.ServiceName}`,
Name: this.chain.ServiceName,
Definition: {
Match: {
HTTP: {
PathPrefix: '/',
},
},
},
NextNode: nextNode,
});
}
return routes;
}),
nodeResolvers: computed('chain.Nodes', function() {
return getNodeResolvers(get(this, 'chain.Nodes'));
}),
resolvers: computed('nodeResolvers.[]', 'chain.Targets', function() {
return getTargetResolvers(
resolvers: computed('chain.{Nodes,Targets}', function() {
return getResolvers(
this.chain.Datacenter,
this.chain.Namespace,
get(this, 'chain.Targets'),
this.nodeResolvers
get(this, 'chain.Nodes')
);
}),
graph: computed('chain.Nodes', function() {
const graph = this.dataStructs.graph();
Object.entries(get(this, 'chain.Nodes')).forEach(function([key, item]) {
const router = this.chain.ServiceName;
Object.entries(get(this, 'chain.Nodes')).forEach(([key, item]) => {
switch (item.Type) {
case 'splitter':
item.Splits.forEach(function(splitter) {
item.Splits.forEach(splitter => {
graph.addLink(`splitter:${item.Name}`, splitter.NextNode);
});
break;
case 'router':
item.Routes.forEach(function(route, i) {
graph.addLink(
`route:${item.Name}-${JSON.stringify(route.Definition.Match.HTTP)}`,
route.NextNode
);
item.Routes.forEach((route, i) => {
route = createRoute(route, router, this.dom.guid);
graph.addLink(route.ID, route.NextNode);
});
break;
}
@ -212,18 +90,15 @@ export default Component.extend({
if (this.selectedId === '' || !this.dom.element(`#${this.selectedId}`)) {
return {};
}
const getTypeFromId = function(id) {
return id.split(':').shift();
};
const id = this.selectedId;
const type = getTypeFromId(id);
const type = id.split(':').shift();
const nodes = [id];
const edges = [];
this.graph.forEachLinkedNode(id, (linkedNode, link) => {
nodes.push(linkedNode.id);
edges.push(`${link.fromId}>${link.toId}`);
this.graph.forEachLinkedNode(linkedNode.id, (linkedNode, link) => {
const nodeType = getTypeFromId(linkedNode.id);
const nodeType = linkedNode.id.split(':').shift();
if (type !== nodeType && type !== 'splitter' && nodeType !== 'splitter') {
nodes.push(linkedNode.id);
edges.push(`${link.fromId}>${link.toId}`);
@ -248,6 +123,16 @@ export default Component.extend({
// TODO: Figure out if we can remove this next
next(() => {
this._listeners.remove();
this._listeners.add(this.dom.document(), {
click: e => {
// all route/splitter/resolver components currently
// have classes that end in '-card'
if (!this.dom.closest('[class$="-card"]', e.target)) {
set(this, 'active', false);
set(this, 'selectedId', '');
}
},
});
[...this.dom.elements('path.split', this.element)].forEach(item => {
this._listeners.add(item, {
mouseover: e => this.actions.showSplit.apply(this, [e]),

View File

@ -15,17 +15,6 @@
</ol>
</dd>
</dl>
{{else if item.Redirect}}
<dl class="redirect">
<dt data-tooltip="Redirect">Redirect</dt>
<dd>
<ol>
<li>
<span>{{item.ID}}</span>
</li>
</ol>
</dd>
</dl>
{{/if}}
</a>
</header>
@ -47,15 +36,15 @@
</ol>
</dd>
</dl>
{{else if item.Redirect}}
{{else if child.Redirect}}
<dl class="redirect">
<dt data-tooltip="Redirect">Redirect</dt>
<dd>
{{child.ID}}
{{child.Name}}
</dd>
</dl>
{{else}}
{{child.ServiceSubset}}
{{child.Name}}
{{/if}}
</a>
</li>

View File

@ -9,7 +9,7 @@
{{/if}}
<dl>
<dt>
{{path.type}}
{{path.type}}
</dt>
<dd>
{{#if (not-eq path.type 'Default')}}

View File

@ -0,0 +1,105 @@
const getNodesByType = function(nodes = {}, type) {
return Object.values(nodes).filter(item => item.Type === type);
};
const findResolver = function(resolvers, service, nspace = 'default', dc) {
if (typeof resolvers[service] === 'undefined') {
resolvers[service] = {
ID: `${service}.${nspace}.${dc}`,
Name: service,
Children: [],
};
}
return resolvers[service];
};
export const getAlternateServices = function(targets, a) {
let type;
const Targets = targets.map(function(b) {
// TODO: this isn't going to work past namespace for services
// with dots in the name, but by the time that becomes an issue
// we might have more data from the endpoint so we don't have to guess
// right now the backend also doesn't support dots in service names
const [aRev, bRev] = [a, b].map(item => item.split('.').reverse());
const types = ['Datacenter', 'Namespace', 'Service', 'Subset'];
return bRev.find(function(item, i) {
const res = item !== aRev[i];
if (res) {
type = types[i];
}
return res;
});
});
return {
Type: type,
Targets: Targets,
};
};
export const getSplitters = function(nodes) {
return getNodesByType(nodes, 'splitter').map(function(item) {
// Splitters need IDs adding so we can find them in the DOM later
item.ID = `splitter:${item.Name}`;
return item;
});
};
export const getRoutes = function(nodes, uid) {
return getNodesByType(nodes, 'router').reduce(function(prev, item) {
return prev.concat(
item.Routes.map(function(route, i) {
// Routes also have IDs added via createRoute
return createRoute(route, item.Name, uid);
})
);
}, []);
};
export const getResolvers = function(dc, nspace = 'default', targets = {}, nodes = {}) {
const resolvers = {};
Object.values(targets).forEach(target => {
const node = nodes[`resolver:${target.ID}`];
const resolver = findResolver(resolvers, target.Service, nspace, dc);
// We use this to figure out whether this target is a redirect target
const alternate = getAlternateServices([target.ID], `service.${nspace}.${dc}`);
let failovers;
// Figure out the failover type
if (typeof node.Resolver.Failover !== 'undefined') {
failovers = getAlternateServices(node.Resolver.Failover.Targets, target.ID);
}
switch (true) {
// This target is a redirect
case alternate.Type !== 'Service':
resolver.Children.push({
Redirect: true,
ID: target.ID,
Name: target[alternate.Type],
});
break;
// This target is a Subset
case typeof target.ServiceSubset !== 'undefined':
resolver.Children.push({
Subset: true,
ID: target.ID,
Name: target.ServiceSubset,
Filter: target.Subset.Filter,
...(typeof failovers !== 'undefined'
? {
Failover: failovers,
}
: {}),
});
break;
// This target is just normal service that might have failovers
default:
if (typeof failovers !== 'undefined') {
resolver.Failover = failovers;
}
}
});
return Object.values(resolvers);
};
export const createRoute = function(route, router, uid) {
return {
...route,
Default: typeof route.Definition.Match === 'undefined',
ID: `route:${router}-${uid(route.Definition)}`,
};
};

View File

@ -0,0 +1,53 @@
import { getAlternateServices } from 'consul-ui/utils/components/discovery-chain/index';
import { module, test } from 'qunit';
module('Unit | Utility | components/discovery-chain/get-alternative-services', function() {
test('it guesses a different namespace', function(assert) {
const expected = {
Type: 'Namespace',
Targets: ['different-ns', 'different-ns2'],
};
const actual = getAlternateServices(
['service.different-ns.dc', 'service.different-ns2.dc'],
'service.namespace.dc'
);
assert.equal(actual.Type, expected.Type);
assert.deepEqual(actual.Targets, expected.Targets);
});
test('it guesses a different datacenter', function(assert) {
const expected = {
Type: 'Datacenter',
Targets: ['dc1', 'dc2'],
};
const actual = getAlternateServices(
['service.namespace.dc1', 'service.namespace.dc2'],
'service.namespace.dc'
);
assert.equal(actual.Type, expected.Type);
assert.deepEqual(actual.Targets, expected.Targets);
});
test('it guesses a different service', function(assert) {
const expected = {
Type: 'Service',
Targets: ['service-2', 'service-3'],
};
const actual = getAlternateServices(
['service-2.namespace.dc', 'service-3.namespace.dc'],
'service.namespace.dc'
);
assert.equal(actual.Type, expected.Type);
assert.deepEqual(actual.Targets, expected.Targets);
});
test('it guesses a different subset', function(assert) {
const expected = {
Type: 'Subset',
Targets: ['v3', 'v2'],
};
const actual = getAlternateServices(
['v3.service.namespace.dc', 'v2.service.namespace.dc'],
'v1.service.namespace.dc'
);
assert.equal(actual.Type, expected.Type);
assert.deepEqual(actual.Targets, expected.Targets);
});
});

View File

@ -0,0 +1,95 @@
import { getResolvers } from 'consul-ui/utils/components/discovery-chain/index';
import { module, test } from 'qunit';
import { get } from 'consul-ui/tests/helpers/api';
const dc = 'dc-1';
const nspace = 'default';
const request = {
url: `/v1/discovery-chain/service-name?dc=${dc}`,
};
module('Unit | Utility | components/discovery-chain/get-resolvers', function() {
test('it assigns Subsets correctly', function(assert) {
return get(request.url, {
headers: {
cookie: {
CONSUL_RESOLVER_COUNT: 1,
CONSUL_SUBSET_COUNT: 1,
CONSUL_REDIRECT_COUNT: 0,
CONSUL_FAILOVER_COUNT: 0,
},
},
}).then(function({ Chain }) {
const actual = getResolvers(dc, nspace, Chain.Targets, Chain.Nodes);
const childId = Object.keys(Chain.Targets)[1];
const target = Chain.Targets[`${childId}`];
const firstChild = actual[0].Children[0];
assert.equal(firstChild.Subset, true);
assert.equal(firstChild.ID, target.ID);
assert.equal(firstChild.Name, target.ServiceSubset);
});
});
test('it assigns Redirects correctly', function(assert) {
return get(request.url, {
headers: {
cookie: {
CONSUL_RESOLVER_COUNT: 1,
CONSUL_REDIRECT_COUNT: 1,
CONSUL_FAILOVER_COUNT: 0,
CONSUL_SUBSET_COUNT: 0,
},
},
}).then(function({ Chain }) {
const actual = getResolvers(dc, nspace, Chain.Targets, Chain.Nodes);
const childId = Object.keys(Chain.Targets)[1];
const target = Chain.Targets[`${childId}`];
const firstChild = actual[0].Children[0];
assert.equal(firstChild.Redirect, true);
assert.equal(firstChild.ID, target.ID);
});
});
test('it assigns Failovers to Subsets correctly', function(assert) {
return Promise.all(
['Datacenter', 'Namespace'].map(function(failoverType) {
return get(request.url, {
headers: {
cookie: {
CONSUL_RESOLVER_COUNT: 1,
CONSUL_REDIRECT_COUNT: 0,
CONSUL_SUBSET_COUNT: 1,
CONSUL_FAILOVER_COUNT: 1,
CONSUL_FAILOVER_TYPE: failoverType,
},
},
}).then(function({ Chain }) {
const actual = getResolvers(dc, nspace, Chain.Targets, Chain.Nodes);
const actualSubset = actual[0].Children[0];
assert.equal(actualSubset.Subset, true);
assert.equal(actualSubset.Failover.Type, failoverType);
});
})
);
});
test('it assigns Failovers correctly', function(assert) {
return Promise.all(
['Datacenter', 'Namespace'].map(function(failoverType, i) {
return get(request.url, {
headers: {
cookie: {
CONSUL_RESOLVER_COUNT: 1,
CONSUL_REDIRECT_COUNT: 0,
CONSUL_SUBSET_COUNT: 0,
CONSUL_FAILOVER_COUNT: 1,
CONSUL_FAILOVER_TYPE: failoverType,
},
},
}).then(function({ Chain }) {
const actual = getResolvers(dc, nspace, Chain.Targets, Chain.Nodes);
const node = Chain.Nodes[`resolver:${Object.keys(Chain.Targets)[0]}`];
const expected = node.Resolver.Failover.Targets.map(item => item.split('.').reverse()[i]);
assert.equal(actual[0].Failover.Type, failoverType);
assert.deepEqual(actual[0].Failover.Targets, expected);
});
})
);
});
});

View File

@ -979,9 +979,9 @@
"@glimmer/util" "^0.42.0"
"@hashicorp/api-double@^1.3.0":
version "1.5.1"
resolved "https://registry.yarnpkg.com/@hashicorp/api-double/-/api-double-1.5.1.tgz#0c0d62f00622d87b4f9a99fcaa8f20cbb35d1796"
integrity sha512-24dsxjbjb5TtVlTdRiG9jFfRLIYqx8NkiyD2zjInzh/022n0dDMk5b1zFWTIcJulO4079Q2z08Cx3B1kDCe9mg==
version "1.6.0"
resolved "https://registry.yarnpkg.com/@hashicorp/api-double/-/api-double-1.6.0.tgz#23c48d1982b81b6c9164067354d7653320ba761f"
integrity sha512-U11NttTVvJUVOFH4bgS8eZ+0s6j4/4DYPz9xkJM2ciZBnG353l2G7LYeivt55QajnCl3ImevEO4vSlvvFf4I4Q==
dependencies:
array-range "^1.0.1"
backtick-template "^0.2.0"
@ -992,9 +992,9 @@
js-yaml "^3.13.1"
"@hashicorp/consul-api-double@^2.6.2":
version "2.9.0"
resolved "https://registry.yarnpkg.com/@hashicorp/consul-api-double/-/consul-api-double-2.9.0.tgz#4489920942b2a7cc8276f15db6a38e414b34dd2f"
integrity sha512-a2GKU2pwLjmaC7OeCbEjHUjE9uYThe79OgxoHbT/8UrTO3yYssUDrlZke5hY8MObz03L+Qq6THRzonEYw4KpiQ==
version "2.11.0"
resolved "https://registry.yarnpkg.com/@hashicorp/consul-api-double/-/consul-api-double-2.11.0.tgz#0b833893ccc5cfb9546b1513127d5e92d30f2262"
integrity sha512-2MO1jiwuJyPlSGQ4AeFtLKJWmLSj0msoiaRHPtj6YPjm69ZkY/t4U4SU3cfpVn2Dx7wHzXe//9GvNHI1gRxAzg==
"@hashicorp/ember-cli-api-double@^2.0.0":
version "2.0.0"
@ -1634,9 +1634,9 @@ astral-regex@^1.0.0:
integrity sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==
async-disk-cache@^1.2.1:
version "1.3.4"
resolved "https://registry.yarnpkg.com/async-disk-cache/-/async-disk-cache-1.3.4.tgz#a5c9f72f199a9933583659f57a0e11377884f816"
integrity sha512-qsIvGJ/XYZ5bSGf5vHt2aEQHZnyuehmk/+51rCJhpkZl4LtvOZ+STbhLbdFAJGYO+dLzUT5Bb4nLKqHBX83vhw==
version "1.3.5"
resolved "https://registry.yarnpkg.com/async-disk-cache/-/async-disk-cache-1.3.5.tgz#cc6206ed79bb6982b878fc52e0505e4f52b62a02"
integrity sha512-VZpqfR0R7CEOJZ/0FOTgWq70lCrZyS1rkI8PXugDUkTKyyAUgZ2zQ09gLhMkEn+wN8LYeUTPxZdXtlX/kmbXKQ==
dependencies:
debug "^2.1.3"
heimdalljs "^0.2.3"
@ -2419,9 +2419,9 @@ binary-extensions@^1.0.0:
integrity sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==
"binaryextensions@1 || 2":
version "2.1.2"
resolved "https://registry.yarnpkg.com/binaryextensions/-/binaryextensions-2.1.2.tgz#c83c3d74233ba7674e4f313cb2a2b70f54e94b7c"
integrity sha512-xVNN69YGDghOqCCtA6FI7avYrr02mTJjOgB0/f1VPD3pJC8QEvjTKWc4epDx8AqxxA75NI0QpVM2gPJXUbE4Tg==
version "2.2.0"
resolved "https://registry.yarnpkg.com/binaryextensions/-/binaryextensions-2.2.0.tgz#e7c6ba82d4f5f5758c26078fe8eea28881233311"
integrity sha512-bHhs98rj/7i/RZpCSJ3uk55pLXOItjIrh2sRQZSM6OoktScX+LxJzvlU+FELp9j3TdcddTmmYArLSGptCTwjuw==
blank-object@^1.0.1:
version "1.0.2"
@ -3339,11 +3339,16 @@ can-symlink@^1.0.0:
dependencies:
tmp "0.0.28"
caniuse-lite@^1.0.30000792, caniuse-lite@^1.0.30000805, caniuse-lite@^1.0.30000844, caniuse-lite@^1.0.30000989:
caniuse-lite@^1.0.30000792, caniuse-lite@^1.0.30000805, caniuse-lite@^1.0.30000989:
version "1.0.30000989"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000989.tgz#b9193e293ccf7e4426c5245134b8f2a56c0ac4b9"
integrity sha512-vrMcvSuMz16YY6GSVZ0dWDTJP8jqk3iFQ/Aq5iqblPwxSVVZI+zxDyTX0VPqtQsDnfdrBDcsmhgTEOh5R8Lbpw==
caniuse-lite@^1.0.30000844:
version "1.0.30001021"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001021.tgz#e75ed1ef6dbadd580ac7e7720bb16f07b083f254"
integrity sha512-wuMhT7/hwkgd8gldgp2jcrUjOU9RXJ4XxGumQeOsUr91l3WwmM68Cpa/ymCnWEDqakwFXhuDQbaKNHXBPgeE9g==
capture-exit@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/capture-exit/-/capture-exit-2.0.0.tgz#fb953bfaebeb781f62898239dabb426d08a509a4"
@ -3791,13 +3796,20 @@ continuable-cache@^0.3.1:
resolved "https://registry.yarnpkg.com/continuable-cache/-/continuable-cache-0.3.1.tgz#bd727a7faed77e71ff3985ac93351a912733ad0f"
integrity sha1-vXJ6f67XfnH/OYWskzUakSczrQ8=
convert-source-map@^1.1.0, convert-source-map@^1.5.1:
convert-source-map@^1.1.0:
version "1.6.0"
resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.6.0.tgz#51b537a8c43e0f04dec1993bffcdd504e758ac20"
integrity sha512-eFu7XigvxdZ1ETfbgPBohgyQ/Z++C0eEhTor0qRwBw9unw+L0/6V8wkSuGgzdThkiS5lSpdptOQPD8Ak40a+7A==
dependencies:
safe-buffer "~5.1.1"
convert-source-map@^1.5.1:
version "1.7.0"
resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.7.0.tgz#17a2cb882d7f77d3490585e2ce6c524424a3a442"
integrity sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA==
dependencies:
safe-buffer "~5.1.1"
cookie-parser@^1.4.4:
version "1.4.4"
resolved "https://registry.yarnpkg.com/cookie-parser/-/cookie-parser-1.4.4.tgz#e6363de4ea98c3def9697b93421c09f30cf5d188"
@ -3856,7 +3868,12 @@ core-js@2.4.1:
resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.4.1.tgz#4de911e667b0eae9124e34254b53aea6fc618d3e"
integrity sha1-TekR5mew6ukSTjQlS1OupvxhjT4=
core-js@^2.4.0, core-js@^2.5.0, core-js@^2.6.5:
core-js@^2.4.0, core-js@^2.5.0:
version "2.6.11"
resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.11.tgz#38831469f9922bded8ee21c9dc46985e0399308c"
integrity sha512-5wjnpaT/3dV+XB4borEsnAYQchn00XSgTAWKDkEqv+K8KevjbzmofK6hfJ9TZIlpj2N0xQpazy7PiRQiWHqzWg==
core-js@^2.6.5:
version "2.6.9"
resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.9.tgz#6b4b214620c834152e179323727fc19741b084f2"
integrity sha512-HOpZf6eXmnl7la+cUdMnLvUxKNqLUzJvgIziQ0DiF3JwSImNphIqdGqzj6hIKyX04MmV0poclQ7+wjWvxQyR2A==
@ -4279,11 +4296,16 @@ ee-first@1.1.1:
resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d"
integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=
electron-to-chromium@^1.3.247, electron-to-chromium@^1.3.30, electron-to-chromium@^1.3.47:
electron-to-chromium@^1.3.247, electron-to-chromium@^1.3.30:
version "1.3.252"
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.252.tgz#5b6261965b564a0f4df0f1c86246487897017f52"
integrity sha512-NWJ5TztDnjExFISZHFwpoJjMbLUifsNBnx7u2JI0gCw6SbKyQYYWWtBHasO/jPtHym69F4EZuTpRNGN11MT/jg==
electron-to-chromium@^1.3.47:
version "1.3.335"
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.335.tgz#5fb6084a25cb1e2542df91e62b62e1931a602303"
integrity sha512-ngKsDGd/xr2lAZvilxTfdvfEiQKmavyXd6irlswaHnewmXoz6JgbM9FUNwgp3NFIUHHegh1F87H8f5BJ8zABxw==
elegant-spinner@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/elegant-spinner/-/elegant-spinner-1.0.1.tgz#db043521c95d7e303fd8f345bedc3349cfb0729e"
@ -5684,10 +5706,10 @@ extsprintf@^1.2.0:
resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f"
integrity sha1-4mifjzVvrWLMplo6kcXfX5VRaS8=
fake-xml-http-request@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/fake-xml-http-request/-/fake-xml-http-request-2.0.0.tgz#41a92f0ca539477700cb1dafd2df251d55dac8ff"
integrity sha512-UjNnynb6eLAB0lyh2PlTEkjRJORnNsVF1hbzU+PQv89/cyBV9GDRCy7JAcLQgeCLYT+3kaumWWZKEJvbaK74eQ==
fake-xml-http-request@~2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/fake-xml-http-request/-/fake-xml-http-request-2.0.1.tgz#e4a7f256af055d8059deb23c9d7ae721d28cf078"
integrity sha512-KzT+G4aLM1Btg25QRGxB6yGLGOVZXXzrH8I4OG3KHwsdoqFclyW3alieqh5NaYGcmbQvNOn/ldGO1rGKf7CNdA==
faker@^4.1.0:
version "4.1.0"
@ -6278,7 +6300,7 @@ glob@^5.0.10:
once "^1.3.0"
path-is-absolute "^1.0.0"
glob@^7.0.0, glob@^7.0.3, glob@^7.0.4, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@~7.1.1:
glob@^7.0.0, glob@^7.0.3, glob@^7.0.4, glob@^7.1.2, glob@^7.1.4, glob@~7.1.1:
version "7.1.4"
resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.4.tgz#aa608a2f6c577ad357e1ae5a5c26d9a8d1969255"
integrity sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==
@ -6290,6 +6312,18 @@ glob@^7.0.0, glob@^7.0.3, glob@^7.0.4, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, gl
once "^1.3.0"
path-is-absolute "^1.0.0"
glob@^7.1.3:
version "7.1.6"
resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6"
integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==
dependencies:
fs.realpath "^1.0.0"
inflight "^1.0.4"
inherits "2"
minimatch "^3.0.4"
once "^1.3.0"
path-is-absolute "^1.0.0"
global-modules@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/global-modules/-/global-modules-1.0.0.tgz#6d770f0eb523ac78164d72b5e71a8877265cc3ea"
@ -8203,18 +8237,30 @@ mime-db@1.40.0:
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.40.0.tgz#a65057e998db090f732a68f6c276d387d4126c32"
integrity sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA==
mime-db@1.43.0:
version "1.43.0"
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.43.0.tgz#0a12e0502650e473d735535050e7c8f4eb4fae58"
integrity sha512-+5dsGEEovYbT8UY9yD7eE4XTc4UwJ1jBYlgaQQF38ENsKR3wj/8q8RFZrF9WIZpB2V1ArTVFUva8sAul1NzRzQ==
"mime-db@>= 1.40.0 < 2":
version "1.41.0"
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.41.0.tgz#9110408e1f6aa1b34aef51f2c9df3caddf46b6a0"
integrity sha512-B5gxBI+2K431XW8C2rcc/lhppbuji67nf9v39eH8pkWoZDxnAL0PxdpH32KYRScniF8qDHBDlI+ipgg5WrCUYw==
mime-types@^2.1.12, mime-types@^2.1.18, mime-types@^2.1.19, mime-types@~2.1.19, mime-types@~2.1.24:
mime-types@^2.1.12, mime-types@^2.1.18, mime-types@^2.1.19, mime-types@~2.1.19:
version "2.1.24"
resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.24.tgz#b6f8d0b3e951efb77dedeca194cff6d16f676f81"
integrity sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ==
dependencies:
mime-db "1.40.0"
mime-types@~2.1.24:
version "2.1.26"
resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.26.tgz#9c921fc09b7e149a65dfdc0da4d20997200b0a06"
integrity sha512-01paPWYgLrkqAyrlDorC1uDwl2p3qZT7yl806vW7DvDoxwXi46jsjFbg+WdwotBIk6/MbEhO/dh5aZ5sNj/dWQ==
dependencies:
mime-db "1.43.0"
mime@1.6.0:
version "1.6.0"
resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1"
@ -9288,12 +9334,12 @@ prepend-http@^2.0.0:
integrity sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc=
pretender@^2.0.0:
version "2.1.1"
resolved "https://registry.yarnpkg.com/pretender/-/pretender-2.1.1.tgz#5085f0a1272c31d5b57c488386f69e6ca207cb35"
integrity sha512-IkidsJzaroAanw3I43tKCFm2xCpurkQr9aPXv5/jpN+LfCwDaeI8rngVWtQZTx4qqbhc5zJspnLHJ4N/25KvDQ==
version "2.1.2"
resolved "https://registry.yarnpkg.com/pretender/-/pretender-2.1.2.tgz#02d7c0a3f18cb0ce376dfc4fb0043ca288f50316"
integrity sha512-5Jx7kBalWDn8oEKfw6nAcx2KK4GkDSQXG3WhgaPsDtak6Rv6nTeQjOdvOM9PEvauS+9Ur+DLfZTDWBtqK6lFVA==
dependencies:
"@xg-wang/whatwg-fetch" "^3.0.0"
fake-xml-http-request "^2.0.0"
fake-xml-http-request "~2.0.0"
route-recognizer "^0.3.3"
prettier@^1.10.2:
@ -9905,13 +9951,20 @@ resolve@1.9.0:
dependencies:
path-parse "^1.0.6"
resolve@^1.1.3, resolve@^1.1.7, resolve@^1.10.0, resolve@^1.10.1, resolve@^1.11.1, resolve@^1.12.0, resolve@^1.3.2, resolve@^1.3.3, resolve@^1.4.0, resolve@^1.5.0, resolve@^1.7.1, resolve@^1.8.1:
resolve@^1.1.3, resolve@^1.1.7, resolve@^1.10.1, resolve@^1.11.1, resolve@^1.12.0, resolve@^1.3.2, resolve@^1.4.0, resolve@^1.5.0, resolve@^1.7.1, resolve@^1.8.1:
version "1.12.0"
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.12.0.tgz#3fc644a35c84a48554609ff26ec52b66fa577df6"
integrity sha512-B/dOmuoAik5bKcD6s6nXDCjzUKnaDvdkRyAk6rsmsKLipWj4797iothd7jmmUhWTfinVMU+wc56rYKsit2Qy4w==
dependencies:
path-parse "^1.0.6"
resolve@^1.10.0, resolve@^1.3.3:
version "1.14.2"
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.14.2.tgz#dbf31d0fa98b1f29aa5169783b9c290cb865fea2"
integrity sha512-EjlOBLBO1kxsUxsKjLt7TAECyKW6fOh1VRkykQkKGzcBbjjPIxBqGh0jf7GJ3k/f5mxMqW3htMD3WdTUVtW8HQ==
dependencies:
path-parse "^1.0.6"
responselike@1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/responselike/-/responselike-1.0.2.tgz#918720ef3b631c5642be068f15ade5a46f4ba1e7"
@ -10810,7 +10863,12 @@ symbol-tree@^3.2.2:
resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2"
integrity sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==
symlink-or-copy@^1.0.0, symlink-or-copy@^1.0.1, symlink-or-copy@^1.1.8, symlink-or-copy@^1.2.0:
symlink-or-copy@^1.0.0, symlink-or-copy@^1.0.1, symlink-or-copy@^1.1.8:
version "1.3.1"
resolved "https://registry.yarnpkg.com/symlink-or-copy/-/symlink-or-copy-1.3.1.tgz#9506dd64d8e98fa21dcbf4018d1eab23e77f71fe"
integrity sha512-0K91MEXFpBUaywiwSSkmKjnGcasG/rVBXFLJz5DrgGabpYD6N+3yZrfD6uUIfpuTu65DZLHi7N8CizHc07BPZA==
symlink-or-copy@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/symlink-or-copy/-/symlink-or-copy-1.2.0.tgz#5d49108e2ab824a34069b68974486c290020b393"
integrity sha512-W31+GLiBmU/ZR02Ii0mVZICuNEN9daZ63xZMPDsYgPgNjMtg+atqLEGI7PPI936jYSQZxoLb/63xos8Adrx4Eg==
@ -10968,9 +11026,9 @@ text-table@^0.2.0:
integrity sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=
"textextensions@1 || 2":
version "2.5.0"
resolved "https://registry.yarnpkg.com/textextensions/-/textextensions-2.5.0.tgz#e21d3831dafa37513dd80666dff541414e314293"
integrity sha512-1IkVr355eHcomgK7fgj1Xsokturx6L5S2JRT5WcRdA6v5shk9sxWuO/w/VbpQexwkXJMQIa/j1dBi3oo7+HhcA==
version "2.6.0"
resolved "https://registry.yarnpkg.com/textextensions/-/textextensions-2.6.0.tgz#d7e4ab13fe54e32e08873be40d51b74229b00fc4"
integrity sha512-49WtAWS+tcsy93dRt6P0P3AMD2m5PvXRhuEA0kaXos5ZLlujtYmpmFsB+QvWUSxE1ZsstmYXfQ7L40+EcQgpAQ==
through2@^2.0.0:
version "2.0.5"