Test mutation.

This commit is contained in:
richburdon 2020-05-24 09:55:36 -04:00
parent ead184c3a9
commit 5476d9fce8
20 changed files with 148 additions and 58 deletions

View File

@ -6,17 +6,19 @@ Apollo GraphQL client and server using express.
### POC ### POC
- [ ] Trigger server-side wire commands (separate express path?) - [ ] Trigger server-side wire commands (mutation or separate express path?)
### Next ### Next
- [ ] Routes. - [ ] Config routes for services (test).
- [ ] Fix JsonTree (yarn link).
- [ ] Client/server API abstraction (error handler, etc.)
- [ ] Webpack config (remove dynamic config?) - [ ] Webpack config (remove dynamic config?)
- [ ] Client/server API abstraction (error handler, etc.)
- [ ] Port dashboard API calls (resolve config first).
- [ ] Port dashboard react modules with dummy resolvers.
- [ ] Fix JsonTree (yarn link).
- [ ] https://github.com/standard/standardx (JSX) - [ ] https://github.com/standard/standardx (JSX)
- [ ] Shared config.
- [ ] Port dashboard modules with dummy resolvers.
### Done ### Done

View File

@ -29,7 +29,7 @@ This creates the following folders:
NOTE: GQL and Production files and exported and may be used by the server. NOTE: GQL and Production files and exported and may be used by the server.
```javascript ```javascript
import QUERY_STATUS from '@dxos/console-client/gql/status.graphql'; import QUERY_STATUS from '@dxos/console-client/gql/system_status.graphql';
import config from '@dxos/console-client/config.json'; import config from '@dxos/console-client/config.json';
... ...

View File

@ -6,7 +6,7 @@
app: app:
title: 'Console' title: 'Console'
org': 'DxOS' org': 'DxOS'
theme: 'dark' theme: 'light'
website: 'https://dxos.org' website: 'https://dxos.org'
publicUrl: '/console' publicUrl: '/console'

View File

@ -3,7 +3,7 @@
# #
{ {
ipfs { ipfs_status {
json json
} }
} }

View File

