Query redux apollo, nav and menu, load .gql, fragments

This commit is contained in:
JUDIT GRESKOVITS 2017-05-10 17:51:07 +01:00 committed by Judit Greskovits
parent baaebb4085
commit 63d16e6f98
29 changed files with 255564 additions and 54 deletions

View File

@ -4,22 +4,25 @@
"private": true,
"dependencies": {
"apollo": "^0.2.2",
"graphql-tag": "^2.0.0",
"immutability-helper": "^2.2.0",
"proxyquire": "^1.7.11",
"react": "^15.5.4",
"react-apollo": "^1.2.0",
"react-dom": "^15.5.4",
"react-redux": "^5.0.4",
"react-router": "^4.1.1",
"react-router-dom": "^4.1.1",
"redux": "^3.6.0"
"redux": "^3.6.0",
"rewire": "^2.5.2"
},
"devDependencies": {
"react-scripts": "0.9.5"
},
"scripts": {
"start": "PORT=3006 react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test --env=jsdom",
"eject": "react-scripts eject"
"start": "PORT=3006 node scripts/customized-config start",
"build": "node scripts/customized-config build",
"test": "node scripts/customized-config test --env=jsdom",
"eject": "node scripts/customized-config eject"
}
}

View File

@ -0,0 +1,14 @@
const path = require('path');
module.exports = function(config) {
// Add support for loading .graphql files
config.module.loaders[0].exclude.push(/\.(graphql|gql)$/);
config.module.loaders.push({
test: /\.(graphql|gql)$/,
exclude: /node_modules/,
loader: require.resolve('graphql-tag/loader'),
});
return config;
}

View File

@ -0,0 +1,72 @@
/*
This module runs the scripts from react-scripts (Create React App)
and gives an opportunity to override the Webpack config by creating
a "config-overrides.dev.js" or "config-overrides.prod.js" file in the
root of the project.
The config-override file should export a single function that takes a
config and returns the modified config, like this:
module.exports = function(webpackConfig) {
return webpackConfig;
};
*/
var rewire = require('rewire');
var proxyquire = require('proxyquire');
switch(process.argv[2]) {
// The "start" script is run during development mode
case 'start':
rewireModule('react-scripts/scripts/start.js', loadCustomizer('./config-overrides.dev'));
break;
// The "build" script is run to produce a production bundle
case 'build':
rewireModule('react-scripts/scripts/build.js', loadCustomizer('./config-overrides.prod'));
break;
// The "test" script runs all the tests with Jest
case 'test':
// Load customizations from the config-overrides.testing file.
// That file should export a single function that takes a config and returns a config
let customizer = loadCustomizer('./config-overrides.testing');
proxyquire('react-scripts/scripts/test.js', {
// When test.js asks for '../utils/createJestConfig' it will get this instead:
'../utils/createJestConfig': (...args) => {
// Use the existing createJestConfig function to create a config, then pass
// it through the customizer
var createJestConfig = require('react-scripts/utils/createJestConfig');
return customizer(createJestConfig(...args));
}
});
break;
default:
console.log('customized-config only supports "start", "build", and "test" options.');
process.exit(-1);
}
// Attempt to load the given module and return null if it fails.
function loadCustomizer(module) {
try {
return require(module);
} catch(e) {
if(e.code !== "MODULE_NOT_FOUND") {
throw e;
}
}
// If the module doesn't exist, return a
// noop that simply returns the config it's given.
return config => config;
}
function rewireModule(modulePath, customizer) {
// Load the module with `rewire`, which allows modifying the
// script's internal variables.
let defaults = rewire(modulePath);
// Reach into the module, grab its global 'config' variable,
// pass it through the customizer function, and then set it back.
// 'config' is Create React App's built-in Webpack config.
let config = defaults.__get__('config');
config = customizer(Object.assign({}, config));
defaults.__set__('config', config);
}

View File

