diff --git a/.eslintrc b/.eslintrc index 27f11e2..b19ef59 100644 --- a/.eslintrc +++ b/.eslintrc @@ -21,6 +21,7 @@ "rules": { "indent": "off", "no-unused-vars": "off", + "multiline-ternary": "off", "import/no-duplicates": "off", "no-use-before-define": "off", "no-useless-constructor": "off", diff --git a/.github/issue_template.md b/.github/issue_template.md index 9c55441..ae5740c 100644 --- a/.github/issue_template.md +++ b/.github/issue_template.md @@ -1,7 +1,3 @@ # Overview Please replace this line with full information about your idea or problem. If it's a bug share as much as possible to reproduce it - ---- - -Please preserve this line to notify @roll (lead of this repository) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 2fe2111..b181a43 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,7 +1,5 @@ -# Overview - -Please replace this line with full information about your pull request. Make sure that tests pass before publishing it +- fixes # --- -Please preserve this line to notify @roll (lead of this repository) +Please make sure that all the checks pass. Please add here any additional information regarding this pull request. It's highly recommended that you link this PR to an issue (please create one if it doesn't exist for this PR) diff --git a/.github/workflows/general.yaml b/.github/workflows/general.yaml index bf93c36..2de68bf 100644 --- a/.github/workflows/general.yaml +++ b/.github/workflows/general.yaml @@ -9,6 +9,8 @@ on: pull_request: branches: - main + schedule: + - cron: "0 3 * * *" jobs: diff --git a/.github/workflows/project.yaml b/.github/workflows/project.yaml new file mode 100644 index 0000000..f3e905a --- /dev/null +++ b/.github/workflows/project.yaml @@ -0,0 +1,24 @@ +name: project + +on: + issues: + types: + - opened + - reopened + pull_request: + types: + - opened + - reopened + +jobs: + project-assign: + runs-on: ubuntu-latest + steps: + - name: Assign to project + uses: leonsteinhaeuser/project-beta-automations@v1.2.1 + with: + gh_token: ${{ secrets.PROJECT_TOKEN }} + resource_node_id: ${{ github.event_name == 'issues' && github.event.issue.node_id || github.event.pull_request.node_id }} + organization: frictionlessdata + project_id: 16 + status_value: Inbox diff --git a/.husky/.gitignore b/.husky/.gitignore deleted file mode 100644 index 31354ec..0000000 --- a/.husky/.gitignore +++ /dev/null @@ -1 +0,0 @@ -_ diff --git a/.husky/pre-commit b/.husky/pre-commit deleted file mode 100755 index 449fcde..0000000 --- a/.husky/pre-commit +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/sh -. "$(dirname "$0")/_/husky.sh" - -npm test diff --git a/.huskyrc b/.huskyrc deleted file mode 100644 index f0af074..0000000 --- a/.huskyrc +++ /dev/null @@ -1,5 +0,0 @@ -{ - "hooks": { - "pre-commit": "npm run test" - } -} diff --git a/.npmrc b/.npmrc index 80bcbed..eff8d91 100644 --- a/.npmrc +++ b/.npmrc @@ -1 +1,2 @@ +# https://github.com/storybookjs/storybook/issues/12983 legacy-peer-deps = true diff --git a/.storybook/main.js b/.storybook/main.js index 3b66790..2b96ed8 100644 --- a/.storybook/main.js +++ b/.storybook/main.js @@ -1,5 +1,8 @@ module.exports = { stories: ['../docs/**/*.stories.mdx', '../docs/**/*.stories.@(js|jsx|ts|tsx)'], addons: ['@storybook/addon-links', '@storybook/addon-essentials'], + webpackFinal: async (config, { configType }) => { + config.node = { ...config.node, fs: 'empty' } + return config + }, } - diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..9acf0bc --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,15 @@ +# Changelog + +Here described only the breaking and most significant changes. The full changelog and documentation for all released versions could be found in nicely formatted [commit history](https://github.com/frictionlessdata/components/commits/main). + +## v1.2.0 + +- Supported workflows by name (#23) + +## v1.1.0 + +- Create optional "onSchemaChange" prop for schema editor component (#18) + +## v1.0.0 + +- First stable version diff --git a/Makefile b/Makefile index 3b8cdae..8807af3 100644 --- a/Makefile +++ b/Makefile @@ -19,7 +19,7 @@ release: @cp README.md docs/Inroduction.stories.mdx && echo '\nWe are including a docs update to the commit\n' @git log --pretty=format:"%C(yellow)%h%Creset %s%Cgreen%d" --reverse -20 @echo "\nReleasing v$(VERSION) in 10 seconds. Press to abort\n" && sleep 10 - git commit -a -m 'v$(VERSION)' && git tag -a v$(VERSION) -m 'v$(VERSION)' + npm test && git commit -a -m 'v$(VERSION)' && git tag -a v$(VERSION) -m 'v$(VERSION)' git push --follow-tags templates: diff --git a/README.md b/README.md index d64cebd..514d792 100644 --- a/README.md +++ b/README.md @@ -6,9 +6,9 @@ [![Coverage](https://img.shields.io/codecov/c/github/frictionlessdata/components/main)](https://codecov.io/gh/frictionlessdata/components) [![Registry](https://img.shields.io/npm/v/frictionless-components.svg)](https://www.npmjs.com/package/frictionless-components) [![Codebase](https://img.shields.io/badge/github-main-brightgreen)](https://github.com/frictionlessdata/components) -[![Support](https://img.shields.io/badge/chat-discord-brightgreen)](https://discord.com/channels/695635777199145130/695635777199145133) +[![Support](https://img.shields.io/badge/support-discord-brightgreen)](https://discordapp.com/invite/Sewv6av) -Visual components for the Frictionless Data project in TypeScript/React. +Data integration components for TypeScript/React that implement Frictionless Data concepts in visual UI. ## Purpose diff --git a/docs/Inroduction.stories.mdx b/docs/Inroduction.stories.mdx index d64cebd..514d792 100644 --- a/docs/Inroduction.stories.mdx +++ b/docs/Inroduction.stories.mdx @@ -6,9 +6,9 @@ [![Coverage](https://img.shields.io/codecov/c/github/frictionlessdata/components/main)](https://codecov.io/gh/frictionlessdata/components) [![Registry](https://img.shields.io/npm/v/frictionless-components.svg)](https://www.npmjs.com/package/frictionless-components) [![Codebase](https://img.shields.io/badge/github-main-brightgreen)](https://github.com/frictionlessdata/components) -[![Support](https://img.shields.io/badge/chat-discord-brightgreen)](https://discord.com/channels/695635777199145130/695635777199145133) +[![Support](https://img.shields.io/badge/support-discord-brightgreen)](https://discordapp.com/invite/Sewv6av) -Visual components for the Frictionless Data project in TypeScript/React. +Data integration components for TypeScript/React that implement Frictionless Data concepts in visual UI. ## Purpose diff --git a/docs/Schema.stories.tsx b/docs/Schema.stories.tsx new file mode 100644 index 0000000..2f9eabb --- /dev/null +++ b/docs/Schema.stories.tsx @@ -0,0 +1,24 @@ +import '../src/styles' +import React from 'react' +import { Story, Meta } from '@storybook/react' +import { Schema, ISchemaProps } from '../src' + +export default { + title: 'Components/Schema', + component: Schema, +} as Meta + +const Template: Story = (args) => +const onSave = () => alert('Clicked on the "Save button"') + +export const Empty = Template.bind({}) +Empty.args = { + onSave, +} + +export const Source = Template.bind({}) +Source.args = { + source: + 'https://raw.githubusercontent.com/frictionlessdata/frictionless-py/main/data/table.csv', + onSave, +} diff --git a/package.json b/package.json index e04caf0..1590df6 100644 --- a/package.json +++ b/package.json @@ -1,10 +1,10 @@ { "name": "@cdl-dryad/frictionless-components", - "version": "0.3.14", + "version": "1.2.0", "license": "MIT", "author": "roll ", - "description": "Visual components for frictionless", - "homepage": "https://github.com/frictionlessdata/frictionless-ui", + "description": "Data integration components for TypeScript/React that implement Frictionless Data concepts in visual UI.", + "homepage": ["https://github.com/frictionlessdata/frictionless-ui", "https://github.com/CDL-Dryad/components"], "bugs": "https://github.com/frictionlessdata/frictionless-ui/issues", "repository": { "type": "git", @@ -29,7 +29,6 @@ "e2e": "cypress run", "format": "prettier --write '{src,test}/**/*.ts*' && eslint --fix '{src,test}/**/*.ts*'", "lint": "prettier --check '{src,test}/**/*.ts*' && eslint '{src,test}/**/*.ts*'", - "prepare": "husky install", "pretest": "npm run lint", "readme": "doctoc --maxlevel 3 README.md", "test": "jest --coverage", @@ -38,16 +37,20 @@ "dependencies": { "@types/lodash": "^4.14.168", "@types/marked": "^2.0.1", + "@types/uuid": "^8.3.0", "classnames": "^2.3.1", "jsonschema": "^1.4.0", "jszip": "^3.6.0", "lodash": "^4.17.21", "marked": "^2.0.1", - "use-async-effect": "^2.2.3" + "react-sortable-hoc": "^2.0.0", + "tableschema": "^1.12.4", + "use-async-effect": "^2.2.3", + "uuid": "^8.3.2" }, "peerDependencies": { - "react": "^17.0.2", - "react-dom": "^17.0.2" + "react": ">=17.0.0", + "react-dom": ">=17.0.0" }, "devDependencies": { "@babel/core": "^7.13.15", @@ -79,14 +82,12 @@ "eslint-plugin-standard": "^5.0.0", "file-loader": "^6.2.0", "http-server": "^0.12.3", - "husky": "^6.0.0", "jest": "^26.6.3", "mini-css-extract-plugin": "^1.4.1", "node-fetch": "^2.6.1", "npm-check-updates": "^11.4.1", "prettier": "^2.2.1", "react": "^17.0.2", - "react-addons-test-utils": "^15.6.2", "react-dom": "^17.0.2", "react-hot-loader": "^4.13.0", "react-test-renderer": "^17.0.2", diff --git a/src/components/ReportTask.tsx b/src/components/ReportTask.tsx index 5046789..edf86da 100644 --- a/src/components/ReportTask.tsx +++ b/src/components/ReportTask.tsx @@ -39,9 +39,23 @@ export function ReportTask(props: IReportTaskProps) { // Helpers +export function removeBaseUrl(text: string) { + return text.replace(/https:\/\/raw\.githubusercontent\.com\/\S*?\/\S*?\/\S*?\//g, '') +} + +export function splitFilePath(path: string) { + const parts = path.split('/') + return { + name: parts.pop(), + base: parts.join('/'), + sep: parts.length ? '/' : '', + } +} + export function getReportErrors(task: IReportTask) { const reportErrors: { [code: string]: IReportError } = {} for (const error of task.errors) { + if (!task.resource.schema) continue const header = task.resource.schema.fields.map((field) => field.name) // Prepare reportError diff --git a/src/components/Schema.tsx b/src/components/Schema.tsx new file mode 100644 index 0000000..7f933bc --- /dev/null +++ b/src/components/Schema.tsx @@ -0,0 +1,253 @@ +import { find } from 'lodash' +import classNames from 'classnames' +import React, { useCallback, useEffect, useState } from 'react' +import { arrayMove } from 'react-sortable-hoc' +import { useAsyncEffect } from 'use-async-effect' +import { SortableContainer, SortableElement } from 'react-sortable-hoc' +import { SchemaFeedback, ISchemaFeedbackProps } from './SchemaFeedback' +import { SchemaPreview } from './SchemaPreview' +import { SchemaField } from './SchemaField' +import * as helpers from '../helpers' +import { IDict } from '../common' + +export interface ISchemaProps { + source?: string | File + schema: IDict | File + onSave: any + oldIndex?: number + newIndex?: number + onSchemaChange?: (schema: any, error: any) => void + disablePreview: boolean + disableSave?: boolean +} + +export function Schema(props: ISchemaProps) { + const [tab, setTab] = useState('edit' as 'edit' | 'preview') + const [error, setError] = useState(null as null | Error) + const [loading, setLoading] = useState(false) + const [columns, setColumns] = useState([] as IDict[]) + const [metadata, setMetadata] = useState({} as IDict) + const [feedback] = useState({} as ISchemaFeedbackProps['feedback']) + + // Mount + useAsyncEffect(async () => { + try { + setLoading(false) + const { columns, metadata } = await helpers.importSchema(props.source, props.schema) + setLoading(false) + setColumns(columns) + setMetadata(metadata) + } catch (error) { + // @ts-ignore + setError(error) + setLoading(false) + } + }, []) + + const memoizedExportSchema = useCallback( + () => helpers.exportSchema(columns, metadata), + [columns, metadata] + ) + + useEffect(() => { + if (props.onSchemaChange) { + const schema = memoizedExportSchema() + props.onSchemaChange(schema, error) + } + }, [columns, metadata, error]) + + // Save + const saveSchema = () => { + if (props.onSave) { + const schema = memoizedExportSchema() + props.onSave(schema, error) + } + } + + // Feedback Reset + const resetFeedback = () => {} + + // Add Field + const addField = () => { + setColumns([...columns, helpers.createColumn(columns.length)]) + } + + // Remove Field + const removeField = (id: number): void => { + const newColumns = columns.filter((column) => column.id !== id) + setColumns([...newColumns]) + } + + // Update Field + const updateField = (id: number, name: string, value: any): void => { + const column = find(columns, (column) => column.id === id) + if (column) { + column.field[name] = value + setColumns([...columns]) + } + } + + // Move Field + const moveField = (props: { oldIndex: number; newIndex: number }) => { + setColumns([...arrayMove(columns, props.oldIndex, props.newIndex)]) + } + + const showTabNumber = !props.disablePreview && !props.disableSave + + return ( +
+
+ {/* Tab navigation */} + + +
+ + {/* Feedback */} + + + {/* Tab contents */} + {!loading && !error && ( +
+ {/* Edit */} + {tab === 'edit' && ( +
+
+ {/* List fields */} + + + {/* Add field */} +
+ +
+
+
+ )} + + {/* Preview */} + {tab === 'preview' && ( +
+ +
+ )} +
+ )} +
+
+ ) +} + +// Internal + +const SortableFields = SortableContainer( + (props: { columns: IDict[]; metadata: IDict; removeField: any; updateField: any }) => ( +
    + {props.columns.map((column: IDict, index: number) => ( + + ))} +
