Pedro Vila-Cerqueira • Mário Ramirez Lab

Chewie-NS Frontend:

How Chewie needs to be groomed

Chewie-NS structure

Main scripts

import React from "react";
import ReactDOM from "react-dom";
import { BrowserRouter } from "react-router-dom";
import { Provider } from "react-redux";
import { createStore, applyMiddleware, compose, combineReducers } from "redux";
import thunk from "redux-thunk";

import "./index.css";
import App from "./App";
import * as serviceWorker from "./serviceWorker";

// Define the application
const app = (
  <Provider store={store}>
    <BrowserRouter>
      <App />
    </BrowserRouter>
  </Provider>
);

ReactDOM.render(app, document.getElementById("root"));

index.js

  • Redux
  • Main App

Main scripts

import React, { Component } from "react";
import {
  Route,
  Switch,
  withRouter,
  Redirect,
  Link,
  useRouteMatch,
} from "react-router-dom";
import { connect } from "react-redux";

// Chewie local components imports
import Aux from "./hoc/Aux/Aux";
import Stats from "./containers/Stats/Stats";
import About from "./containers/About/About";
import Locus from "./containers/Locus/Locus";
import Chewie from "./containers/Chewie/Chewie";
import Schema from "./containers/Schema/Schema";
import Logout from "./containers/Auth/Logout/Logout";
import Species from "./containers/Species/Species";
import MuiLogin from "./containers/Auth/MuiLogin/MuiLogin";
import Sequences from "./containers/Sequences/Sequences";
import MuiRegister from "./containers/Auth/MuiRegister/MuiRegister";
import Profile from "./containers/Auth/Profile/Profile";
import MuiSideDrawer from "./components/Navigation/MuiSideDrawer/MuiSideDrawer";
import * as actions from "./store/actions/index";

// Material Ui Components
import MuiLink from "@material-ui/core/Link";
import Breadcrumbs from "@material-ui/core/Breadcrumbs";

// Determine URL matches for Breadcrumb generation
function SimpleBreadcrumbs() {
  const aboutMatches = useRouteMatch("/about");
  const statsMatches = useRouteMatch("/stats");
  const sequencesMatches = useRouteMatch("/sequences");
  const speciesIdMatches = useRouteMatch("/species/:species_id");
  const schemasIdMatches = useRouteMatch(
    "/species/:species_id/schemas/:schema_id"
  );
  const locusIdMatches = useRouteMatch(
    "/species/:species_id/schemas/:schema_id/locus/:locus_id"
  );

  const styles = {
    breadcrumb: {
      color: "#bb7944",
    },
  };

  const spd = JSON.parse(localStorage.getItem("speciesD"));
  const schemaName = localStorage.getItem("schemaName");

  // Breadcrumbs generation
  return (
    <>
      <Breadcrumbs aria-label="breadcrumb">
        {aboutMatches && (
          <div>
            <MuiLink component={Link} to="/about" style={styles.breadcrumb}>
              About
            </MuiLink>
          </div>
        )}
        {sequencesMatches && (
          <MuiLink component={Link} to="/sequences" style={styles.breadcrumb}>
            Search
          </MuiLink>
        )}
        {statsMatches && (
          <MuiLink component={Link} to="/stats" style={styles.breadcrumb}>
            Schemas
          </MuiLink>
        )}
        {speciesIdMatches && (
          <MuiLink
            component={Link}
            to={`/species/${speciesIdMatches.params.species_id}`}
            style={styles.breadcrumb}
          >
            <i>{spd[speciesIdMatches.params.species_id]}</i>
          </MuiLink>
        )}
        {schemasIdMatches && (
          <MuiLink
            component={Link}
            to={`/species/${schemasIdMatches.params.species_id}/schemas/${schemasIdMatches.params.schema_id}`}
            style={styles.breadcrumb}
          >
            {schemaName}
          </MuiLink>
        )}
        {locusIdMatches && (
          <MuiLink
            component={Link}
            to={`/schemas/${locusIdMatches.params.schema_id}/locus/${locusIdMatches.params.locus_id}`}
            style={styles.breadcrumb}
          >
            Locus {locusIdMatches.params.locus_id}
          </MuiLink>
        )}
      </Breadcrumbs>
    </>
  );
}

class App extends Component {
  componentDidMount() {
    this.props.onTryAutoSignup();
  }

