Skip to content
Snippets Groups Projects
Commit b6b4256f authored by Erik Reid's avatar Erik Reid
Browse files

initial template

parents
No related branches found
No related tags found
No related merge requests found
setup.py 0 → 100644
from setuptools import setup, find_packages
setup(
name='gap-service-editor',
version="0.1",
author='GEANT',
author_email='swd@geant.org',
description='skeleton flask react app',
url=('TBD'),
packages=find_packages(),
install_requires=[
'jsonschema',
'flask',
'flask-cors'
],
include_package_data=True,
)
import json
import os
import tempfile
import pytest
import gap_service_editor
@pytest.fixture
def data_config_filename():
test_config_data = {
'str-param': 'test data string',
'int-param': -47
}
with tempfile.NamedTemporaryFile() as f:
f.write(json.dumps(test_config_data).encode('utf-8'))
f.flush()
yield f.name
@pytest.fixture
def client(data_config_filename):
os.environ['SETTINGS_FILENAME'] = data_config_filename
with gap_service_editor.create_app().test_client() as c:
yield c
import json
import jsonschema
import pytest
from gap_service_editor.routes.default import VERSION_SCHEMA
from gap_service_editor.routes.api import THING_LIST_SCHEMA
@pytest.mark.parametrize(
'endpoint',
['version', 'api/things'])
def test_bad_accept(endpoint, client):
rv = client.post(
endpoint,
headers={'Accept': ['text/html']})
assert rv.status_code == 406
def test_version_request(client):
rv = client.post(
'version',
headers={'Accept': ['application/json']})
assert rv.status_code == 200
result = json.loads(rv.data.decode('utf-8'))
jsonschema.validate(result, VERSION_SCHEMA)
def test_things(client):
rv = client.post(
'api/things',
headers={'Accept': ['application/json']})
assert rv.status_code == 200
result = json.loads(rv.data.decode('utf-8'))
jsonschema.validate(result, THING_LIST_SCHEMA)
tox.ini 0 → 100644
[tox]
envlist = py36
[flake8]
exclude = ./.tox,./webapp
[testenv]
deps =
coverage
flake8
-r requirements.txt
commands =
coverage erase
coverage run --source gap_service_editor -m py.test {posargs}
coverage xml
coverage html
coverage report --fail-under 85
flake8
sphinx-build -M html docs/source docs/build
{
"presets": [
"@babel/preset-env",
"@babel/preset-react",
"@babel/preset-typescript"
],
"plugins": [
[
"@babel/plugin-transform-runtime",
{
"regenerator": true
}
],
"@babel/plugin-proposal-class-properties"
]
}
**/*.css
**/*.scss
{
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaVersion": 2018,
"sourceType": "module"
},
"plugins": [
"@typescript-eslint",
"react-hooks"
],
"extends": [
"plugin:react/recommended",
"plugin:@typescript-eslint/recommended"
],
"rules": {
"react-hooks/rules-of-hooks": "error",
"react-hooks/exhaustive-deps": "warn",
"react/prop-types": "off",
"@typescript-eslint/no-unused-vars": [
"warn", { "argsIgnorePattern": "^_" }
]
},
"settings": {
"react": {
"pragma": "React",
"version": "detect"
}
}
}
\ No newline at end of file
# Working with the Web App
## development environment
From this folder, run:
```bash
$ npm init
```
To run the webpack development server:
```bash
$ npm run start
```
Note that you should run the Flask application separately
and append `?test=1` to the dev url that launches in the browser.
## Releasing
To build a new bundle, run:
```bash
$ npm run build
```
This will build the new bundle and deploy it to
`gap_service_editor/static/*`. This should be committed.
This diff is collapsed.
{
"name": "gap-service-editor",
"version": "0.0.1",
"devDependencies": {
"@babel/core": "^7.13.10",
"@babel/plugin-transform-runtime": "^7.13.10",
"@babel/preset-env": "^7.13.10",
"@babel/preset-react": "^7.16.0",
"@babel/preset-typescript": "^7.13.0",
"@babel/runtime": "^7.13.10",
"@types/fork-ts-checker-webpack-plugin": "^0.4.5",
"@types/react": "^17.0.37",
"@types/react-dom": "^17.0.11",
"@types/webpack": "^4.41.26",
"@types/webpack-dev-server": "^3.11.2",
"@typescript-eslint/eslint-plugin": "^4.17.0",
"@typescript-eslint/parser": "^4.17.0",
"babel-loader": "^8.2.2",
"babel-plugin-transform-class-properties": "^6.24.1",
"css-loader": "^5.1.2",
"date-fns": "^2.26.0",
"eslint": "^7.22.0",
"eslint-plugin-react": "^7.27.1",
"eslint-plugin-react-hooks": "^4.3.0",
"fork-ts-checker-webpack-plugin": "^6.1.1",
"sass": "^1.32.8",
"sass-loader": "^11.0.1",
"style-loader": "^2.0.0",
"ts-node": "^9.1.1",
"typescript": "^4.2.3",
"webpack": "^5.25.0",
"webpack-cli": "^4.5.0",
"webpack-dev-server": "^3.11.2"
},
"scripts": {
"start": "webpack serve --mode development --open",
"build": "webpack --mode production"
},
"dependencies": {
"@types/react-bootstrap-table-next": "^4.0.16",
"bootstrap": "^4.6.0",
"install": "^0.13.0",
"npm": "^7.6.3",
"react-bootstrap": "^2.0.2",
"react-bootstrap-table-next": "^4.0.3"
}
}
import React, {useEffect} from 'react';
import BootstrapTable from 'react-bootstrap-table-next';
import { formatDistance } from 'date-fns';
import 'react-bootstrap-table-next/dist/react-bootstrap-table2.min.css';
import "./styles.scss"
interface Thing {
id: string;
data1: string;
data2: string;
data3: string;
state: boolean;
time: number;
}
export interface IAppProps {
refresh_frequency_ms?: number;
api_url?: string;
}
function timeFormatter(time: number, _thing: Thing): string {
return formatDistance(new Date(1000 * time), new Date());
}
const columns = [
{
dataField: 'state',
text: 'State'
},
{
dataField: 'data1',
text: 'Data 1'
// title: columnTitle
}, {
dataField: 'data2',
text: 'Data 2'
}, {
dataField: 'data3',
text: 'Data 3',
// classes: severityClasses,
// formatter: severityFormatter
}, {
dataField: 'time',
text: 'When',
formatter: timeFormatter
}
];
const ThingRow = (t: Thing): Thing => {
return {
id: t.id,
time: t.time,
state: t.state,
data1: t.data1,
data2: t.data2,
data3: t.data3
}
}
const rowClass = (row: Thing, _rowIndex: number): string => {
if (row.state)
return 'state_true';
else
return 'state_false';
}
const Things: React.FC<IAppProps> = (props) => {
const [rows, setRows] = React.useState<Thing[]>([]);
console.log('re-rendering Services')
useEffect(() => {
async function loadThings() {
console.log('starting loadThings(), source: "' + props.api_url + '"');
// api_uri shouldn't be undefined, since
// there's a default value - but TypeScript can't see
// this, apparently (?)
if (props.api_url) {
const response = await fetch(props.api_url);
const rsp_json = await response.json();
// console.log(JSON.stringify(rsp_json))
setRows(rsp_json.map((t: Thing) => ThingRow(t)));
console.log(
"loaded "
+ rsp_json.length.toString()
+ " from '" + props.api_url + "'");
} else {
console.log("props.service_list_api_uri is null: " + props.api_url);
}
}
loadThings();
setInterval(loadThings, props.refresh_frequency_ms);
}, [props.api_url, props.refresh_frequency_ms]);
const thing_comparator = (t1: Thing, t2: Thing): number => {
// sort by id ... arbitrary, as an example
return t1.id.localeCompare(t2.id);
}
return <BootstrapTable
keyField='key'
data={rows.sort(thing_comparator)}
columns={columns}
rowClasses={rowClass}
/>
}
Things.defaultProps = {
refresh_frequency_ms: 600000, // 10 minutes
api_url: window.location.origin + '/api/things'
};
export default Things;
import React from "react";
import ReactDOM from "react-dom";
import Things from "./Things"
const query = new URLSearchParams(window.location.search);
if (query.has('test'))
ReactDOM.render(
<Things
refresh_frequency_ms={5000}
api_url={'http://localhost:33333/api/things'} />,
document.getElementById("root") as HTMLElement);
else
ReactDOM.render(
<Things />,
document.getElementById("root") as HTMLElement);
\ No newline at end of file
table {
min-width: 650;
}
thead {
background-color: lightgray;
}
.state_true {
background-color: lightgreen;
}
.state_false {
background-color: red;
}
{
"compilerOptions": {
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"allowSyntheticDefaultImports": true,
"skipLibCheck": true,
"esModuleInterop": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react"
},
"include": ["src"]
}
import path from "path";
import webpack from "webpack";
import ForkTsCheckerWebpackPlugin from 'fork-ts-checker-webpack-plugin';
const config: webpack.Configuration = {
entry: "./src/index.tsx",
module: {
rules: [
{
test: /\.(ts|js)x?$/,
exclude: /node_modules/,
use: {
loader: "babel-loader",
options: {
presets: [
"@babel/preset-env",
"@babel/preset-react",
"@babel/preset-typescript",
],
},
},
},
{
test: /\.scss$/,
exclude: /node_modules/,
use: [
{ loader: 'style-loader' },
{ loader: 'css-loader' },
{ loader: 'sass-loader' },
]
},
{
test: /\.css$/,
use: [
{ loader: 'style-loader' },
{ loader: 'css-loader' }
]
},
],
},
resolve: {
extensions: [".tsx", ".ts", ".js"],
},
output: {
path: path.resolve(__dirname, "..", "gap_service_editor", "static"),
filename: "bundle.js",
},
devServer: {
contentBase: path.join(__dirname, "..", "gap_service_editor", "static"),
compress: true,
port: 4000,
},
plugins: [
new ForkTsCheckerWebpackPlugin({
async: false,
eslint: {
files: "./src/**/*",
},
}),
],
};
export default config;
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment