Skip to content

Commit 1f1f297

Browse files
Demo app (#81)
* base react demo app * improve eslint configuration * demo app logic * linter fixes * combine demo and docs in GH pages website * add demo app to CI builds * adjust test timeout
1 parent e5bcac4 commit 1f1f297

16 files changed

+388
-6
lines changed

.github/workflows/ci.yml

+12-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ jobs:
2424
- name: Prettier
2525
run: |
2626
npm run prettier
27-
docs:
27+
website:
2828
runs-on: ubuntu-latest
2929
steps:
3030
- uses: actions/checkout@v4
@@ -33,9 +33,20 @@ jobs:
3333
- name: Install
3434
run: |
3535
npm install
36+
- name: Build package
37+
run: |
38+
npm run build
3639
- name: Build documentation
3740
run: |
3841
npm run docs
42+
- name: Build demo application
43+
run: |
44+
cd demo
45+
npm install
46+
npm install .. --install-links
47+
npm run build
48+
cd ..
49+
cp -R docs demo/dist
3950
unit-tests:
4051
runs-on: ${{ matrix.os }}
4152
strategy:

.github/workflows/docs.yml .github/workflows/website.yml

+15-4
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
name: Deploy documentation
1+
name: Deploy website to GitHub Pages
22
on:
33
push:
44
branches:
@@ -12,13 +12,24 @@ jobs:
1212
- uses: actions/checkout@v4
1313
- name: Setup node.js
1414
uses: actions/setup-node@v4
15-
- name: Install
15+
- name: Install library
1616
run: |
1717
npm install
18+
- name: Build package
19+
run: |
20+
npm run build
1821
- name: Build documentation
1922
run: |
2023
npm run docs
21-
- name: Deploy documentation
24+
- name: Build demo application
25+
run: |
26+
cd demo
27+
npm install
28+
npm install .. --install-links
29+
npm run build
30+
cd ..
31+
cp -R docs demo/dist
32+
- name: Deploy website
2233
uses: JamesIves/github-pages-deploy-action@v4
2334
with:
24-
folder: docs
35+
folder: demo/dist

demo/.gitignore

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# Logs
2+
logs
3+
*.log
4+
npm-debug.log*
5+
yarn-debug.log*
6+
yarn-error.log*
7+
pnpm-debug.log*
8+
lerna-debug.log*
9+
10+
node_modules
11+
dist
12+
dist-ssr
13+
*.local
14+
15+
# Editor directories and files
16+
.vscode/*
17+
!.vscode/extensions.json
18+
.idea
19+
.DS_Store
20+
*.suo
21+
*.ntvs*
22+
*.njsproj
23+
*.sln
24+
*.sw?

demo/README.md

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# Request Converter Demo
2+
3+
This is a small React application that demonstrates how to work with the
4+
Request Converter library.

demo/eslint.config.js

+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import js from '@eslint/js'
2+
import globals from 'globals'
3+
import react from 'eslint-plugin-react'
4+
import reactHooks from 'eslint-plugin-react-hooks'
5+
import reactRefresh from 'eslint-plugin-react-refresh'
6+
import tseslint from 'typescript-eslint'
7+
8+
export default tseslint.config(
9+
{ ignores: ['dist'] },
10+
{
11+
settings: { react: { version: '18.3' } },
12+
extends: [js.configs.recommended, ...tseslint.configs.recommendedTypeChecked],
13+
files: ['**/*.{ts,tsx}'],
14+
languageOptions: {
15+
ecmaVersion: 2020,
16+
globals: globals.browser,
17+
parserOptions: {
18+
project: ['./tsconfig.node.json', './tsconfig.app.json'],
19+
tsconfigRootDir: import.meta.dirname,
20+
},
21+
},
22+
plugins: {
23+
react,
24+
'react-hooks': reactHooks,
25+
'react-refresh': reactRefresh,
26+
},
27+
rules: {
28+
...react.configs.recommended.rules,
29+
...react.configs['jsx-runtime'].rules,
30+
...reactHooks.configs.recommended.rules,
31+
'react-refresh/only-export-components': [
32+
'warn',
33+
{ allowConstantExport: true },
34+
],
35+
},
36+
},
37+
)

demo/index.html

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<!doctype html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
6+
<link rel="preconnect" href="https://fonts.googleapis.com">
7+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
8+
<link href="https://fonts.googleapis.com/css2?family=Roboto:ital,wght@0,100..900;1,100..900&display=swap" rel="stylesheet">
9+
<title>Elasticsearch Request Converter Demo</title>
10+
</head>
11+
<body>
12+
<div id="root"></div>
13+
<script type="module" src="/src/main.tsx"></script>
14+
</body>
15+
</html>

