generated from openedx/frontend-template-application
-
Notifications
You must be signed in to change notification settings - Fork 39
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: slot component for dynamic plugins #184
Closed
Closed
Changes from 2 commits
Commits
Show all changes
27 commits
Select commit
Hold shift + click to select a range
2756e3d
feat: slot component for dynamic plugins
johnvente e9f29a6
temp: using the component wrapper with the plugins
johnvente b59d783
fix: conflicts
johnvente 5fcc77f
fix: conflicts
johnvente c548d45
fix: linter problems
johnvente d6b09c5
fix: unit test
johnvente 7fee38c
fix: children and props wrapper dependecies
johnvente 55468d6
fix: unit test for pluggable component
johnvente cd64f47
test: update tests for pluggable component
johnvente dd365d5
feat: build form email full pluggable
johnvente 5c16343
test: update test for pluggable component
johnvente 52f6dad
feat: context factory util and build email form extensible with context
johnvente ee2dffa
refactor: update plugins with context data
johnvente adbf942
test: reducer test for build email form extensible
johnvente 13003f1
refactor: remove unnecessary comments
johnvente ed8a010
refactor: addressing some some improvements
johnvente 0d94489
feat: adding course id for plugins
johnvente 7e64b99
fix: solve conflicts
johnvente 6b1e8ec
fix: dependencies problems
johnvente 4d7a58d
feat: allow multiple plugins for pluggable component
johnvente 5f013fc
refactor: change plugins hook to a component
johnvente fc0b1bf
docs: ui slot external config
johnvente f2716f0
fix: solve conflicts
johnvente ffa1797
refactor: removing unnecessary bulk email form and changing paragon d…
johnvente 45cedc9
refactor: removing unnecessary code
johnvente 39b9941
fix: check box form test
johnvente fccfd57
refactor: changing BuildEmailFormExtensible to BulkEmailForm
johnvente File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,9 +1,28 @@ | ||
const path = require('path'); | ||
/* eslint-disable import/no-extraneous-dependencies */ | ||
|
||
const { createConfig } = require('@edx/frontend-build'); | ||
|
||
module.exports = createConfig('eslint', { | ||
settings: { | ||
'import/resolver': { | ||
webpack: { | ||
config: [ | ||
path.resolve(__dirname, 'webpack.dev.config.js'), | ||
path.resolve(__dirname, 'webpack.prod.config.js'), | ||
], | ||
}, | ||
}, | ||
}, | ||
rules: { | ||
'react/function-component-definition': 'off', | ||
'import/no-extraneous-dependencies': [ | ||
'error', { | ||
devDependencies: false, | ||
optionalDependencies: false, | ||
peerDependencies: false, | ||
packageDir: __dirname, | ||
}, | ||
], | ||
|
||
}, | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
import PropTypes from 'prop-types'; | ||
import { Form, Container } from '@edx/paragon'; | ||
|
||
const CheckBoxForm = ({ isChecked, handleChange, label }) => ( | ||
<Container className="my-4 border border-success-300 p-4"> | ||
<Form.Checkbox checked={isChecked} onChange={handleChange}> | ||
{label} | ||
</Form.Checkbox> | ||
</Container> | ||
); | ||
|
||
CheckBoxForm.propTypes = { | ||
isChecked: PropTypes.bool.isRequired, | ||
handleChange: PropTypes.func.isRequired, | ||
label: PropTypes.string.isRequired, | ||
}; | ||
|
||
export default CheckBoxForm; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
{ | ||
"name": "@openedx-plugins/communications-app-check-box-form", | ||
"version": "1.0.0", | ||
"description": "edx input type checkbox form to use it in this mfe", | ||
"scripts": { | ||
"test": "echo \"Error: no test specified\" && exit 1" | ||
}, | ||
"peerDependencies": { | ||
"@edx/frontend-app-communications": "*", | ||
"@edx/frontend-platform": "*", | ||
"@edx/paragon": "*", | ||
"prop-types": "*", | ||
"react": "*" | ||
}, | ||
"peerDependenciesMeta": { | ||
"@edx/frontend-app-communications": { | ||
"optional": true | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
import PropTypes from 'prop-types'; | ||
import { Form, Container } from '@edx/paragon'; | ||
|
||
const InputForm = ({ | ||
isValid, controlId, label, feedbackText, | ||
}) => { | ||
const feedbackType = isValid ? 'valid' : 'invalid'; | ||
return ( | ||
<Form.Group isValid={isValid} controlId={controlId} data-testid="plugin-input" className="p-3 border border-success-300"> | ||
<Form.Label className="h3 text-primary-500">{label}</Form.Label> | ||
<Container className="row"> | ||
<Form.Control className="col-3" /> | ||
<p className="col-8">@openedx-plugins/communications-app-input-form</p> | ||
</Container> | ||
<Form.Control.Feedback type={feedbackType}> | ||
{feedbackText} | ||
</Form.Control.Feedback> | ||
</Form.Group> | ||
); | ||
}; | ||
|
||
InputForm.propTypes = { | ||
isValid: PropTypes.bool.isRequired, | ||
controlId: PropTypes.string.isRequired, | ||
label: PropTypes.string.isRequired, | ||
feedbackText: PropTypes.string.isRequired, | ||
}; | ||
|
||
export default InputForm; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
{ | ||
"name": "@openedx-plugins/communications-app-input-form", | ||
"version": "1.0.0", | ||
"description": "edx input form to use it in this mfe", | ||
"scripts": { | ||
"test": "echo \"Error: no test specified\" && exit 1" | ||
}, | ||
"peerDependencies": { | ||
"@edx/frontend-app-communications": "*", | ||
"@edx/frontend-platform": "*", | ||
"@edx/paragon": "*", | ||
"prop-types": "*", | ||
"react": "*" | ||
}, | ||
"peerDependenciesMeta": { | ||
"@edx/frontend-app-communications": { | ||
"optional": true | ||
} | ||
} | ||
} |
51 changes: 51 additions & 0 deletions
51
src/components/PluggableComponent/__snapshots__/index.test.jsx.snap
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
// Jest Snapshot v1, https://goo.gl/fbAQLP | ||
|
||
exports[`PluggableComponent renders correctly 1`] = ` | ||
<div> | ||
<div | ||
class="pgn__form-group" | ||
data-testid="plugin-input" | ||
> | ||
<label | ||
class="pgn__form-label" | ||
for="randomID" | ||
> | ||
Hello | ||
</label> | ||
<div | ||
class="pgn__form-control-decorator-group" | ||
> | ||
<input | ||
class="form-control is-valid" | ||
id="randomID" | ||
/> | ||
</div> | ||
<div | ||
class="pgn__form-control-description pgn__form-text pgn__form-text-valid" | ||
> | ||
<span | ||
class="pgn__icon" | ||
> | ||
<svg | ||
aria-hidden="true" | ||
fill="none" | ||
focusable="false" | ||
height="24" | ||
role="img" | ||
viewBox="0 0 24 24" | ||
width="24" | ||
xmlns="http://www.w3.org/2000/svg" | ||
> | ||
<path | ||
d="M9 16.2 4.8 12l-1.4 1.4L9 19 21 7l-1.4-1.4L9 16.2Z" | ||
fill="currentColor" | ||
/> | ||
</svg> | ||
</span> | ||
<div> | ||
You are correct | ||
</div> | ||
</div> | ||
</div> | ||
</div> | ||
`; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
/* eslint-disable react-hooks/exhaustive-deps */ | ||
import React, { useState, useEffect } from 'react'; | ||
import loadable from '@loadable/component'; | ||
import PropTypes from 'prop-types'; | ||
|
||
import { isPluginAvailable } from './utils'; | ||
|
||
/** | ||
* PluggableComponent - A component that allows dynamic loading and replacement of child components. | ||
* | ||
* @param {object} props - Component props | ||
* @param {React.ReactNode} props.children - Child elements to be passed to the plugin component | ||
* @param {string} props.as - String indicating the module to import dynamically | ||
* @param {string} props.id - Identifier for the plugin | ||
* @param {object} props.pluggableComponentProps - Additional props to be passed to the component | ||
* @returns {React.ReactNode} - Rendered component | ||
*/ | ||
const PluggableComponent = ({ | ||
children, | ||
as, | ||
id, | ||
...pluggableComponentProps | ||
}) => { | ||
const [newComponent, setNewComponent] = useState(children); | ||
|
||
useEffect(() => { | ||
const loadPluginComponent = async () => { | ||
try { | ||
const hasModuleInstalled = await isPluginAvailable(as); | ||
|
||
if (hasModuleInstalled) { | ||
const PluginComponent = loadable(() => import(`@node_modules/@openedx-plugins/${as}`)); | ||
|
||
const component = children ? ( | ||
<PluginComponent key={id} {...pluggableComponentProps}> | ||
{children} | ||
</PluginComponent> | ||
) : ( | ||
<PluginComponent key={id} {...pluggableComponentProps} /> | ||
); | ||
|
||
setNewComponent(component); | ||
} | ||
} catch (error) { | ||
console.error(`Failed to load plugin ${as}:`, error); | ||
} | ||
}; | ||
|
||
loadPluginComponent(); | ||
}, [id, as, children]); | ||
|
||
return newComponent; | ||
}; | ||
|
||
PluggableComponent.propTypes = { | ||
children: PropTypes.node, | ||
as: PropTypes.string, | ||
id: PropTypes.string, | ||
}; | ||
|
||
export default PluggableComponent; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
import React from 'react'; | ||
import { render, waitFor, screen } from '@testing-library/react'; | ||
import PluggableComponent from '.'; | ||
|
||
describe('PluggableComponent', () => { | ||
it('renders correctly', async () => { | ||
const props = { | ||
isValid: true, | ||
controlId: 'randomID', | ||
label: 'Hello', | ||
feedbackText: 'You are correct', | ||
}; | ||
const { container } = render( | ||
<PluggableComponent | ||
id="pluggableComponent" | ||
as="communications-app-input-form" | ||
{...props} | ||
> | ||
<h1>Hi this is the original component</h1> | ||
</PluggableComponent>, | ||
); | ||
|
||
await waitFor(() => { | ||
const inputForm = screen.getByTestId('plugin-input'); | ||
expect(inputForm).toBeInTheDocument(); | ||
expect(screen.getByText(props.label)).toBeInTheDocument(); | ||
expect(screen.getByText(props.feedbackText)).toBeInTheDocument(); | ||
expect(container).toMatchSnapshot(); | ||
}); | ||
}); | ||
|
||
it('loads children component when import is invalid', async () => { | ||
render( | ||
<PluggableComponent id="est-pluggable" as="invalid import"> | ||
<div data-testid="plugin">Plugin Loaded</div> | ||
</PluggableComponent>, | ||
); | ||
|
||
await waitFor(() => { | ||
const defaultComponent = screen.getByTestId('plugin'); | ||
expect(screen.getByText('Plugin Loaded')).toBeInTheDocument(); | ||
expect(defaultComponent).toBeInTheDocument(); | ||
}); | ||
}); | ||
|
||
it('loads children component when import object', async () => { | ||
render( | ||
<PluggableComponent | ||
id="test-pluggable" | ||
as="" | ||
> | ||
<div data-testid="plugin">Plugin Loaded</div> | ||
</PluggableComponent>, | ||
); | ||
|
||
await waitFor(() => { | ||
const defaultComponent = screen.getByTestId('plugin'); | ||
expect(screen.getByText('Plugin Loaded')).toBeInTheDocument(); | ||
expect(defaultComponent).toBeInTheDocument(); | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
export const isPluginAvailable = async (pluginName) => { | ||
if (!pluginName) { return false; } | ||
|
||
try { | ||
await import(`@node_modules/@openedx-plugins/${pluginName}`); | ||
return true; | ||
} catch (error) { | ||
return false; | ||
} | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
import { isPluginAvailable } from './utils'; | ||
|
||
describe('isPluginAvailable util', () => { | ||
it('returns true if a plugin is installed', async () => { | ||
const checkBoxPlugin = await isPluginAvailable('communications-app-check-box-form'); | ||
expect(checkBoxPlugin).toBe(true); | ||
}); | ||
|
||
it('returns false if a plugin is not installed', async () => { | ||
const nonexistentPlugin = await isPluginAvailable('nonexistentPlugin'); | ||
expect(nonexistentPlugin).toBe(false); | ||
}); | ||
|
||
it('returns false if an empty string is provided as plugin name', async () => { | ||
const emptyPlugin = await isPluginAvailable(''); | ||
expect(emptyPlugin).toBe(false); | ||
}); | ||
|
||
it('returns false if null is provided as plugin name', async () => { | ||
const nullPLugin = await isPluginAvailable(null); | ||
expect(nullPLugin).toBe(false); | ||
}); | ||
}); |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like that the children are passed to the plugin, so it can optionally wrap the default contents 👍🏻