@ -1,7 +1,7 @@
import React, { Component } from 'react';
import { gql, graphql } from 'react-apollo';
import { graphql } from 'react-apollo';
import { Link } from 'react-router-dom';
import update from 'immutability-helper';
import DeploymentGroupsQuery from '../../graphql/DeploymentGroups.gql';
class DeploymentGroupList extends Component {
@ -38,17 +38,7 @@ class DeploymentGroupList extends Component {
}
}
const deploymentGroups = gql`
query {
deploymentGroups {
uuid
name
id
}
}
`;
const DeploymentGroupListWithData = graphql(deploymentGroups, {
const DeploymentGroupListWithData = graphql(DeploymentGroupsQuery, {
props: ({ data: { deploymentGroups, loading, error }}) => ({
deploymentGroups,
loading,

View File

@ -1,5 +1,6 @@
import React, { Component } from 'react';
import { gql, graphql } from 'react-apollo';
import { graphql } from 'react-apollo';
import InstancesQuery from '../../graphql/Instances.gql';
class InstanceList extends Component {
@ -28,20 +29,7 @@ class InstanceList extends Component {
}
}
const instances = gql`
query Instances($deploymentGroupId: String!){
deploymentGroup(id: $deploymentGroupId) {
services {
instances {
uuid
name
}
}
}
}
`;
const InstanceListWithData = graphql(instances, {
const InstanceListWithData = graphql(InstancesQuery, {
options(props) {
return {
variables: {

View File

@ -0,0 +1,71 @@
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { Link } from 'react-router-dom';
class Breadcrumb extends Component {
render() {
const {
deploymentGroup,
service,
match,
location
} = this.props;
const dgLink = deploymentGroup ?
<Link to={`/deployment-groups/${deploymentGroup.id}`}>
{deploymentGroup.name}
</Link> : null;
const sLink = service ?
<Link to={`/deployment-groups/${deploymentGroup.id}/services/${service.id}`}>
{service.name}
</Link> : null;
let breadcrumb = dgLink && sLink ?
<p>{dgLink} / {sLink}</p> : dgLink ?
<p>{dgLink}</p> : null;
return (
<div>
<div>
<h3>{breadcrumb}</h3>
</div>
</div>
);
}
}
const ConnectedBreadcrumb = connect(
(state, ownProps) => {
const params = ownProps.match.params;
const deploymentGroupId = params.deploymentGroup;
const serviceId = params.service;
const apolloData = state.apollo.data;
const keys = Object.keys(apolloData);
let deploymentGroup, service;
if(keys.length) {
// These should be selectors
if(deploymentGroupId) {
deploymentGroup = keys.reduce((dg, k) =>
apolloData[k].__typename === 'DeploymentGroup' &&
apolloData[k].id === deploymentGroupId ?
apolloData[k] : dg, {});
if(serviceId) {
service = keys.reduce((s, k) =>
apolloData[k].__typename === 'Service' &&
apolloData[k].id === serviceId ?
apolloData[k] : s, {});
}
}
}
return {
deploymentGroup,
service
};
},
(dispatch) => ({})
)(Breadcrumb);
export default ConnectedBreadcrumb;

View File

@ -0,0 +1,2 @@
export { default as Breadcrumb } from './breadcrumb';
export { default as Menu } from './menu';

View File

@ -0,0 +1,68 @@
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { Link } from 'react-router-dom';
class Menu extends Component {
render() {
const {
sections,
matchUrl
} = this.props;
const menu = sections ?
sections.map((s, i) =>
<Link key={i} to={`${matchUrl}/${s.id}`}> {s.name} </Link>) : null;
return (
<div>
<div>
<h4>{menu}</h4>
</div>
</div>
);
}
}
const ConnectedMenu = connect(
(state, ownProps) => {
const params = ownProps.match.params;
const matchUrl = ownProps.match.url;
const deploymentGroupId = params.deploymentGroup;
const serviceId = params.service;
let sections;
// To come from Redux store
if(deploymentGroupId && serviceId) {
sections = [{
name: 'Metrics',
id: 'metrics'
}, {
name: 'Single Metrics',
id: 'single-metrics'
}, {
name: 'Instances',
id: 'instances'
}]
}
else if(deploymentGroupId) {
sections = [{
name: 'Services',
id: 'services'
}, {
name: 'Instances',
id: 'instances'
}]
}
return {
sections,
matchUrl
};
},
(dispatch) => ({})
)(Menu);
export default ConnectedMenu;

View File

@ -0,0 +1,2 @@
export { default as ServiceMetrics } from './metrics';
export { default as SingleMetrics } from './single-metrics';

View File

@ -0,0 +1,56 @@
import React, { Component } from 'react';
import { graphql } from 'react-apollo';
import MetricsQuery from '../../graphql/Metrics.gql';
class Metrics extends Component {
render() {
const {
instances,
metrics
} = this.props;
return (
<div>
<div>
<h4>Metrics</h4>
<p>{JSON.stringify(metrics)}</p>
</div>
</div>
);
}
}
const MetricsWithData = graphql(MetricsQuery, {
options(props) {
return {
variables: {
deploymentGroupId: props.match.params.deploymentGroup
}
};
},
props: ({ data: { deploymentGroup, loading, error } }) => {
const instances = deploymentGroup && deploymentGroup.services ?
deploymentGroup.services.reduce((instances, service) =>
instances.concat(service.instances), []) : null;
const metrics = instances ? instances.reduce((metrics, instance) =>
metrics.concat(instance.metrics.map((m) =>
Object.assign({}, m, {
instance: {
uuid: instance.uuid,
name: instance.name
}}))), []) : null;
return ({
instances,
metrics,
loading,
error
})
}
})(Metrics)
export default MetricsWithData;

View File

@ -0,0 +1,35 @@
import React, { Component } from 'react';
import { graphql } from 'react-apollo';
import SingleMetricsQuery from '../../graphql/SingleMetrics.gql';
class SingleMetrics extends Component {
render() {
const {
metrics
} = this.props;
return (
<div>
<div>
<h4>Metrics</h4>
<p>{JSON.stringify(metrics)}</p>
</div>
</div>
);
}
}
const SingleMetricsWithData = graphql(SingleMetricsQuery, {
options: {
pollInterval: 15*1000
},
props: ({ data: { instanceMetric, loading, error } }) => ({
metrics: instanceMetric,
loading,
error
})
})(SingleMetrics)
export default SingleMetricsWithData;

View File

@ -1,6 +1,7 @@
import React, { Component } from 'react';
import { gql, graphql } from 'react-apollo';
import { graphql } from 'react-apollo';
import { Link } from 'react-router-dom';
import ServicesQuery from '../../graphql/Services.gql';
class ServiceList extends Component {
@ -36,19 +37,7 @@ class ServiceList extends Component {
}
}
const services = gql`
query Services($deploymentGroupId: String!){
deploymentGroup(id: $deploymentGroupId) {
services {
uuid
name
id
}
}
}
`;
const ServiceListWithData = graphql(services, {
const ServiceListWithData = graphql(ServicesQuery, {
options(props) {
return {
variables: {

View File

@ -0,0 +1,11 @@
query DeploymentGroups {
deploymentGroups {
...DeploymentGroupInfo
}
}
fragment DeploymentGroupInfo on DeploymentGroup {
uuid
name
id
}

View File

@ -0,0 +1,16 @@
query Instances($deploymentGroupId: String!){
deploymentGroup(id: $deploymentGroupId) {
uuid
id
name
services {
uuid
id
name
instances {
uuid
name
}
}
}
}

View File

@ -0,0 +1,25 @@
query Instances($deploymentGroupId: String!){
deploymentGroup(id: $deploymentGroupId) {
uuid
id
name
services {
uuid
id
name
instances {
uuid
name
metrics {
type {
id
}
data {
timestamp
value
}
}
}
}
}
}

View File

@ -0,0 +1,4 @@
fragment MetricsData {
timestamp
value
}

View File

@ -0,0 +1,12 @@
query Services($deploymentGroupId: String!){
deploymentGroup(id: $deploymentGroupId) {
uuid
name
id
services {
uuid
name
id
}
}
}

View File

@ -0,0 +1,13 @@
query SingleMetrics {
instanceMetric {
type {
uuid
name
id
}
data {
timestamp
value
}
}
}

View File

@ -7,10 +7,15 @@ import {
} from 'react-router-dom';
import { Container } from '../components/layout';
import { Breadcrumb, Menu } from '../containers/navigation';
import { DeploymentGroupList } from '../containers/deployment-groups';
import { ServiceList } from '../containers/services';
import { InstanceList } from '../containers/instances';
import { ServiceMetrics, SingleMetrics } from '../containers/service';
const rootRedirect = (p) => (
<Redirect to='/deployment-groups' />
);
@ -23,6 +28,15 @@ const Router = (
<BrowserRouter>
<Container>
<Switch>
<Route path='/deployment-groups/:deploymentGroup/services/:service' component={Breadcrumb} />
<Route path='/deployment-groups/:deploymentGroup' component={Breadcrumb} />
</Switch>
<Switch>
<Route path='/deployment-groups/:deploymentGroup/services/:service' component={Menu} />
<Route path='/deployment-groups/:deploymentGroup' component={Menu} />
</Switch>
<Route path='/' exact component={rootRedirect} />
<Route path='/deployment-groups' exact component={DeploymentGroupList} />
@ -30,6 +44,8 @@ const Router = (
<Route path='/deployment-groups/:deploymentGroup/services' exact component={ServiceList} />
<Route path='/deployment-groups/:deploymentGroup/services/:service/instances' exact component={InstanceList} />
<Route path='/deployment-groups/:deploymentGroup/services/:service/metrics' exact component={ServiceMetrics} />
<Route path='/deployment-groups/:deploymentGroup/services/:service/single-metrics' exact component={SingleMetrics} />
</Container>
</BrowserRouter>

View File

@ -2,6 +2,10 @@ import { createStore, combineReducers, applyMiddleware, compose } from 'redux';
import { ApolloClient, createNetworkInterface } from 'react-apollo';
export const client = new ApolloClient({
dataIdFromObject: o => {
const id = o.id ? o.id : o.uuid ? o.uuid : o.timestamp ? o.timestamp : 'apollo-cache-key-not-defined';
return `${o.__typename}:${id}`;
},
networkInterface: createNetworkInterface({
uri: 'http://localhost:3000/graphql'
})

View File

@ -2154,6 +2154,13 @@ filesize@3.3.0:
version "3.3.0"
resolved "https://registry.yarnpkg.com/filesize/-/filesize-3.3.0.tgz#53149ea3460e3b2e024962a51648aa572cf98122"
fill-keys@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/fill-keys/-/fill-keys-1.0.2.tgz#9a8fa36f4e8ad634e3bf6b4f3c8882551452eb20"
dependencies:
is-object "~1.0.1"
merge-descriptors "~1.0.0"
fill-range@^2.1.0:
version "2.2.3"
resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-2.2.3.tgz#50b77dfd7e469bc7492470963699fe7a8485a723"
@ -2761,6 +2768,10 @@ is-number@^2.0.2, is-number@^2.1.0:
dependencies:
kind-of "^3.0.2"
is-object@~1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/is-object/-/is-object-1.0.1.tgz#8952688c5ec2ffd6b03ecc85e769e02903083470"
is-path-cwd@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/is-path-cwd/-/is-path-cwd-1.0.0.tgz#d225ec23132e89edd38fda767472e62e65f1106d"
@ -3444,7 +3455,7 @@ memory-fs@~0.4.1:
errno "^0.1.3"
readable-stream "^2.0.1"
merge-descriptors@1.0.1:
merge-descriptors@1.0.1, merge-descriptors@~1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61"
@ -3512,6 +3523,10 @@ minimist@^1.1.1, minimist@^1.2.0:
dependencies:
minimist "0.0.8"
module-not-found-error@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/module-not-found-error/-/module-not-found-error-1.0.1.tgz#cf8b4ff4f29640674d6cdd02b0e3bc523c2bbdc0"
ms@0.7.1:
version "0.7.1"
resolved "https://registry.yarnpkg.com/ms/-/ms-0.7.1.tgz#9cd13c03adbff25b65effde7ce864ee952017098"
@ -4221,6 +4236,14 @@ proxy-addr@~1.1.3:
forwarded "~0.1.0"
ipaddr.js "1.3.0"
proxyquire@^1.7.11:
version "1.7.11"
resolved "https://registry.yarnpkg.com/proxyquire/-/proxyquire-1.7.11.tgz#13b494eb1e71fb21cc3ebe3699e637d3bec1af9e"
dependencies:
fill-keys "^1.0.2"
module-not-found-error "^1.0.0"
resolve "~1.1.7"
prr@~0.0.0:
version "0.0.0"
resolved "https://registry.yarnpkg.com/prr/-/prr-0.0.0.tgz#1a84b85908325501411853d0081ee3fa86e2926a"
@ -4650,7 +4673,7 @@ resolve-pathname@^2.0.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/resolve-pathname/-/resolve-pathname-2.1.0.tgz#e8358801b86b83b17560d4e3c382d7aef2100944"
resolve@1.1.7:
resolve@1.1.7, resolve@~1.1.7:
version "1.1.7"
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b"
@ -4667,6 +4690,10 @@ restore-cursor@^1.0.1:
exit-hook "^1.0.0"
onetime "^1.0.0"
rewire@^2.5.2:
version "2.5.2"
resolved "https://registry.yarnpkg.com/rewire/-/rewire-2.5.2.tgz#6427de7b7feefa7d36401507eb64a5385bc58dc7"
right-align@^0.1.1:
version "0.1.3"
resolved "https://registry.yarnpkg.com/right-align/-/right-align-0.1.3.tgz#61339b722fe6a3515689210d24e14c96148613ef"

View File

@ -14,6 +14,7 @@
"babel-preset-es2015": "^6.24.1",
"graphql": "^0.9.6",
"graphql-server-hapi": "^0.7.2",
"graphql-subscriptions": "^0.3.1",
"graphql-tools": "^0.11.0",
"hapi": "^16.1.1",
"lodash": "^4.17.4",

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,11 @@
node_memory_rss_bytes
node_memory_heap_total_bytes
node_memory_heap_used_bytes
process_heap_bytes
process_resident_memory_bytes
process_virtual_memory_bytes
process_cpu_seconds_total
process_cpu_system_seconds_total
process_cpu_user_seconds_total
node_lag_duration_milliseconds
http_request_duration_milliseconds

View File

@ -0,0 +1,20 @@
import normalMetricDataJson from './datasets/dataset-normal.json';
import leakMetricDataJson from './datasets/dataset-leak.json';
const normaliseMetricDataset = (metricDataset) => {
return Object.keys(metricDataset).reduce((dataset, type) => {
dataset[type] = metricDataset[type].map((ds) => ({
timestamp: ds[0],
value: ds[1]
}))
return dataset;
}, {});
}
const normalMetricData = normaliseMetricDataset(normalMetricDataJson);
const leakMetricData = normaliseMetricDataset(leakMetricDataJson);
export {
normalMetricData,
leakMetricData
}

View File

@ -1,6 +1,6 @@
import { find, filter } from 'lodash';
import paramCase from 'param-case';
import data from './mock-data';
import { normalMetricData, leakMetricData } from './mock-data/metrics';
const portal = { username: 'juditgreskovits', host: 'dockerhost'};
const deploymentGroups = data.projects.data;
@ -9,6 +9,14 @@ const instances = data.instances.data;
const metricTypes = data.metrics.data.types;
const datacenters = data.datacenters.data;
const count = 10;
let index = 0;
const getInstanceMetricData = (dataset, type) => {
return dataset[type].slice(index, index + count);
}
const tick = setInterval(() => index++, 15*1000);
const resolveFunctions = {
Query: {
portal() {
@ -67,6 +75,17 @@ const resolveFunctions = {
datacenters() {
return datacenters;
},
// tmp test
instanceMetric() {
return {
type: {
uuid: 'node_memory_rss_bytes',
id: 'node_memory_rss_bytes',
name: 'node_memory_rss_bytes',
},
data: getInstanceMetricData(leakMetricData, 'node_memory_rss_bytes')
};
}
},
DeploymentGroup: {
services(deploymentGroup) {
@ -83,6 +102,18 @@ const resolveFunctions = {
find(metricTypes, { uuid: metric.type })) : []
},
},
Instance: {
metrics(instance) {
return ([{
type: {
uuid: 'metric-type-uuid',
id: 'metric-type-id',
name: 'metric-type-name'
},
data: normalMetricData.node_memory_rss_bytes
}])
}
}
};
export default resolveFunctions;

View File

@ -77,6 +77,7 @@ type Package {
type Instance {
uuid: String!
name: String!
id: String!
deploymentGoup: String!
service: String!
metrics: [InstanceMetric]!
@ -88,9 +89,8 @@ type InstanceMetric {
}
type MetricData {
type: MetricType!
timestamp: Date!
value: Int!
timestamp: Int!
value: Float!
}
type Datacenter {
@ -108,8 +108,11 @@ type Query {
instances(serviceUuid: String, serviceId: String): [Instance]
instance(uuid: String, id: String): Instance
metricTypes: [MetricType]
metricData(instanceUuid: String!, metricType: String!, from: Date!, to: Date!): [InstanceMetric]!
package: Package
datacenters: [Datacenter]
# tmp test
instanceMetric: InstanceMetric!
}
`;

View File

@ -775,7 +775,7 @@ end-of-stream@1.0.0:
dependencies:
once "~1.3.0"
es6-promise@^3.0.2:
es6-promise@^3.0.2, es6-promise@^3.2.1:
version "3.3.1"
resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-3.3.1.tgz#a08cdde84ccdbf34d027a1451bc91d4bcd28a613"
@ -988,6 +988,12 @@ graphql-server-module-graphiql@^0.7.2:
version "0.7.2"
resolved "https://registry.yarnpkg.com/graphql-server-module-graphiql/-/graphql-server-module-graphiql-0.7.2.tgz#aa1f2a26eadbf7127c1b077e633d5086da52b330"
graphql-subscriptions@^0.3.1:
version "0.3.1"
resolved "https://registry.yarnpkg.com/graphql-subscriptions/-/graphql-subscriptions-0.3.1.tgz#0cedc2d507420cf26cf414080b079f05402f0303"
dependencies:
es6-promise "^3.2.1"
graphql-tools@^0.11.0:
version "0.11.0"
resolved "https://registry.yarnpkg.com/graphql-tools/-/graphql-tools-0.11.0.tgz#14c372f6ddad7e63a757094d541a937d6b31b7da"