demo/package.json

+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
{
2+
"name": "demo",
3+
"private": true,
4+
"version": "0.0.0",
5+
"type": "module",
6+
"scripts": {
7+
"dev": "vite",
8+
"build": "tsc -b && vite build",
9+
"lint": "eslint .",
10+
"preview": "vite preview"
11+
},
12+
"dependencies": {
13+
"@elastic/request-converter": "file:..",
14+
"bootstrap": "^5.3.3",
15+
"compare-versions": "^6.1.1",
16+
"react": "^19.0.0",
17+
"react-bootstrap": "^2.10.9",
18+
"react-dom": "^19.0.0",
19+
"react-syntax-highlighter": "^15.6.1"
20+
},
21+
"devDependencies": {
22+
"@eslint/js": "^9.19.0",
23+
"@types/react": "^19.0.8",
24+
"@types/react-dom": "^19.0.3",
25+
"@types/react-syntax-highlighter": "^15.5.13",
26+
"@vitejs/plugin-react": "^4.3.4",
27+
"eslint": "^9.19.0",
28+
"eslint-plugin-react": "^7.37.4",
29+
"eslint-plugin-react-hooks": "^5.0.0",
30+
"eslint-plugin-react-refresh": "^0.4.18",
31+
"globals": "^15.14.0",
32+
"typescript": "~5.7.2",
33+
"typescript-eslint": "^8.22.0",
34+
"vite": "^6.1.0",
35+
"vite-plugin-node-polyfills": "^0.23.0"
36+
}
37+
}

demo/src/App.tsx

+118
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
import React, { useState, useEffect } from 'react';
2+
import Container from 'react-bootstrap/Container';
3+
import Row from 'react-bootstrap/Row';
4+
import Col from 'react-bootstrap/Col';
5+
import Stack from 'react-bootstrap/Stack';
6+
import Form from 'react-bootstrap/Form';
7+
import SyntaxHighlighter from 'react-syntax-highlighter';
8+
import { atomOneLight } from 'react-syntax-highlighter/dist/esm/styles/hljs';
9+
import { compareVersions } from 'compare-versions';
10+
import { convertRequests, loadSchema, listFormats } from "@elastic/request-converter";
11+
12+
function App() {
13+
const formats = listFormats().sort();
14+
const [versions, setVersions] = useState<string[]>(['main']);
15+
const [schemaVersion, setSchemaVersion] = useState<string>('main');
16+
const [hasSchema, setHasSchema] = useState<boolean>(false);
17+
const [source, setSource] = useState<string>('GET /_search');
18+
const [error, setError] = useState<string | null>(null);
19+
const [code, setCode] = useState<string>('Loading...');
20+
const [language, setLanguage] = useState<string>(formats[0]);
21+
22+
useEffect(() => {
23+
const sourceElem = document.getElementById("source");
24+
if (sourceElem) {
25+
sourceElem.focus();
26+
}
27+
}, []);
28+
29+
useEffect(() => {
30+
void (async () => {
31+
const r = await fetch('https://api.github.com/repos/elastic/elasticsearch-specification/branches');
32+
if (r.status === 200) {
33+
const s: {name: string}[] = (await r.json()) as {name: string}[];
34+
const versions = s.map(v => v.name).filter(v => v.match(/^[0-9]+\.[0-9x]+$/));
35+
setVersions(['main'].concat(versions.sort(compareVersions).reverse()));
36+
}
37+
})();
38+
}, []);
39+
40+
useEffect(() => {
41+
void (async () => {
42+
const response = await fetch(`https://raw.githubusercontent.com/elastic/elasticsearch-specification/refs/heads/${schemaVersion}/output/schema/schema.json`);
43+
if (response.ok) {
44+
setHasSchema(false);
45+
await loadSchema((await response.json()) as object);
46+
setHasSchema(true);
47+
}
48+
})();
49+
}, [schemaVersion]);
50+
51+
useEffect(() => {
52+
void (async () => {
53+
if (hasSchema) {
54+
let c: string = "";
55+
try {
56+
c = await convertRequests(source, language, {complete: true, printResponse: true}) as string;
57+
setError(null);
58+
}
59+
catch (err) {
60+
if (err instanceof Error) {
61+
setError(err.toString());
62+
}
63+
else {
64+
setError('Uknown error');
65+
}
66+
}
67+
if (c) {
68+
setCode(c);
69+
}
70+
}
71+
})();
72+
}, [hasSchema, source, language]);
73+
74+
const onRequestChanged = (ev: React.ChangeEvent<HTMLSelectElement>) => {
75+
setSource(ev.target.value);
76+
ev.target.style.height = ev.target.scrollHeight + "px";
77+
};
78+
79+
return (
80+
<Container id="main-container">
81+
<h1>Elasticsearch Request Converter Demo</h1>
82+
<p><a href="./docs/index.html">Library documentation</a></p>
83+
<Row>
84+
<Col className="col-6">
85+
<Stack direction="horizontal" className="heading">
86+
<p>Source&nbsp;Request</p>
87+
<p className="spacer"></p>
88+
<Form.Select id="version-choice" size="sm" defaultValue="main" onChange={ev => setSchemaVersion(ev.target.value)}>
89+
{versions.map(v => <option key={v} value={v}>{v}</option>)}
90+
</Form.Select>
91+
</Stack>
92+
</Col>
93+
<Col className="col-6">
94+
<Stack direction="horizontal" className="heading">
95+
<p>Converted&nbsp;Code</p>
96+
<p className="spacer"></p>
97+
<Form.Select id="language-choice" size="sm" defaultValue={language} onChange={ev => setLanguage(ev.target.value)}>
98+
{formats.map(fmt => <option key={fmt} value={fmt}>{fmt}</option>)}
99+
</Form.Select>
100+
</Stack>
101+
</Col>
102+
</Row>
103+
<Form id="main-form">
104+
<Row id="main-row">
105+
<Col className="col-6">
106+
<Form.Control className={error ? "is-invalid" : ""} as="textarea" id="source" value={source} onChange={(ev: any) => onRequestChanged(ev)} />
107+
{error && <Form.Control.Feedback type="invalid">{error}</Form.Control.Feedback>}
108+
</Col>
109+
<Col className="col-6">
110+
<SyntaxHighlighter wrapLongLines={true} language={language} style={atomOneLight}>{code}</SyntaxHighlighter>
111+
</Col>
112+
</Row>
113+
</Form>
114+
</Container>
115+
)
116+
}
117+
118+
export default App