+ ) +) + +const SortableField = SortableElement( + (props: { column: IDict; metadata: IDict; removeField: any; updateField: any }) => ( +
  • + +
  • + ) +) diff --git a/src/components/SchemaFeedback.tsx b/src/components/SchemaFeedback.tsx new file mode 100644 index 0000000..3bb012c --- /dev/null +++ b/src/components/SchemaFeedback.tsx @@ -0,0 +1,37 @@ +import React from 'react' +import classNames from 'classnames' + +export interface ISchemaFeedbackProps { + onReset: () => void + feedback: { + message: string + reset: boolean + type: string + } +} + +export function SchemaFeedback(props: ISchemaFeedbackProps) { + return ( +
    +
    + {props.feedback.message} + {!!props.feedback.reset && ( + +  To start from scratch please  + { + ev.preventDefault() + props.onReset() + }} + > + click here + + . + + )} +
    +
    + ) +} diff --git a/src/components/SchemaField.tsx b/src/components/SchemaField.tsx new file mode 100644 index 0000000..4213a5a --- /dev/null +++ b/src/components/SchemaField.tsx @@ -0,0 +1,193 @@ +import React, { useState } from 'react' +import { IDict } from '../common' +import * as helpers from '../helpers' + +export interface ISchemaFieldProps { + column: IDict + metadata: IDict + removeField: any + updateField: any +} + +export function SchemaField(props: ISchemaFieldProps) { + const types = helpers.getFieldTypes() + const formats = helpers.getFieldFormats(props.column.field.type) + const [isDetails, setIsDetails] = useState(false) + return ( +
    + {/* General */} +
    + {/* Name */} +
    +
    +
    +
    +
    Name
    +
    + props.updateField(props.column.id, 'name', ev.target.value)} + /> +
    +
    + + {/* Type */} +
    +
    +
    +
    Type
    +
    + +
    +
    + + {/* Format */} +
    +
    +
    +
    Format
    +
    + { + props.updateField(props.column.id, 'format', ev.target.value) + }} + /> +
    +
    + + {/* Controls */} +
    + {/* Details */} + + + {/* Remove */} + +
    +
    + + {/* Details */} + {isDetails && ( +
    +
    +
    +
    + {/* Extra fields */} +
    + {/* Title */} +
    + + + props.updateField(props.column.id, 'title', ev.target.value) + } + /> +
    + + {/* Description */} +
    + +