  render() {
    // Define common app routes
    let routes = (
      <Aux>
        <SimpleBreadcrumbs />
        <Switch>
          <Route path="/auth" component={MuiLogin} />
          <Route path="/register" component={MuiRegister} />
          <Route path="/about" component={About} />
          <Route path="/" exact component={Chewie} />
          <Route path="/stats" component={Stats} />
          <Route path="/sequences" component={Sequences} />
          <Route
            path="/species/:species_id/schemas/:schema_id/locus/:locus_id"
            component={Locus}
          />
          <Route
            path="/species/:species_id/schemas/:schema_id"
            component={Schema}
          />
          <Route path="/species/:species_id" component={Species} />
          <Redirect to="/" />
        </Switch>
      </Aux>
    );

    if (this.props.isAuthenticated) {
      // Defines user authenticated routes
      routes = (
        <Switch>
          <Route path="/logout" component={Logout} />
          <Route path="/profile" component={Profile} />
          <Route path="/about" component={About} />
          <Route path="/stats" component={Stats} />
          <Route path="/sequences" component={Sequences} />
          <Route path="/" exact component={Chewie} />
          <Route
            path="/species/:species_id/schemas/:schema_id/locus/:locus_id"
            component={Locus}
          />
          <Route
            path="/species/:species_id/schemas/:schema_id"
            component={Schema}
          />
          <Route path="/species/:species_id" component={Species} />
          <Redirect to="/" />
        </Switch>
      );
    }

    return (
      <div>
        <MuiSideDrawer>{routes}</MuiSideDrawer>
      </div>
    );
  }
}

const mapStateToProps = (state) => {
  return {
    isAuthenticated: state.auth.token !== null,
  };
};

const mapDispatchToProps = (dispatch) => {
  return {
    onTryAutoSignup: () => dispatch(actions.authCheckState()),
  };
};

export default withRouter(connect(mapStateToProps, mapDispatchToProps)(App));

App.js

  • Breadcrumbs
  • Pages/Routes

Chewie-NS structure

Chewie-NS structure

Components

Containers

  • Don't have state
  • Have state
import React from "react";
import classes from "./Markdown.module.css";

import ReactMarkdown from "react-markdown";

// Defines an image renderer
const imageRenderer = (props) => {
  return <img className={classes.photo} alt={props.alt} src={props.src} />;
};

// Defines a link renderer
const linkRenderer = (props) => {
  return (
    <a href={props.href} target="_blank" rel="noopener noreferrer">
      {props.children}
    </a>
  );
};

// Save the renderers in an object
const renderers = {
  image: imageRenderer,
  link: linkRenderer,
};

// Define the Markdwon component
const markdown = (props) => {
  return (
    <div className={classes.App}>
      <ReactMarkdown source={props.markdown} renderers={ renderers } />
    </div>
  );
};

export default markdown;

Chewie-NS structure

Components

Containers

  • Don't have state
  • Have state
class Sequences extends Component {
  state = {
    seq: "",
  };

  onSubmitHandler = (event) => {
    event.preventDefault();

    this.props.onFetchSequence(this.state.seq);
  };

  onClickClear = () => {
    this.setState({
      seq: "",
    });
  };

  render() {
    const { classes } = this.props;

    let sequenceTable = <MUIDataTable />;

    let errorMessage = null;

    if (this.props.error) {
      errorMessage = <p>{this.props.error.message}</p>;
    }

    if (!this.props.loading) {
      let seqData = this.props.sequence_data;

      sequenceTable = (
        <MuiThemeProvider theme={this.getMuiTheme()}>
          <MUIDataTable
            title={"Results"}
            data={seqData}
            columns={SEQUENCES_COLUMNS}
            options={SEQUENCES_OPTIONS}
          />
        </MuiThemeProvider>
      );
    }

    return (
      <div id="homeDiv">
        <div id="titleDiv">
          <h1 style={{ textAlign: "center" }}>Allele Search</h1>
        </div>
        <div id="TextAreaDiv">
          <Container component="main" maxWidth="xs">
            <CssBaseline />
            <div className={classes.paper}>
              <form
                className={classes.root}
                onSubmit={(e) => this.onSubmitHandler(e)}
                noValidate
                autoComplete="off"
              >
                <div>
                  <TextField
                    id="outlined-textarea"
                    label="Alelle Sequence or Hash"
                    placeholder="DNA Sequence or Hash"
                    rows={4}
                    multiline
                    variant="outlined"
                    onInput={(e) => this.setState({ seq: e.target.value })}
                  />
                  <Button
                    type="submit"
                    fullWidth
                    variant="contained"
                    className={classes.submit}
                    classes={{
                      root: classes.buttonRoot,
                    }}
                  >
                    SEARCH
                  </Button>
                </div>
              </form>
            </div>
          </Container>
        </div>
        <div>{errorMessage}</div>
        <div>{sequenceTable}</div>
        <Copyright />
      </div>
    );
  }
}