demo/src/index.css

+44
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
html, body, #root {
2+
margin-top: 0;
3+
margin-bottom: 0;
4+
height: 100%;
5+
min-height: 100%;
6+
}
7+
8+
body {
9+
color: #444;
10+
}
11+
12+
h1 {
13+
font-size: 2em;
14+
font-weight: 1000;
15+
color: #888;
16+
}
17+
18+
textarea.form-control {
19+
resize: none;
20+
min-height: 200px;
21+
font-family: monospace;
22+
font-size: 14px;
23+
}
24+
25+
.heading {
26+
align-items: baseline;
27+
}
28+
29+
.spacer {
30+
width: 100%;
31+
}
32+
33+
#version-choice, #language-choice {
34+
width: 150px;
35+
}
36+
37+
pre {
38+
margin-top: 10px;
39+
padding: 5px 5px 10px 5px;
40+
overflow: hidden;
41+
white-space: pre-wrap;
42+
}
43+
44+

demo/src/main.tsx

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { StrictMode } from 'react';
2+
import { createRoot } from 'react-dom/client';
3+
import 'bootstrap/dist/css/bootstrap.min.css';
4+
import './index.css';
5+
import App from './App.tsx';
6+
7+
createRoot(document.getElementById('root')!).render(
8+
<StrictMode>
9+
<App />
10+
</StrictMode>,
11+
)

demo/src/vite-env.d.ts

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
/// <reference types="vite/client" />

demo/tsconfig.app.json

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
{
2+
"compilerOptions": {
3+
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
4+
"target": "ES2020",
5+
"useDefineForClassFields": true,
6+
"lib": ["ES2020", "DOM", "DOM.Iterable"],
7+
"module": "ESNext",
8+
"skipLibCheck": true,
9+
10+
/* Bundler mode */
11+
"moduleResolution": "bundler",
12+
"allowImportingTsExtensions": true,
13+
"isolatedModules": true,
14+
"moduleDetection": "force",
15+
"noEmit": true,
16+
"jsx": "react-jsx",
17+
18+
/* Linting */
19+
"strict": true,
20+
"noUnusedLocals": true,
21+
"noUnusedParameters": true,
22+
"noFallthroughCasesInSwitch": true,
23+
"noUncheckedSideEffectImports": true
24+
},
25+
"include": ["src"]
26+
}

demo/tsconfig.json

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"files": [],
3+
"references": [
4+
{ "path": "./tsconfig.app.json" },
5+
{ "path": "./tsconfig.node.json" }
6+
]
7+
}

0 commit comments

Comments
 (0)