@ -3,7 +3,7 @@
# #
{ {
status { system_status {
timestamp timestamp
version version
} }

View File

@ -0,0 +1,10 @@
#
# Copyright 2020 DxOS
#
mutation Action($command: String!) {
wns_action(command: $command) {
timestamp
code
}
}

View File

@ -3,7 +3,7 @@
# #
{ {
wns { wns_status @client {
json @client json
} }
} }

View File

@ -27,6 +27,7 @@
"testEnvironment": "node" "testEnvironment": "node"
}, },
"dependencies": { "dependencies": {
"@apollo/react-components": "^3.1.5",
"@apollo/react-hooks": "^3.1.5", "@apollo/react-hooks": "^3.1.5",
"@babel/runtime": "^7.8.7", "@babel/runtime": "^7.8.7",
"@dxos/gem-core": "^1.0.0-beta.11", "@dxos/gem-core": "^1.0.0-beta.11",

View File

@ -3,6 +3,7 @@
// //
import React, { Component } from 'react'; import React, { Component } from 'react';
import Typography from '@material-ui/core/Typography';
/** /**
* Root-level error boundary. * Root-level error boundary.
@ -34,7 +35,10 @@ class ErrorBoundary extends Component {
if (error) { if (error) {
return ( return (
<div>
<Typography>Error</Typography>
<pre>{String(error)}</pre> <pre>{String(error)}</pre>
</div>
); );
} }

View File

@ -16,9 +16,16 @@ const useStyles = makeStyles(() => ({
} }
})); }));
/**
* Remove Apollo __typename directive.
* @param {Object} data
* @returns {Object}
*/
const removeTypename = data => transform(data, (result, value, key) => { const removeTypename = data => transform(data, (result, value, key) => {
result[key] = isObject(value) && '__typename' in value ? omit(value, '__typename') : value; if (key !== '__typename') {
}); result[key] = isObject(value) ? ('__typename' in value ? omit(value, '__typename') : value) : value;
}
}, {});
const Json = ({ data }) => { const Json = ({ data }) => {
const classes = useStyles(); const classes = useStyles();

View File

@ -17,7 +17,8 @@ const useStyles = makeStyles((theme) => ({
root: { root: {
display: 'flex', display: 'flex',
flexDirection: 'column', flexDirection: 'column',
flex: 1 flex: 1,
overflow: 'hidden'
}, },
container: { container: {
display: 'flex', display: 'flex',
@ -25,19 +26,19 @@ const useStyles = makeStyles((theme) => ({
flex: 1, flex: 1,
overflow: 'hidden' overflow: 'hidden'
}, },
sidebar: {
display: 'flex',
flexDirection: 'column',
flexShrink: 0,
width: 180,
borderRight: `1px solid ${theme.palette.primary.dark}`
},
main: { main: {
display: 'flex', display: 'flex',
flex: 1, flex: 1,
overflow: 'hidden' overflow: 'hidden'
}, },
cooter: { sidebar: {
display: 'flex',
flexDirection: 'column',
flexShrink: 0,
width: 200,
borderRight: `1px solid ${theme.palette.primary.dark}`
},
footer: {
display: 'flex', display: 'flex',
flexShrink: 0 flexShrink: 0
} }

View File

@ -2,25 +2,23 @@
// Copyright 2020 DxOS // Copyright 2020 DxOS
// //
import React, { useContext } from 'react'; import React from 'react';
import { useQuery } from '@apollo/react-hooks'; import { useQuery } from '@apollo/react-hooks';
import Json from '../components/Json'; import Json from '../components/Json';
import { ConsoleContext, useQueryStatusReducer } from '../hooks'; import { useQueryStatusReducer } from '../hooks';
import QUERY from '../../gql/ipfs.graphql'; import IPFS_STATUS from '../../gql/ipfs_status.graphql';
const IPFS = () => { const IPFS = () => {
const { config } = useContext(ConsoleContext); const data = useQueryStatusReducer(useQuery(IPFS_STATUS));
const data = useQueryStatusReducer(useQuery(QUERY, { pollInterval: config.api.pollInterval }));
if (!data) { if (!data) {
return null; return null;
} }
// TODO(burdon): Return structured GraphQL.
return ( return (
<Json data={{ ipfs: JSON.parse(data.ipfs.json) }} /> <Json data={JSON.parse(data.ipfs_status.json)} />
); );
}; };

View File

@ -9,17 +9,17 @@ import Json from '../components/Json';
import { ConsoleContext, useQueryStatusReducer } from '../hooks'; import { ConsoleContext, useQueryStatusReducer } from '../hooks';
import QUERY from '../../gql/status.graphql'; import SYSTEM_STATUS from '../../gql/system_status.graphql';
const Status = () => { const Status = () => {
const { config } = useContext(ConsoleContext); const { config } = useContext(ConsoleContext);
const data = useQueryStatusReducer(useQuery(QUERY, { pollInterval: config.api.pollInterval })); const data = useQueryStatusReducer(useQuery(SYSTEM_STATUS, { pollInterval: config.api.pollInterval }));
if (!data) { if (!data) {
return null; return null;
} }
return ( return (
<Json data={data} /> <Json data={data.system_status} />
); );
}; };

View File

@ -4,13 +4,16 @@
import React, { useContext } from 'react'; import React, { useContext } from 'react';
import { useQuery } from '@apollo/react-hooks'; import { useQuery } from '@apollo/react-hooks';
import { Mutation } from '@apollo/react-components';
import { makeStyles } from '@material-ui/core'; import { makeStyles } from '@material-ui/core';
import Button from '@material-ui/core/Button';
import Json from '../components/Json'; import Json from '../components/Json';
import { ConsoleContext, useQueryStatusReducer } from '../hooks'; import { ConsoleContext, useQueryStatusReducer } from '../hooks';
import QUERY from '../../gql/wns.graphql'; import WNS_STATUS from '../../gql/wns_status.graphql';
import WNS_ACTION from '../../gql/wns_action.graphql';
const useStyles = makeStyles((theme) => ({ const useStyles = makeStyles((theme) => ({
root: { root: {
@ -24,20 +27,30 @@ const useStyles = makeStyles((theme) => ({
const WNS = () => { const WNS = () => {
const classes = useStyles(); const classes = useStyles();
const { config } = useContext(ConsoleContext); const { config } = useContext(ConsoleContext);
const data = useQueryStatusReducer(useQuery(QUERY, { pollInterval: config.api.pollInterval })); const data = useQueryStatusReducer(useQuery(WNS_STATUS, { pollInterval: config.api.pollInterval }));
if (!data) { if (!data) {
return null; return null;
} }
// TODO(burdon): peers causes issues.
// Warning: Failed prop type: Invalid prop `children` supplied to `ForwardRef(Typography)`, expected a ReactNode.
const d = JSON.parse(data.wns.json);
d.peers = [];
// TODO(burdon): Return structured GraphQL.
return ( return (
<div className={classes.root}> <div className={classes.root}>
<Json data={{ wns: d }} /> <Mutation mutation={WNS_ACTION}>
{(action, { data }) => (
<div>
<Button
onClick={() => {
action({ variables: { command: 'test' } });
}}
>
Test
</Button>
<pre>Result: {JSON.stringify(data)}</pre>
</div>
)}
</Mutation>
<Json data={JSON.parse(data.wns_status.json)} />
</div> </div>
); );
}; };

View File

@ -21,7 +21,7 @@ export const createResolvers = config => {
return { return {
Query: { Query: {
wns: async () => { wns_status: async () => {
log('Querying WNS...'); log('Querying WNS...');
const status = await registry.getStatus(); const status = await registry.getStatus();

View File

@ -1,7 +1,7 @@
{ {
"build": { "build": {
"name": "@dxos/console-client", "name": "@dxos/console-client",
"buildDate": "2020-05-24T02:00:10.452Z", "buildDate": "2020-05-24T13:13:49.317Z",
"version": "1.0.0-beta.0" "version": "1.0.0-beta.0"
} }
} }

View File

@ -6,17 +6,36 @@ type JSONResult {
json: String! json: String!
} }
type Log {
log: [String]!
}
type Status { type Status {
timestamp: String! timestamp: String!
version: String! version: String!
} }
type Result {
timestamp: String!
code: Int!
}
#
# Schema
#
type Query { type Query {
status: Status system_status: Status
ipfs: JSONResult ipfs_status: JSONResult
wns: JSONResult wns_status: JSONResult
wns_log: Log
}
type Mutation {
wns_action(command: String!): Result
} }
schema { schema {
mutation: Mutation
query: Query query: Query
} }

View File

@ -10,9 +10,9 @@ import yaml from 'js-yaml';
import { ApolloServer, gql } from 'apollo-server-express'; import { ApolloServer, gql } from 'apollo-server-express';
import { print } from 'graphql/language'; import { print } from 'graphql/language';
import QUERY_STATUS from '@dxos/console-client/gql/status.graphql'; import SYSTEM_STATUS from '@dxos/console-client/gql/system_status.graphql';
import { resolvers } from './resolvers'; import { createResolvers } from './resolvers';
import SCHEMA from './gql/api.graphql'; import SCHEMA from './gql/api.graphql';
@ -60,7 +60,8 @@ app.get(`${publicUrl}(/:filePath)?`, (req, res) => {
const server = new ApolloServer({ const server = new ApolloServer({
typeDefs: SCHEMA, typeDefs: SCHEMA,
resolvers,
resolvers: createResolvers(config),
// https://www.apollographql.com/docs/apollo-server/testing/graphql-playground // https://www.apollographql.com/docs/apollo-server/testing/graphql-playground
// https://github.com/prisma-labs/graphql-playground#usage // https://github.com/prisma-labs/graphql-playground#usage
@ -73,7 +74,7 @@ const server = new ApolloServer({
{ {
name: 'Status', name: 'Status',
endpoint: config.api.path, endpoint: config.api.path,
query: print(gql(QUERY_STATUS)) query: print(gql(SYSTEM_STATUS))
} }
] ]
} }

View File

@ -13,13 +13,31 @@ const log = debug('dxos:console:server:resolvers');
// Resolvers // Resolvers
// //
export const resolvers = { const timestamp = () => new Date().toUTCString();
export const createResolvers = config => ({
Mutation: {
//
// WNS
//
wns_action: async (_, __, { action }) => {
log(`WNS action: ${action}`);
return {
timestamp: timestamp(),
code: 0
};
}
},
Query: { Query: {
// //
// Status // System
// //
status: () => ({
timestamp: new Date().toUTCString(), system_status: () => ({
timestamp: timestamp(),
version version
}), }),
@ -29,15 +47,20 @@ export const resolvers = {
// https://github.com/ipfs/js-ipfs // https://github.com/ipfs/js-ipfs
// https://github.com/ipfs/js-ipfs/tree/master/packages/ipfs-http-client#api // https://github.com/ipfs/js-ipfs/tree/master/packages/ipfs-http-client#api
// //
ipfs: async () => {
ipfs_status: async () => {
log('Calling IPFS...'); log('Calling IPFS...');
// TODO(burdon): Config. // TODO(burdon): Config.
// NOTE: Hangs if server not running.
const ipfs = new IpfsHttpClient('/ip4/127.0.0.1/tcp/5001'); const ipfs = new IpfsHttpClient('/ip4/127.0.0.1/tcp/5001');
const version = await ipfs.version(); const version = await ipfs.version();
const status = await ipfs.id(); const status = await ipfs.id();
console.log(version);
log('Done');
return { return {
json: JSON.stringify({ json: JSON.stringify({
version, version,
@ -46,4 +69,4 @@ export const resolvers = {
}; };
} }
} }
}; });

View File

@ -29,6 +29,17 @@
ts-invariant "^0.4.4" ts-invariant "^0.4.4"
tslib "^1.10.0" tslib "^1.10.0"
"@apollo/react-components@^3.1.5":
version "3.1.5"
resolved "https://registry.yarnpkg.com/@apollo/react-components/-/react-components-3.1.5.tgz#040d2f35ce4947747efe16f76d59dcbd797ffdaf"
integrity sha512-c82VyUuE9VBnJB7bnX+3dmwpIPMhyjMwyoSLyQWPHxz8jK4ak30XszJtqFf4eC4hwvvLYa+Ou6X73Q8V8e2/jg==
dependencies:
"@apollo/react-common" "^3.1.4"
"@apollo/react-hooks" "^3.1.5"
prop-types "^15.7.2"
ts-invariant "^0.4.4"
tslib "^1.10.0"
"@apollo/react-hooks@^3.1.5": "@apollo/react-hooks@^3.1.5":
version "3.1.5" version "3.1.5"
resolved "https://registry.yarnpkg.com/@apollo/react-hooks/-/react-hooks-3.1.5.tgz#7e710be52461255ae7fc0b3b9c2ece64299c10e6" resolved "https://registry.yarnpkg.com/@apollo/react-hooks/-/react-hooks-3.1.5.tgz#7e710be52461255ae7fc0b3b9c2ece64299c10e6"