Chewie-NS structure

Frontpage images

Chewie-NS structure

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <meta name="theme-color" content="#000000">
    <!--
      manifest.json provides metadata used when your web app is added to the
      homescreen on Android. See https://developers.google.com/web/fundamentals/engage-and-retain/web-app-manifest/
    -->
    <link rel="manifest" href="%PUBLIC_URL%/manifest.json">
    <!--
      Notice the use of %PUBLIC_URL% in the tags above.
      It will be replaced with the URL of the `public` folder during the build.
      Only files inside the `public` folder can be referenced from the HTML.

      Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
      work correctly both with client-side routing and a non-root public URL.
      Learn how to configure a non-root public URL by running `npm run build`.
    -->
    <title>Chewie Nomenclature Server</title>
  </head>
  <body style="background-color: #f2f2f2">
    <noscript>
      You need to enable JavaScript to run this app.
    </noscript>
    <div id="root"></div>
    <!--
      This HTML file is a template.
      If you open it directly in the browser, you will see an empty page.

      You can add webfonts, meta tags, or analytics to this file.
      The build step will place the bundled scripts into the <body> tag.

      To begin the development, run `npm start` or `yarn start`.
      To create a production bundle, use `npm run build` or `yarn build`.
    -->
  </body>
</html>

Add new columns to tables

import React from "react";
import { Link as RouterLink } from "react-router-dom";

// Chewie local imports
import Aux from "../../../hoc/Aux/Aux";

// Material UI imports
import Button from "@material-ui/core/Button";
import CircularProgress from "@material-ui/core/CircularProgress";

export const STATS_COLUMNS = [
  {
    name: "species_id",
    label: "Species ID",
    options: {
      filter: false,
      sort: true,
      display: true,
      setCellHeaderProps: (value) => {
        return {
          style: {
            fontWeight: "bold",
          },
        };
      },
    },
  },
  {
    name: "species_name",
    label: "Species",
    options: {
      filter: true,
      setCellHeaderProps: (value) => {
        return {
          style: {
            fontWeight: "bold",
          },
        };
      },
      customBodyRender: (value, tableMeta, updateValue) => (
        <Aux>
          <i>{value}</i>
        </Aux>
      ),
    },
  },
  {
    name: "nr_schemas",
    label: "No. Schemas available",
    options: {
      filter: true,
      setCellHeaderProps: (value) => {
        return {
          style: {
            fontWeight: "bold",
          },
        };
      },
    },
  },
  {
    name: "Schemas Details",
    options: {
      filter: false,
      empty: true,
      setCellHeaderProps: (value) => {
        return {
          style: {
            fontWeight: "bold",
          },
        };
      },
      customBodyRender: (value, tableMeta, updateValue) => {
        return (
          <Button
            variant="contained"
            color="default"
            component={RouterLink}
            to={"/species/" + tableMeta.rowData[0]}
            onClick={() => console.log(tableMeta.rowData[0])}
          >
            Schema Details
          </Button>
        );
      },
    },
  },
];

export const STATS_OPTIONS = {
  textLabels: {
    body: {
      noMatch: <CircularProgress />,
    },
  },
  responsive: "scrollMaxHeight",
  selectableRowsHeader: false,
  selectableRows: "none",
  selectableRowsOnClick: false,
  print: false,
  download: false,
  search: false,
  filter: false,
  viewColumns: false,
};

Change table options

import React from "react";
import { Link as RouterLink } from "react-router-dom";

// Chewie local imports
import Aux from "../../../hoc/Aux/Aux";

// Material UI imports
import Button from "@material-ui/core/Button";
import CircularProgress from "@material-ui/core/CircularProgress";

export const STATS_COLUMNS = [
  {
    name: "species_id",
    label: "Species ID",
    options: {
      filter: false,
      sort: true,
      display: true,
      setCellHeaderProps: (value) => {
        return {
          style: {
            fontWeight: "bold",
          },
        };
      },
    },
  },
  {
    name: "species_name",
    label: "Species",
    options: {
      filter: true,
      setCellHeaderProps: (value) => {
        return {
          style: {
            fontWeight: "bold",
          },
        };
      },
      customBodyRender: (value, tableMeta, updateValue) => (
        <Aux>
          <i>{value}</i>
        </Aux>
      ),
    },
  },
  {
    name: "nr_schemas",
    label: "No. Schemas available",
    options: {
      filter: true,
      setCellHeaderProps: (value) => {
        return {
          style: {
            fontWeight: "bold",
          },
        };
      },
    },
  },
  {
    name: "Schemas Details",
    options: {
      filter: false,
      empty: true,
      setCellHeaderProps: (value) => {
        return {
          style: {
            fontWeight: "bold",
          },
        };
      },
      customBodyRender: (value, tableMeta, updateValue) => {
        return (
          <Button
            variant="contained"
            color="default"
            component={RouterLink}
            to={"/species/" + tableMeta.rowData[0]}
            onClick={() => console.log(tableMeta.rowData[0])}
          >
            Schema Details
          </Button>
        );
      },
    },
  },
];

