Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 13 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -82,12 +82,12 @@
"eslint-import-resolver-babel-module": "^5.1.2",
"express": "^4.14.0",
"google-libphonenumber": "^3.2.7",
"graphql": "^0.13.2",
"graphql": "^14.6.0",
"graphql-date": "^1.0.3",
"graphql-tag": "^0.1.9",
"graphql-tag": "^2.10.3",
"graphql-tools": "^2.8.0",
"graphql-type-datetime": "^0.2.4",
"graphql-type-json": "^0.1.4",
"graphql-type-json": "^0.3.1",
"humps": "^1.1.0",
"is-url": "^1.2.2",
"json-loader": "^0.5.4",
Expand Down Expand Up @@ -120,6 +120,7 @@
"yup": "^0.24.0"
},
"devDependencies": {
"@apollo/react-hooks": "^3.1.3",
"@babel/cli": "^7.8.4",
"@babel/core": "^7.8.4",
"@babel/plugin-proposal-class-properties": "^7.8.3",
Expand All @@ -130,7 +131,14 @@
"@babel/register": "^7.8.3",
"@sentry/browser": "^5.12.5",
"aphrodite": "^2.3.1",
"apollo-client": "^1.9.2",
"apollo-boost": "^0.4.7",
"apollo-client": "^2.6.8",
"apollo-client-legacy": "npm:apollo-client@1.9.2",
"apollo-link": "^1.2.13",
"apollo-link-batch-http": "^1.2.13",
"apollo-link-error": "^1.1.12",
"apollo-link-http": "^1.5.16",
"apollo-link-retry": "^2.2.15",
"babel-eslint": "^10.0.3",
"babel-jest": "^25.1.0",
"babel-loader": "^8.0.6",
Expand Down Expand Up @@ -174,8 +182,8 @@
"react-router-redux": "^4.0.5",
"react-sortable-hoc": "^1.11.0",
"react-test-renderer": "16",
"react-virtualized-auto-sizer": "^1.0.2",
"react-virtualized": "^9.21.2",
"react-virtualized-auto-sizer": "^1.0.2",
"redux": "^3.7.2",
"redux-thunk": "^2.1.0",
"regenerator-runtime": "^0.10.5",
Expand Down
84 changes: 84 additions & 0 deletions src/client/apollo.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import { ApolloClient } from "apollo-client";
import { InMemoryCache } from "apollo-cache-inmemory";
import { ApolloLink } from "apollo-link";
import { RetryLink } from "apollo-link-retry";
import { BatchHttpLink } from "apollo-link-batch-http";
import { onError } from "apollo-link-error";
import * as Sentry from "@sentry/browser";

window.S = Sentry;

const cache = new InMemoryCache();

const retryLink = new RetryLink();
const httpLink = new BatchHttpLink({
uri: process.env.GRAPHQL_URL || "/graphql",
credentials: "same-origin"
});

const errorLink = onError(({ graphQLErrors, networkError }) => {
if (graphQLErrors) {
graphQLErrors.forEach(({ message, locations, path, code }) => {
console.error(`[GraphQL error] [${code}]: ${message}`, {
locations,
path
});

Sentry.addBreadcrumb({
category: "apollo-graphql-error",
message: `Apollo GraphQL error: ${message}`,
data: { message, locations, path, code },
level: "error"
});

if (code === "UNAUTHORIZED") {
window.location = `/login?nextUrl=${encodeURIComponent(
window.location.pathname
)}`;
}

if (code === "NOT_FOUND") {
window.location = `/404`;
}
});
}

if (networkError) {
console.error(`[Network error]: ${networkError}`);

Sentry.addBreadcrumb({
category: "apollo-network-error",
message: `Apollo network error: ${networkError}`,
data: { networkError },
level: "error"
});
}
});

const breadcrumbLink = new ApolloLink((operation, forward) => {
Sentry.addBreadcrumb({
category: "apollo-request",
message: `Apollo request - ${operation.operationName}`,
data: operation,
level: "info"
});

return forward(operation).map(data => {
Sentry.addBreadcrumb({
category: "apollo-response",
message: `Apollo response - ${operation.operationName}`,
data,
level: "info"
});

return data;
});
});

export default new ApolloClient({
cache,
link: ApolloLink.from([retryLink, errorLink, breadcrumbLink, httpLink]),
connectToDevTools: true,
name: "spoke-client",
version: window.GIT_COMMIT_SHORT || "unknown-commit"
});
16 changes: 10 additions & 6 deletions src/client/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ import React, { Suspense } from "react";
import ReactDOM from "react-dom";
import { Router, browserHistory } from "react-router";
import { syncHistoryWithStore } from "react-router-redux";
import { ApolloProvider } from "react-apollo";
import { ApolloProvider as ApolloProviderLegacy } from "react-apollo";
import { ApolloProvider } from "@apollo/react-hooks";
import MuiThemeProvider from "material-ui/styles/MuiThemeProvider";

import muiTheme from "src/styles/mui-theme";
Expand All @@ -13,6 +14,7 @@ import Store from "src/store";
import ApolloClientSingleton from "src/network/apollo-client-singleton";
import LoadingIndicator from "src/components/LoadingIndicator";
import ErrorBoundary from "src/components/ErrorBoundary";
import client from "src/client/apollo";

import { login, logout } from "./auth-service";
import Telemetry from "./telemetry";
Expand All @@ -30,11 +32,13 @@ const history = syncHistoryWithStore(browserHistory, store.data);
ReactDOM.render(
<MuiThemeProvider muiTheme={muiTheme}>
<ErrorBoundary>
<ApolloProvider store={store.data} client={ApolloClientSingleton}>
<Suspense fallback={<LoadingIndicator />}>
<Router history={history} routes={makeRoutes()} />
</Suspense>
</ApolloProvider>
<ApolloProviderLegacy store={store.data} client={ApolloClientSingleton}>
<ApolloProvider client={client}>
<Suspense fallback={<LoadingIndicator />}>
<Router history={history} routes={makeRoutes()} />
</Suspense>
</ApolloProvider>
</ApolloProviderLegacy>
</ErrorBoundary>
</MuiThemeProvider>,
document.getElementById("mount")
Expand Down
12 changes: 12 additions & 0 deletions src/client/lib/error-helpers.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
import _ from "lodash";

/** MODERN for new Apollo */

export function errorCodes(graphqlError) {
if (!graphqlError.graphQLErrors) {
return new Set();
}

return new Set(graphqlError.graphQLErrors.map(err => err.code));
}

/** LEGACY for old Apollo */

export function getGraphQLErrors(response) {
return _.get(response, "errors.graphQLErrors", []);
}
Expand Down
15 changes: 15 additions & 0 deletions src/components/CheckReady.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import React from "react";
import LoadingIndicator from "./LoadingIndicator";

export default function checkReady(...results) {
// check for error
if (results.find(r => r.error)) {
return <p>There was an error</p>;
}

if (results.find(r => r.loading)) {
return <LoadingIndicator />;
}

return null;
}
90 changes: 43 additions & 47 deletions src/components/TopNav.jsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
import PropTypes from "prop-types";
import React from "react";
import { Link } from "react-router";
import { StyleSheet, css } from "aphrodite";
import gql from "graphql-tag";
import { useQuery } from "@apollo/react-hooks";

import IconButton from "material-ui/IconButton";
import ArrowBackIcon from "material-ui/svg-icons/navigation/arrow-back";
import { Link } from "react-router";

import UserMenu from "../containers/UserMenu";
import theme from "../styles/theme";
import { StyleSheet, css } from "aphrodite";
import loadData from "../containers/hoc/load-data";
import gql from "graphql-tag";

const styles = StyleSheet.create({
container: {
Expand Down Expand Up @@ -47,12 +50,28 @@ const styles = StyleSheet.create({
}
});

class TopNav extends React.Component {
state = {
userMenuOpen: false
};
export default function TopNav({ backToURL, orgId, title }) {
const { loading, error, data: organizationData } = useQuery(
gql`
query getCurrentOrganization($organizationId: String!) {
organization(id: $organizationId) {
id
name
}
}
`,
{
variables: {
organizationId: orgId
},
fetchPolicy: "network-only"
}
);

if (loading) return <p>Loading...</p>;
if (error) return <p>Error :(</p>;

renderBack(backToURL) {
const renderBack = () => {
if (backToURL) {
return (
<Link to={backToURL}>
Expand All @@ -66,51 +85,28 @@ class TopNav extends React.Component {
);
}
return <div />;
}
};

render() {
const { backToURL, orgId, title, data } = this.props;
return (
<div className={css(styles.container)}>
<div className={css(styles.flexColumn)}>
<div className={css(styles.inline)}>{this.renderBack(backToURL)}</div>
<div className={css(styles.inline, styles.header)}>{title}</div>
</div>
<div className={css(styles.rightFlexColumn)}>
<div className={css(styles.inline, styles.header)}>
{data.organization.name}
</div>
</div>
<div className={css(styles.userMenu)}>
<UserMenu orgId={orgId} />
return (
<div className={css(styles.container)}>
<div className={css(styles.flexColumn)}>
<div className={css(styles.inline)}>{renderBack(backToURL)}</div>
<div className={css(styles.inline, styles.header)}>{title}</div>
</div>
<div className={css(styles.rightFlexColumn)}>
<div className={css(styles.inline, styles.header)}>
{organizationData.name}
</div>
</div>
);
}
<div className={css(styles.userMenu)}>
<UserMenu orgId={orgId} />
</div>
</div>
);
}

TopNav.propTypes = {
backToURL: PropTypes.string,
title: PropTypes.string.isRequired,
orgId: PropTypes.string,
data: PropTypes.object
orgId: PropTypes.string
};

const mapQueriesToProps = ({ ownProps }) => ({
data: {
query: gql`
query getCurrentOrganization($organizationId: String!) {
organization(id: $organizationId) {
id
name
}
}
`,
variables: {
organizationId: ownProps.orgId
},
fetchPolicy: "network-only"
}
});

export default loadData(TopNav, { mapQueriesToProps });
Loading