Skip to content

Commit

Permalink
Deploy to prod (#167)
Browse files Browse the repository at this point in the history
* Bug 78 bool (#100)

* updates readme

* defaults boolean vals to false (so checkbox works)

* Bug 78 bool (#101)

* updates readme

* defaults boolean vals to false (so checkbox works)

* recursive method to replace boolean widgets, need to fix

* -

* manual ui schema in progress

* ephys ui schema

* fix ephys ui schema

* endline

* small fix

* Bug 78 bool widget (#103)

* feat: adds rjsf material ui package

* feat: adds custom checkbox for booleans

---------

Co-authored-by: Mekhla Kapoor <54870020+mekhlakapoor@users.noreply.github.com>

* removes override

---------

Co-authored-by: jtyoung84 <104453205+jtyoung84@users.noreply.github.com>

* hot fix (#106)

* fix: filters version list by schema type (#116)

* Feat 110 remove schemas (#115)

* Deploy to prod (#107)

* Bug 78 bool (#100)

* updates readme

* defaults boolean vals to false (so checkbox works)

* Bug 78 bool (#101)

* updates readme

* defaults boolean vals to false (so checkbox works)

* recursive method to replace boolean widgets, need to fix

* -

* manual ui schema in progress

* ephys ui schema

* fix ephys ui schema

* endline

* small fix

* Bug 78 bool widget (#103)

* feat: adds rjsf material ui package

* feat: adds custom checkbox for booleans

---------

Co-authored-by: Mekhla Kapoor <54870020+mekhlakapoor@users.noreply.github.com>

* removes override

---------

Co-authored-by: jtyoung84 <104453205+jtyoung84@users.noreply.github.com>

* hot fix (#106)

---------

Co-authored-by: Mekhla Kapoor <54870020+mekhlakapoor@users.noreply.github.com>
Co-authored-by: jtyoung84 <104453205+jtyoung84@users.noreply.github.com>

* version bump [skip actions]

* removes ephys, ophys, behavior rigs & sessions

* off dev

* schemas filtered with env variable

* env template

---------

Co-authored-by: Yosef Bedaso <20714699+yosefmaru@users.noreply.github.com>
Co-authored-by: jtyoung84 <104453205+jtyoung84@users.noreply.github.com>
Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com>

* Renders dictionaries (#118)

* recursion, unit test, additional properties

* fix import

* docstring

* Sort schema versions dropdown by lastest-first semantic version

* Add sample schema list and sorted version list for unit tests

* Add unit tests for Dropdowns and sorted schema version

* Feat 117: Sort schema versions list in dropdown by lastest-first semantic version (#123)

* Sort schema versions dropdown by lastest-first semantic version

* Add sample schema list and sorted version list for unit tests

* Add unit tests for Dropdowns and sorted schema version

* Fix 119: Fix pre-process and render issues and improve UI (#127)

* add error handling for preProcessSchema() andfetchAndSetSchema()
* handle prop.default is null
* improve App component UI and styling
* refactor Dropdowns to Toolbar, add styling, disable version dropdown and upload btn on default
* add default titles to dropdown of allowed types/ subschemas
* add react-toastify for notifications

* handle schema version changes from App component (#130)

* Feat 27: Add linter and unit test frameworks (#133)

* add ESLint with React plugin, JS Standard style guide

* add unit test commands

* add current coverage as test coverageThreshold

* linter auto-fix styling issues

* fix linting issues (imports, React in scope, camelCase)

* add propTypes to resolve prop validation linting issues

* Feat 27: Add linter and unit test frameworks

* finish merge

* Fix 131: Enforce re-render and handle errors in `RenderForm` component (#142)

* Add ErrorBoundary for RenderForm component

* use selectedSchemaPath as key to re-render RenderForm component

* create unit test suite for App component

* Fix 132: Update schema filtering and validation (#143)

* parse and validate schema type, version to fix filter logic

* create unit tests for parseAndFilterSchemas()

* replace selectedSchemaVersion with unique selectedSchemaPath

* Feat 68: Add border-left css for nested form fieldsets (#147)

* Fix 139: Enable discriminator support in validator and fix validation errors (#151)

* enable discriminator validation in ajv validator

* remove extra formData on changes to seleciton/ discriminator

* update empty object check

* update unit tests

* fix redundant lines

* Fix 108: Error handling, validation, and UI fixes for Autofill feature (#154)

* add error handling and file type validation for file upload

* validate uploaded schema type, version with possible schemas

* clear autofilled formData after change schema

* allow autofill with any schema type

* refactor RehydrateForm.js from component to util function

* success toast to indicate autofill complete, fix button focus

* code cleanup

* update unit tests for autofill feature changes

* update test coverage thresholds

* Fix 102: Fix nested constants render issues (#157)

* fix rendering of nested literals/ consts

* revert other widgets to material-ui to keep label

* Enforce const values set to formData, enforce null consts as null strings

* update @rjsf/core

* useLayoutEffect in custom widget to sync changes on screen

* explicityly set missing type for all consts, check default value is correct

* update unit tests to check consts preprocessed and rendered correctly

* update docstrings

* update docstrings

* Fix: Render null consts as nullable strings and update CustomTextWidget (#161)

* update CustomTextWidget effect to only run when necessary

* render null consts as nullable strings with CustomTextWidget

* Feat 149: Add Help button and popup UI (#166)

* add Help popup with link to repo and tips

* add unit tests

* add aind-data-schema readthedocs link to help ui

* resolve conflicts

---------

Co-authored-by: Mekhla Kapoor <54870020+mekhlakapoor@users.noreply.github.com>
Co-authored-by: jtyoung84 <104453205+jtyoung84@users.noreply.github.com>
Co-authored-by: Yosef Bedaso <20714699+yosefmaru@users.noreply.github.com>
Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com>
  • Loading branch information
5 people committed Apr 5, 2024
1 parent 276aae9 commit dc006ba
Show file tree
Hide file tree
Showing 11 changed files with 169 additions and 16 deletions.
8 changes: 4 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,10 @@
"jest": {
"coverageThreshold": {
"global": {
"statements": 62,
"branches": 61,
"functions": 70,
"lines": 62
"statements": 64,
"branches": 65,
"functions": 71,
"lines": 65
}
},
"transformIgnorePatterns": [
Expand Down
38 changes: 38 additions & 0 deletions src/components/Toolbar.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,28 @@ import PropTypes from 'prop-types'
import 'bootstrap/dist/css/bootstrap.min.css'
import { compareVersions } from 'compare-versions'
import styles from './Toolbar.module.css'
import { toast } from 'react-toastify'
import Config from '../config'
import { getLinkAsButton } from '../utilities/uiUtils'

const helpToast = (
<div >
<h2 className='text-primary'>Help</h2>
View or submit feedback to our {getLinkAsButton(Config.REPO_URL, 'GitHub repository')}:
<div>
{getLinkAsButton(`${Config.REPO_URL}/issues`, 'Issues (bugs or feature requests)', 'View/submit bugs or feature requests', true)}
{getLinkAsButton(`${Config.REPO_URL}/discussions`, 'Discussions', 'View/submit discussions', true)}
</div>
<br />
<h4>Getting started</h4>
Use this tool to create metadata files based on {getLinkAsButton(Config.AIND_DATA_SCHEMA_REPO_URL, 'aind-data-schema')}
&nbsp;&#40;{getLinkAsButton(Config.AIND_DATA_SCHEMA_READTHEDOCS_URL, 'readthedocs')}&#41;.
<ul>
<li>Select a schema from the dropdown. The latest version will be loaded as a fillable form.</li>
<li>Or, use the &apos;Autofill from file&apos; button to load an existing metadata file (must be JSON).</li>
<li>The submitted metadata will be validated and saved as a JSON file to your device.</li>
</ul>
</div>)

function Toolbar (props) {
/**
Expand Down Expand Up @@ -35,6 +57,14 @@ function Toolbar (props) {
event.target.blur()
}

const showHelpPopup = (event) => {
toast(helpToast, {
toastId: 'help-toast', // provide id to disable duplicates
autoClose: false
})
event.target.blur()
}

return (
<div>
<select
Expand Down Expand Up @@ -66,6 +96,14 @@ function Toolbar (props) {
</option>
))}
</select>
<button
title="Get help"
type="button"
className={['btn', 'btn-default', styles.btnRight].join(' ')}
onClick={showHelpPopup}
>
Help
</button>
<button
title="Autofill with existing data from local file"
type="button"
Expand Down
1 change: 1 addition & 0 deletions src/components/Toolbar.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,6 @@

.btnRight {
float: right;
margin-left: 5px;
color: navy;
}
36 changes: 36 additions & 0 deletions src/components/Toolbar.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@ import Toolbar from './Toolbar'
import { parseAndFilterSchemas } from '../utilities/schemaFetchers'
import sampleSchemaLinks from '../testing/sample-schema-links.json'
import sampleSortedVersionListInstrument from '../testing/sample-sorted-version-list-instrument.json'
import { toast } from 'react-toastify'

const nullCallback = () => { }
const sampleSchemaList = parseAndFilterSchemas(sampleSchemaLinks)
jest.mock('react-toastify', () => ({ toast: jest.fn() }))

describe('Toolbar component', () => {
it('renders appropriate inputs on default', () => {
Expand All @@ -21,9 +23,11 @@ describe('Toolbar component', () => {
expect(screen.getByTitle('Select a schema')).toBeInTheDocument()
expect(screen.getByTitle('Select a version')).toBeInTheDocument()
expect(screen.getByTitle('Autofill with existing data from local file')).toBeInTheDocument()
expect(screen.getByTitle('Get help')).toBeInTheDocument()
expect(screen.getByTitle('Select a schema')).toBeEnabled()
expect(screen.getByTitle('Select a version')).toBeDisabled()
expect(screen.getByTitle('Autofill with existing data from local file')).toBeEnabled()
expect(screen.getByTitle('Get help')).toBeEnabled()
})

it('calls ParentTypeCallback and enables version selection dropdown when a schema type is chosen', () => {
Expand Down Expand Up @@ -86,4 +90,36 @@ describe('Toolbar component', () => {
fireEvent.click(screen.getByTitle('Autofill with existing data from local file'))
expect(mockRehydrateCallback).toHaveBeenCalled()
})

it('displays a popup with Help info when the Help button is clicked', () => {
const expectedHelpDiv = expect.objectContaining({
type: 'div',
props: {
children: expect.arrayContaining([
expect.objectContaining({
type: 'h2',
props: expect.objectContaining({ children: 'Help' })
}),
expect.objectContaining({
type: 'h4',
props: expect.objectContaining({ children: 'Getting started' })
})
])
}
})
const expectedToastParams = {
toastId: 'help-toast',
autoClose: false
}
render(<Toolbar
ParentTypeCallback={nullCallback}
ParentVersionCallback={nullCallback}
selectedSchemaType=''
selectedSchemaPath=''
schemaList={sampleSchemaList}
handleRehydrate={nullCallback}
/>)
fireEvent.click(screen.getByTitle('Get help'))
expect(toast).toHaveBeenCalledWith(expectedHelpDiv, expectedToastParams)
})
})
7 changes: 7 additions & 0 deletions src/config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
const Config = {
REPO_URL: 'https://github.com/AllenNeuralDynamics/aind-metadata-entry-js',
AIND_DATA_SCHEMA_REPO_URL: 'https://github.com/AllenNeuralDynamics/aind-data-schema',
AIND_DATA_SCHEMA_READTHEDOCS_URL: 'https://aind-data-schema.readthedocs.io/en/latest/'
}

export default Config
7 changes: 7 additions & 0 deletions src/config.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
describe('config', () => {
it('has all required properties', () => {
const Config = require('./config').default
expect(Config.REPO_URL).toBeDefined()
expect(Config.AIND_DATA_SCHEMA_REPO_URL).toBeDefined()
})
})
25 changes: 16 additions & 9 deletions src/custom-ui/CustomWidgets.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import CheckboxWidget from '@rjsf/material-ui/lib/CheckboxWidget/CheckboxWidget'
import TextWidget from '@rjsf/core/lib/components/widgets/TextWidget'
import React, { useLayoutEffect } from 'react'
import PropTypes from 'prop-types'
import { toConstant } from '@rjsf/utils'

const CustomTimeWidget = (props) => {
const onChange = (selectedDate) => {
Expand Down Expand Up @@ -46,27 +45,35 @@ const CustomCheckboxWidget = (props) => {
/**
* Custom text widget to enable custom behavior for constants.
* If const, update formData value to const value using onChange callback, render as readonly (grayed out)
* If null const, return null (do not display text input)
* Otherwise, return default text widget
* @param {*} props RJSF widget props
* @returns A custom text widget
*/
const CustomTextWidget = (props) => {
const { schema, onChange, value } = props
// useLayoutEffect to run effect runs before browser repaints screen (reduce flickering)
useLayoutEffect(() => {
if (props.schema.const !== undefined) {
props.onChange(toConstant(props.schema))
if (schema.const !== undefined && value !== schema.const) {
onChange(schema.const)
}
}, [props])
return <TextWidget {...props}
readonly={props.schema.const !== undefined ?? props.readonly}
value={props.schema.const !== undefined ? toConstant(props.schema) : props.value}
}, [onChange, schema.const, value])

if (schema.const === null) {
return null
} else if (schema.const) {
return <TextWidget {...props}
readonly={true}
value={schema.const}
/>
} else {
return <TextWidget {...props}/>
}
}
CustomTextWidget.propTypes = {
value: PropTypes.any,
onChange: PropTypes.func,
schema: PropTypes.object,
readonly: PropTypes.bool
schema: PropTypes.object
}

export const widgets = { checkbox: CustomCheckboxWidget, time: CustomTimeWidget, text: CustomTextWidget }
4 changes: 2 additions & 2 deletions src/utilities/schemaHandler.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -157,12 +157,12 @@ test('Checks preProcessSchema modifies const schema to add missing default or ty
{ key: 'boolean_const', type: 'boolean' },
{ key: 'object_const', type: 'object' },
{ key: 'array_const', type: 'array' },
{ key: 'null_const', type: 'null' }
{ key: 'null_const', type: ['null', 'string'] }
]
const processedSchema1 = preProcessSchema(testSchema1)
expect(processedSchema1.properties.describedBy.default).toBe(testSchema1.properties.describedBy.const)
for (const expectedType of expectedTypes) {
expect(processedSchema1.properties[expectedType.key].type).toBe(expectedType.type)
expect(processedSchema1.properties[expectedType.key].type).toStrictEqual(expectedType.type)
}
})

Expand Down
6 changes: 5 additions & 1 deletion src/utilities/schemaHandlers.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,11 @@ const preProcessHelper = (obj) => {
// If default is undefined or not matching const value, set to const
// Note: We use a custom text widget to autofill the const value and set the field as readonly.
if (prop.const !== undefined) {
if (prop.type === undefined) { prop.type = guessType(prop.const) }
if (prop.type === undefined) {
const constType = guessType(prop.const)
// use nullable string type for null consts to enable allOf defaults
prop.type = constType === 'null' ? ['null', 'string'] : constType
}
if (!deepEquals(prop.default, prop.const)) { prop.default = prop.const }
}

Expand Down
17 changes: 17 additions & 0 deletions src/utilities/uiUtils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import React from 'react'

/**
* Create an HTML link <a> element formatted as a button or a simple link. Link opens in a new tab.
* @param {string} url The link URL
* @param {string} text The text to display
* @param {string | null} tooltip The tooltip text
* @param {boolean | null} displayAsButton Flag to display the link as a button
* @returns HTML <a> element for formatted link
*/
export function getLinkAsButton (url, text, tooltip = '', displayAsButton = false) {
if (displayAsButton) {
return (<a href={url} target="_blank" rel='noreferrer' type="button" className="btn btn-default" title={tooltip}>{text}</a>)
} else {
return (<a href={url} target="_blank" rel='noreferrer' title={tooltip}>{text}</a>)
}
}
36 changes: 36 additions & 0 deletions src/utilities/uiUtils.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { render, screen } from '@testing-library/react'
import { getLinkAsButton } from './uiUtils'

describe('uiUtils', () => {
describe('getLinkAsButton', () => {
const PLACEHOLDER_URL = 'https://example.com'
const PLACEHOLDER_TEXT = 'Example Link'
const PLACEHOLDER_TOOLTIP = 'This is an example link'
test('should return an <a> element with the correct attributes on default', () => {
const linkElement = getLinkAsButton(PLACEHOLDER_URL, PLACEHOLDER_TEXT)
render(linkElement)
expect(screen.getByText(PLACEHOLDER_TEXT)).toBeInTheDocument()
expect(screen.getByText(PLACEHOLDER_TEXT)).toHaveAttribute('href', PLACEHOLDER_URL)
expect(screen.getByText(PLACEHOLDER_TEXT)).not.toHaveAttribute('title', PLACEHOLDER_TOOLTIP)
expect(screen.getByText(PLACEHOLDER_TEXT)).toHaveAttribute('target', '_blank')
expect(screen.getByText(PLACEHOLDER_TEXT)).toHaveAttribute('rel', 'noreferrer')
expect(screen.getByText(PLACEHOLDER_TEXT)).not.toHaveAttribute('type', 'button')
expect(screen.getByText(PLACEHOLDER_TEXT)).not.toHaveClass('btn')
})

test('should return an <a> element with a tooltip if specified', () => {
const linkElement = getLinkAsButton(PLACEHOLDER_URL, PLACEHOLDER_TEXT, PLACEHOLDER_TOOLTIP)
render(linkElement)
expect(screen.getByText(PLACEHOLDER_TEXT)).toBeInTheDocument()
expect(screen.getByText(PLACEHOLDER_TEXT)).toHaveAttribute('title', PLACEHOLDER_TOOLTIP)
})

test('should return an <a> element with the correct button classes and type if displayAsButton is true', () => {
const linkElement = getLinkAsButton(PLACEHOLDER_URL, PLACEHOLDER_TEXT, PLACEHOLDER_TOOLTIP, true)
render(linkElement)
expect(screen.getByText(PLACEHOLDER_TEXT)).toBeInTheDocument()
expect(screen.getByText(PLACEHOLDER_TEXT)).toHaveAttribute('type', 'button')
expect(screen.getByText(PLACEHOLDER_TEXT)).toHaveClass('btn btn-default')
})
})
})

0 comments on commit dc006ba

Please sign in to comment.