export const STATS_OPTIONS = {
  textLabels: {
    body: {
      noMatch: <CircularProgress />,
    },
  },
  responsive: "scrollMaxHeight",
  selectableRowsHeader: false,
  selectableRows: "none",
  selectableRowsOnClick: false,
  print: false,
  download: false,
  search: false,
  filter: false,
  viewColumns: false,
};

Chewie-NS structure

Higher-order components

<Aux>
        <div id="schemasAvailable" style={{ float: "right" }}>
          <Button
            variant="contained"
            color="default"
            component={Link}
            to="/stats"
          >
            Back to Available Schemas
          </Button>
        </div>
        <div style={{ marginLeft: "5%", marginRight: "5%" }}>
          <div>
            <h1 style={{ textAlign: "center" }}>Schemas Overview</h1>
          </div>
          <div style={{ marginTop: "40px" }}>{species}</div>
          <div style={{ marginTop: "40px", marginBottom: "40px" }}>
            {species_plot}
          </div>
          <Copyright />
        </div>
      </Aux>

New page example

import React, { Component } from 'react';

// more imports


// define constants need below
// like styles

class Example extends Component {
  state = {
    data: "",
  }; 

  // lifecycle methods
  // such as componentDidMount
  // this method is used to 
  // perform API requests
  componentDidMount() {
    this.props.onFetchSpeciesStats();
  }

  render() {
    
    // define components one-by-one
    // apply some simple logic 
  
    return <div>Chewie says Aarrr wwgggh waah</div>;
  }
}

export default Example;

Add new page to App.js

import React, { Component } from "react";
import {
  Route,
  Switch,
  withRouter,
  Redirect,
  Link,
  useRouteMatch,
} from "react-router-dom";
import { connect } from "react-redux";

// Chewie local components imports
import Aux from "./hoc/Aux/Aux";
import Stats from "./containers/Stats/Stats";
import About from "./containers/About/About";
import Locus from "./containers/Locus/Locus";
import Chewie from "./containers/Chewie/Chewie";
import Schema from "./containers/Schema/Schema";
import Logout from "./containers/Auth/Logout/Logout";
import Species from "./containers/Species/Species";
import MuiLogin from "./containers/Auth/MuiLogin/MuiLogin";
import Sequences from "./containers/Sequences/Sequences";
import MuiRegister from "./containers/Auth/MuiRegister/MuiRegister";
import Profile from "./containers/Auth/Profile/Profile";
import MuiSideDrawer from "./components/Navigation/MuiSideDrawer/MuiSideDrawer";
import Example from "./containers/Example/Example";
import * as actions from "./store/actions/index";

// Material Ui Components
import MuiLink from "@material-ui/core/Link";
import Breadcrumbs from "@material-ui/core/Breadcrumbs";

// Determine URL matches for Breadcrumb generation
function SimpleBreadcrumbs() {
  const aboutMatches = useRouteMatch("/about");
  const statsMatches = useRouteMatch("/stats");
  const sequencesMatches = useRouteMatch("/sequences");
  const speciesIdMatches = useRouteMatch("/species/:species_id");
  const schemasIdMatches = useRouteMatch(
    "/species/:species_id/schemas/:schema_id"
  );
  const locusIdMatches = useRouteMatch(
    "/species/:species_id/schemas/:schema_id/locus/:locus_id"
  );

  const styles = {
    breadcrumb: {
      color: "#bb7944",
    },
  };

  const spd = JSON.parse(localStorage.getItem("speciesD"));
  const schemaName = localStorage.getItem("schemaName");

  // Breadcrumbs generation
  return (
    <>
      <Breadcrumbs aria-label="breadcrumb">
        {aboutMatches && (
          <div>
            <MuiLink component={Link} to="/about" style={styles.breadcrumb}>
              About
            </MuiLink>
          </div>
        )}
        {sequencesMatches && (
          <MuiLink component={Link} to="/sequences" style={styles.breadcrumb}>
            Search
          </MuiLink>
        )}
        {statsMatches && (
          <MuiLink component={Link} to="/stats" style={styles.breadcrumb}>
            Schemas
          </MuiLink>
        )}
        {speciesIdMatches && (
          <MuiLink
            component={Link}
            to={`/species/${speciesIdMatches.params.species_id}`}
            style={styles.breadcrumb}
          >
            <i>{spd[speciesIdMatches.params.species_id]}</i>
          </MuiLink>
        )}
        {schemasIdMatches && (
          <MuiLink
            component={Link}
            to={`/species/${schemasIdMatches.params.species_id}/schemas/${schemasIdMatches.params.schema_id}`}
            style={styles.breadcrumb}
          >
            {schemaName}
          </MuiLink>
        )}
        {locusIdMatches && (
          <MuiLink
            component={Link}
            to={`/schemas/${locusIdMatches.params.schema_id}/locus/${locusIdMatches.params.locus_id}`}
            style={styles.breadcrumb}
          >
            Locus {locusIdMatches.params.locus_id}
          </MuiLink>
        )}
      </Breadcrumbs>
    </>
  );
}

