# HG changeset patch # User Frank Bessou <frank.bessou@logilab.fr> # Date 1613138587 -3600 # Fri Feb 12 15:03:07 2021 +0100 # Node ID ac514970ebfc66aef1b5ba701b38f254daeadae5 # Parent e8e24874379a4cf093334b005a10476a89729964 feat: demo diff --git a/packages/demo/App.js b/packages/demo/App.js deleted file mode 100644 --- a/packages/demo/App.js +++ /dev/null @@ -1,116 +0,0 @@ -import * as React from "react"; -import { - Admin, - Resource, - ListGuesser, - List, - Datagrid, - TextField, - DateField, - NumberField, - ShowGuesser, - EditGuesser, - Edit, - SimpleForm, - TextInput, - NumberInput, - DateInput, -} from "react-admin"; -//import jsonServerProvider from 'ra-data-json-server'; - -// import authProvider from './authProvider'; -// see https://marmelab.com/react-admin/Authentication.html -const authProvider = { - // authentication - login: params => Promise.resolve(), - checkError: error => Promise.resolve(), - checkAuth: params => Promise.resolve(), - logout: () => Promise.resolve(), - getIdentity: () => Promise.resolve(), - // authorization - getPermissions: params => Promise.resolve(), -}; - -// const dataProvider = jsonServerProvider('https://jsonplaceholder.typicode.com'); -// https://marmelab.com/react-admin/DataProviders.html > Request Format - -const dataProvider = { - getList: (resource, params) => { - return fetch( - "https://www.logilab.fr/view?rql=Any+C+LIMIT+10+WHERE+C+is+Card&vid=ejsonexport", - ) - .then(resp => resp.json()) - .then(json => { - return { - data: json.map(item => ({...item, id: item.eid})), - total: json.length, - }; - }); - }, - getOne: (resource, params) => { - return fetch( - "https://www.logilab.fr/view?rql=Any+C+LIMIT+1+WHERE+C+is+Card&vid=ejsonexport", - ) - .then(resp => resp.json()) - .then(json => { - return { - data: json.map(item => ({...item, id: item.eid}))[0], - }; - }); - }, - getMany: (resource, params) => ({}), // NotImplemented - getManyReference: (resource, params) => ({}), // NotImplemented - update: (resource, params) => ({}), // NotImplemented - updateMany: (resource, params) => ({}), // NotImplemented - create: (resource, params) => ({}), // NotImplemented - //delete: (resource, params) => ({}), // NotImplemented - deleteMany: (resource, params) => ({}), // NotImplemented -}; - -export const UserEdit = props => ( - <Edit {...props}> - <SimpleForm> - <TextInput source="cw_etype" /> - <NumberInput source="eid" /> - <TextInput source="title" /> - <TextInput source="synopsis" /> - <TextInput source="content" /> - <TextInput source="content_format" /> - <TextInput source="wikiid" /> - <DateInput source="creation_date" /> - <TextInput source="cwuri" /> - <DateInput source="modification_date" /> - <TextInput source="id" /> - </SimpleForm> - </Edit> -); - -export const UserList = props => ( - <List {...props}> - <Datagrid rowClick="edit"> - <NumberField source="id" /> - <TextField source="cw_etype" /> - <TextField source="title" /> - <TextField source="synopsis" /> - <TextField source="content" /> - <TextField source="content_format" /> - <TextField source="wikiid" /> - <DateField source="creation_date" /> - <TextField source="cwuri" /> - <DateField source="modification_date" /> - </Datagrid> - </List> -); - -const App = () => ( - <Admin dataProvider={dataProvider}> - { - // authProvider={authProvider} - // use cwclientlibjs to get list of CWETypes - // and add one <Resource per type - } - <Resource name="users" list={UserList} show={ShowGuesser} edit={UserEdit} /> - </Admin> -); - -export default App; diff --git a/packages/demo/index.js b/packages/demo/index.js deleted file mode 100644 --- a/packages/demo/index.js +++ /dev/null @@ -1,11 +0,0 @@ -import React from 'react'; -import ReactDOM from 'react-dom'; -import './index.css'; -import App from './App'; - -ReactDOM.render( - <React.StrictMode> - <App /> - </React.StrictMode>, - document.getElementById('root') -); diff --git a/packages/demo/package.json b/packages/demo/package.json --- a/packages/demo/package.json +++ b/packages/demo/package.json @@ -13,8 +13,8 @@ "react-dom": "^17.0.1" }, "scripts": { - "build": "webpack", - "start": "webpack serve" + "build": "webpack --mode=production", + "start": "webpack serve --mode=development" }, "devDependencies": { "ts-loader": "^8.0.14", diff --git a/packages/demo/src/App.tsx b/packages/demo/src/App.tsx --- a/packages/demo/src/App.tsx +++ b/packages/demo/src/App.tsx @@ -15,53 +15,56 @@ DateInput, EditProps, ListProps, + DataProvider, } from "react-admin"; +import { createDataProvider } from "ra-cubicweb/dist"; -import { dataProvider } from "ra-cubicweb"; +import { schema } from "./schema"; -const UserEdit = (props: EditProps) => ( +const MuseumEdit = (props: EditProps) => ( <Edit {...props}> <SimpleForm> - <TextInput source="cw_etype" /> - <NumberInput source="eid" /> - <TextInput source="title" /> - <TextInput source="synopsis" /> - <TextInput source="content" /> - <TextInput source="content_format" /> - <TextInput source="wikiid" /> - <DateInput source="creation_date" /> - <TextInput source="cwuri" /> - <DateInput source="modification_date" /> - <TextInput source="id" /> + <NumberInput source="id" /> + <TextInput source="name" /> + <NumberInput source="latitude" /> + <NumberInput source="longitude" /> + <TextInput source="postal_address" /> </SimpleForm> </Edit> ); -const UserList = (props: ListProps) => ( +const MuseumList = (props: ListProps) => ( <List {...props}> <Datagrid rowClick="edit"> <NumberField source="id" /> - <TextField source="cw_etype" /> - <TextField source="title" /> - <TextField source="synopsis" /> - <TextField source="content" /> - <TextField source="content_format" /> - <TextField source="wikiid" /> - <DateField source="creation_date" /> - <TextField source="cwuri" /> - <DateField source="modification_date" /> + <TextField source="name" /> + <NumberField source="latitude" /> + <NumberField source="longitude" /> + <TextField source="postal_address" /> </Datagrid> </List> ); +const dataProvider = createDataProvider("http://localhost:8080", schema); +dataProvider.getList("toto", { + pagination: { page: 1, perPage: 10 }, + sort: { field: "id", order: "ASC" }, + filter: null, +}); + const App = (): JSX.Element => ( - <Admin dataProvider={dataProvider}> + <Admin dataProvider={dataProvider as DataProvider}> { // authProvider={authProvider} // use cwclientlibjs to get list of CWETypes // and add one <Resource per type } - <Resource name="users" list={UserList} show={ShowGuesser} edit={UserEdit} /> + <Resource + name="Museum" + list={MuseumList} + show={ShowGuesser} + edit={MuseumEdit} + /> </Admin> ); diff --git a/packages/demo/src/schema.ts b/packages/demo/src/schema.ts new file mode 100644 --- /dev/null +++ b/packages/demo/src/schema.ts @@ -0,0 +1,42 @@ +import { ETypes, Relationships, Schema, yams } from "ra-cubicweb/dist/Schema"; + +function makeSchema<E extends ETypes, R extends Relationships<E>>( + etypes: E, + relationships: R +): Schema<E, R> { + return { + etypes, + relationships, + }; +} + +export const schema = makeSchema( + { + Museum: { + name: yams.String(), + latitude: yams.Float(), + longitude: yams.Float(), + postal_address: yams.String(), + }, + City: { + name: yams.String(), + zip_code: yams.Int(), + }, + Person: { + name: yams.String(), + email: yams.String(), + }, + }, + { + is_in: { + subject: "Museum", + object: "City", + cardinality: "1*", + }, + director: { + subject: "Museum", + object: "Person", + cardinality: "**", + }, + } +); diff --git a/packages/demo/webpack.config.js b/packages/demo/webpack.config.js --- a/packages/demo/webpack.config.js +++ b/packages/demo/webpack.config.js @@ -1,6 +1,9 @@ const webpack = require("webpack"); module.exports = { + entry: { + main: "./src/index.tsx", + }, resolve: { extensions: [".js", ".ts", ".tsx"], }, @@ -17,4 +20,7 @@ "process.env": {}, // react-admin bundles node code }), ], + devServer: { + publicPath: "/dist/", + }, }; diff --git a/packages/ra-cubicweb/package.json b/packages/ra-cubicweb/package.json --- a/packages/ra-cubicweb/package.json +++ b/packages/ra-cubicweb/package.json @@ -5,6 +5,7 @@ "types": "dist/index.d.ts", "license": "MIT", "peerDependencies": { + "@logilab/cwclientlibjs": "^1.1.0", "ra-core": "^3.12.0" }, "scripts": { @@ -14,5 +15,8 @@ "@types/node": "^14.14.22", "@types/react": "^17.0.0", "ra-core": "^3.12.0" + }, + "dependencies": { + "@logilab/cwclientlibjs": "^1.1.0" } } diff --git a/packages/ra-cubicweb/src/Schema.ts b/packages/ra-cubicweb/src/Schema.ts new file mode 100644 --- /dev/null +++ b/packages/ra-cubicweb/src/Schema.ts @@ -0,0 +1,35 @@ +export const yams = { + String: () => ({ type: "String" }), + Date: () => ({ type: "Date" }), + Float: () => ({ type: "Float" }), + Int: () => ({ type: "Int" }), +} as const; + +export type BuildObj = ReturnType<typeof yams[keyof typeof yams]>; + +export type Cardinality = "*" | "1" | "?" | "+"; + +export type CardinalityPair = `${Cardinality}${Cardinality}`; + +export type Relationships<E> = Record< + string, + { + cardinality: CardinalityPair; + subject: keyof E; + object: keyof E; + } +>; + +export type ETypes = { + [etypeName: string]: { + [attributeName: string]: BuildObj; + }; +}; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export type Schema<E extends ETypes = any, R extends Relationships<E> = any> = { + etypes: E; + relationships: R; +}; + +export type ETypesNames<S extends Schema> = keyof S["etypes"]; diff --git a/packages/ra-cubicweb/src/index.ts b/packages/ra-cubicweb/src/index.ts --- a/packages/ra-cubicweb/src/index.ts +++ b/packages/ra-cubicweb/src/index.ts @@ -1,4 +1,6 @@ +import { client } from "@logilab/cwclientlibjs"; import { AuthProvider, DataProvider } from "ra-core"; +import { Schema, ETypesNames } from "./Schema"; // import authProvider from './authProvider'; // see https://marmelab.com/react-admin/Authentication.html @@ -17,38 +19,67 @@ eid: string; } -export const dataProvider: DataProvider = { - getList: (_resource, _params) => { - return fetch( - "https://www.logilab.fr/view?rql=Any+C+LIMIT+10+WHERE+C+is+Card&vid=ejsonexport" - ) - .then((resp) => resp.json()) - .then((json) => { - return { - data: json.map((item: Entity) => ({ ...item, id: item.eid })), - total: json.length, - }; +export function createDataProvider<S extends Schema>( + endpoint: string, + schema: S +): DataProvider { + const httpClient = new client.CwSimpleHttpClient(endpoint, true); + const rqlClient = new client.CwRqlClient(httpClient); + return { + getList: async (resource: ETypesNames<S>, { pagination }) => { + const attributesNames = ["eid", ...Object.keys(schema.etypes[resource])]; + const selection: string[] = []; + const restrictions: string[] = []; + attributesNames.forEach((key, idx) => { + const variable = `X${idx}`; + selection.push(variable); + restrictions.push(`X ${key} ${variable}`); }); - }, - getOne: (_resource, _params) => { - return fetch( - "https://www.logilab.fr/view?rql=Any+C+LIMIT+1+WHERE+C+is+Card&vid=ejsonexport" - ) - .then((resp) => resp.json()) - .then((json) => { - return { - data: json.map((entity: Entity) => ({ - ...entity, - id: entity.eid, - }))[0], - }; - }); - }, - getMany: (_resource, _params) => Promise.reject("Not implemented"), - getManyReference: (_resource, _params) => Promise.reject("Not implemented"), - update: (_resource, _params) => Promise.reject("Not implemented"), - updateMany: (_resource, _params) => Promise.reject("Not implemented"), - create: (_resource, _params) => Promise.reject("Not implemented"), - delete: (_resource, _params) => Promise.reject("Not implemented"), - deleteMany: (_resource, _params) => Promise.reject("Not implemented"), -}; + const total = await rqlClient + .queryRows(`Any Count(${selection[0]}) WHERE ${restrictions.join(",")}`) + .then((rows) => rows[0][0]); + return rqlClient + .queryRows( + `Any ${selection.join(", ")} LIMIT ${pagination.perPage} OFFSET ${ + pagination.page * pagination.perPage + } WHERE ${restrictions.join(", ")}`, + {} + ) + .then((rows) => { + return { + data: rows.map((row) => + row.reduce( + (agg, attributeValue, idx) => ({ + [attributesNames[idx]]: attributeValue, + ...agg, + }), + { id: row[0] } + ) + ), + total, + }; + }); + }, + getOne: (resource: keyof S["etypes"], _params) => { + return fetch( + `${endpoint}/view?rql=Any+C+LIMIT+1+WHERE+C+is+${resource}+,+C+eid+${_params.id}&vid=ejsonexport` + ) + .then((resp) => resp.json()) + .then((json) => { + return { + data: json.map((entity: Entity) => ({ + ...entity, + id: entity.eid, + }))[0], + }; + }); + }, + getMany: (_resource, _params) => Promise.reject("Not implemented"), + getManyReference: (_resource, _params) => Promise.reject("Not implemented"), + update: (_resource, _params) => Promise.reject("Not implemented"), + updateMany: (_resource, _params) => Promise.reject("Not implemented"), + create: (_resource, _params) => Promise.reject("Not implemented"), + delete: (_resource, _params) => Promise.reject("Not implemented"), + deleteMany: (_resource, _params) => Promise.reject("Not implemented"), + }; +} diff --git a/yarn.lock b/yarn.lock --- a/yarn.lock +++ b/yarn.lock @@ -75,6 +75,14 @@ "@types/yargs" "^15.0.0" chalk "^4.0.0" +"@logilab/cwclientlibjs@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@logilab/cwclientlibjs/-/cwclientlibjs-1.1.0.tgz#4b64f8b590f1f754e3322f822b9d3ed0a391d889" + integrity sha512-RWznAmHVO8+TEYnAL82YUtXiwEmcGgAh/Zpsl1qDw+uXBa5EhQeUiFwrsNMDqDprm7opMwCsjMXnQcXERcAMCA== + dependencies: + crypto-js "^3.1.9-1" + isomorphic-fetch "^2.2.1" + "@material-ui/core@^4.11.2": version "4.11.3" resolved "https://registry.yarnpkg.com/@material-ui/core/-/core-4.11.3.tgz#f22e41775b0bd075e36a7a093d43951bf7f63850" @@ -1170,6 +1178,11 @@ shebang-command "^2.0.0" which "^2.0.1" +crypto-js@^3.1.9-1: + version "3.3.0" + resolved "https://registry.yarnpkg.com/crypto-js/-/crypto-js-3.3.0.tgz#846dd1cce2f68aacfa156c8578f926a609b7976b" + integrity sha512-DIT51nX0dCfKltpRiXV+/TVZq+Qq2NgF4644+K7Ttnla7zEzqc+kjJyiB96BHNyUTBxyjzRcZYpUdZa+QAqi6Q== + css-mediaquery@^0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/css-mediaquery/-/css-mediaquery-0.1.2.tgz#6a2c37344928618631c54bd33cedd301da18bea0" @@ -1403,6 +1416,13 @@ resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k= +encoding@^0.1.11: + version "0.1.13" + resolved "https://registry.yarnpkg.com/encoding/-/encoding-0.1.13.tgz#56574afdd791f54a8e9b2785c0582a2d26210fa9" + integrity sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A== + dependencies: + iconv-lite "^0.6.2" + end-of-stream@^1.1.0: version "1.4.4" resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" @@ -2216,6 +2236,13 @@ dependencies: safer-buffer ">= 2.1.2 < 3" +iconv-lite@^0.6.2: + version "0.6.2" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.2.tgz#ce13d1875b0c3a674bd6a04b7f76b01b1b6ded01" + integrity sha512-2y91h5OpQlolefMPmUlivelittSWy0rP+oYVpn6A7GwVHNE8AWzoYOBNmlwks3LobaJxgHCYZAnyNo2GgpNRNQ== + dependencies: + safer-buffer ">= 2.1.2 < 3.0.0" + ignore@^4.0.6: version "4.0.6" resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc" @@ -2497,7 +2524,7 @@ call-bind "^1.0.2" has-symbols "^1.0.1" -is-stream@^1.1.0: +is-stream@^1.0.1, is-stream@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ= @@ -2551,6 +2578,14 @@ resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" integrity sha1-TkMekrEalzFjaqH5yNHMvP2reN8= +isomorphic-fetch@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz#611ae1acf14f5e81f729507472819fe9733558a9" + integrity sha1-YRrhrPFPXoH3KVB0coGf6XM1WKk= + dependencies: + node-fetch "^1.0.1" + whatwg-fetch ">=0.10.0" + jest-worker@^26.6.2: version "26.6.2" resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-26.6.2.tgz#7f72cbc4d643c365e27b9fd775f9d0eaa9c7a8ed" @@ -2998,6 +3033,14 @@ resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ== +node-fetch@^1.0.1: + version "1.7.3" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-1.7.3.tgz#980f6f72d85211a5347c6b2bc18c5b84c3eb47ef" + integrity sha512-NhZ4CsKx7cYm2vSrBAr2PvFOe6sWDf0UYLRqA6svUYg7+/TSfVAu49jYC4BvQ4Sms9SZgdqGBgroqfDhJdTyKQ== + dependencies: + encoding "^0.1.11" + is-stream "^1.0.1" + node-forge@^0.10.0: version "0.10.0" resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.10.0.tgz#32dea2afb3e9926f02ee5ce8794902691a676bf3" @@ -3852,7 +3895,7 @@ dependencies: ret "~0.1.10" -"safer-buffer@>= 2.1.2 < 3": +"safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0": version "2.1.2" resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== @@ -4723,6 +4766,11 @@ resolved "https://registry.yarnpkg.com/websocket-extensions/-/websocket-extensions-0.1.4.tgz#7f8473bc839dfd87608adb95d7eb075211578a42" integrity sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg== +whatwg-fetch@>=0.10.0: + version "3.5.0" + resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-3.5.0.tgz#605a2cd0a7146e5db141e29d1c62ab84c0c4c868" + integrity sha512-jXkLtsR42xhXg7akoDKvKWE40eJeI+2KZqcp2h3NsOrRnDvtWX36KcKl30dy+hxECivdk2BVUHVNrPtoMBUx6A== + which-module@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a"