class App extends Component {
  componentDidMount() {
    this.props.onTryAutoSignup();
  }

  render() {
    // Define common app routes
    let routes = (
      <Aux>
        <SimpleBreadcrumbs />
        <Switch>
          <Route path="/auth" component={MuiLogin} />
          <Route path="/register" component={MuiRegister} />
          <Route path="/about" component={About} />
		  <Route path="/example" component={Example} />
          <Route path="/" exact component={Chewie} />
          <Route path="/stats" component={Stats} />
          <Route path="/sequences" component={Sequences} />
          <Route
            path="/species/:species_id/schemas/:schema_id/locus/:locus_id"
            component={Locus}
          />
          <Route
            path="/species/:species_id/schemas/:schema_id"
            component={Schema}
          />
          <Route path="/species/:species_id" component={Species} />
          <Redirect to="/" />
        </Switch>
      </Aux>
    );

    if (this.props.isAuthenticated) {
      // Defines user authenticated routes
      routes = (
        <Switch>
          <Route path="/logout" component={Logout} />
          <Route path="/profile" component={Profile} />
          <Route path="/about" component={About} />
          <Route path="/stats" component={Stats} />
          <Route path="/sequences" component={Sequences} />
          <Route path="/" exact component={Chewie} />
          <Route
            path="/species/:species_id/schemas/:schema_id/locus/:locus_id"
            component={Locus}
          />
          <Route
            path="/species/:species_id/schemas/:schema_id"
            component={Schema}
          />
          <Route path="/species/:species_id" component={Species} />
          <Redirect to="/" />
        </Switch>
      );
    }

    return (
      <div>
        <MuiSideDrawer>{routes}</MuiSideDrawer>
      </div>
    );
  }
}

const mapStateToProps = (state) => {
  return {
    isAuthenticated: state.auth.token !== null,
  };
};

const mapDispatchToProps = (dispatch) => {
  return {
    onTryAutoSignup: () => dispatch(actions.authCheckState()),
  };
};

export default withRouter(connect(mapStateToProps, mapDispatchToProps)(App));

Reflect changes on NS

# stop react and nginx service
sudo docker-compose -f docker-compose-production.yaml stop nginx_react

# make changes locally or by git pull

# restart the service

sudo docker-compose -f docker-compose-production.yaml up -d --no-deps --build

Redux Concept

Action Creator

Action

dispatch

Reducer

State

import React, { Component } from 'react';

// more imports


// define constants need below
// like styles

class Example extends Component {
  state = {
    data: "",
  }; 

  // lifecycle methods
  // such as componentDidMount
  // this method is used to 
  // perform API requests
   componentDidMount() {
     this.props.onFetchSpeciesStats();
   }

  render() {
    
    // define components one-by-one
    // apply some simple logic 
  
    return <div>Chewie says Aarrr wwgggh waah</div>;
  }
}

// Redux functions

// Map state from the central warehouse
// to the props of this component
 const mapStateToProps = (state) => {
     return {
       stats: state.stats.stats,
       loading: state.stats.loading,
       error: state.stats.error,
     };
   };
  
   // Map dispatch functions that trigger
   // actions from redux 
   // to the props of this component
   const mapDispatchToProps = (dispatch) => {
     return {
       onFetchStats: () => dispatch(actions.fetchStats()),
       onFetchSpeciesStats: () => dispatch(actions.fetchStatsSpecies()),
     };
   };
  
export default connect(
     mapStateToProps,
     mapDispatchToProps
   )(withErrorHandler(Stats, axios));

// export default Example;

Chewie-NS frontend

By Pedro Cerqueira

Chewie-NS frontend

  • 193