-
Notifications
You must be signed in to change notification settings - Fork 28
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(network nodes): add network nodes page
Also add a screen to mark disconnected nodes as gone chore(translations): add spanish translations for network nodes Also fix some texts keys chore(reachable-nodes): rename plugin
- Loading branch information
1 parent
142d0ac
commit f33e77a
Showing
15 changed files
with
507 additions
and
2 deletions.
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
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
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,13 @@ | ||
import Page from './src/networkNodesPage'; | ||
import { NetworkNodesMenu } from './src/networkNodesMenu'; | ||
import DeleteNodesPage from './src/containers/deleteNodesPage'; | ||
|
||
export default { | ||
name: 'NetworkNodes', | ||
page: Page, | ||
menu: NetworkNodesMenu, | ||
menuView: 'community', | ||
additionalProtectedRoutes: [ | ||
['delete-nodes', DeleteNodesPage] | ||
] | ||
}; |
134 changes: 134 additions & 0 deletions
134
plugins/lime-plugin-reachable-nodes/networkNodes.spec.js
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,134 @@ | ||
import { h } from 'preact'; | ||
import { fireEvent, act, screen } from '@testing-library/preact'; | ||
import '@testing-library/jest-dom'; | ||
import waitForExpect from 'wait-for-expect'; | ||
|
||
import NetworkNodesPage from './src/networkNodesPage'; | ||
import DeleteNodesPage from './src/containers/deleteNodesPage'; | ||
import queryCache from 'utils/queryCache'; | ||
import { getNodes, markNodesAsGone } from './src/networkNodesApi'; | ||
import { render } from 'utils/test_utils'; | ||
|
||
jest.mock('./src/networkNodesApi'); | ||
|
||
beforeEach(() => { | ||
getNodes.mockImplementation(async () => [ | ||
{ hostname: 'node1', status: 'connected' }, | ||
{ hostname: 'node2', status: 'connected' }, | ||
{ hostname: 'node3', status: 'connected' }, | ||
{ hostname: 'node4', status: 'disconnected' }, | ||
{ hostname: 'node5', status: 'disconnected' }, | ||
{ hostname: 'node6', status: 'disconnected' }, | ||
{ hostname: 'node7', status: 'disconnected' }, | ||
{ hostname: 'node8', status: 'gone' }, | ||
{ hostname: 'node9', status: 'gone' }, | ||
]); | ||
markNodesAsGone.mockImplementation(async () => []); | ||
}); | ||
|
||
afterEach(() => { | ||
act(() => queryCache.clear()); | ||
}); | ||
|
||
describe('network nodes screen', () => { | ||
it('shows one tab for connected nodes and one for discconected nodes with length', async () => { | ||
render(<NetworkNodesPage />); | ||
expect(await screen.findByRole('tab', { name: /^connected \(3\)/i })).toBeVisible(); | ||
expect(await screen.findByRole('tab', { name: /^disconnected \(4\)/i })).toBeVisible(); | ||
}) | ||
|
||
it('shows one row with the hostname for each connect node', async () => { | ||
render(<NetworkNodesPage />); | ||
expect(await screen.findByText('node1')).toBeVisible(); | ||
expect(await screen.findByText('node2')).toBeVisible(); | ||
expect(await screen.findByText('node3')).toBeVisible(); | ||
}) | ||
|
||
it('shows one row with the hostname for each disconnect node', async () => { | ||
render(<NetworkNodesPage />); | ||
const tabDisconnected = await screen.findByRole('tab', { name: /^disconnected \(4\)/i }); | ||
fireEvent.click(tabDisconnected); | ||
expect(await screen.findByText('node4')).toBeVisible(); | ||
expect(await screen.findByText('node5')).toBeVisible(); | ||
expect(await screen.findByText('node6')).toBeVisible(); | ||
expect(await screen.findByText('node7')).toBeVisible(); | ||
}) | ||
|
||
it('shows a link to go to delete nodes page', async () => { | ||
render(<NetworkNodesPage />); | ||
const tabDisconnected = await screen.findByRole('tab', { name: /^disconnected \(4\)/i }); | ||
fireEvent.click(tabDisconnected); | ||
expect(await screen.findByRole('link', { name: /go to delete nodes/i })).toBeVisible(); | ||
}) | ||
|
||
it('does not show a link to go to delete nodes page if there are no disconnected nodes', async () => { | ||
getNodes.mockImplementation(async () => [ | ||
{ hostname: 'node1', status: 'connected' }, | ||
{ hostname: 'node2', status: 'connected' }, | ||
{ hostname: 'node3', status: 'connected' }, | ||
]); | ||
render(<NetworkNodesPage />); | ||
const tabDisconnected = await screen.findByRole('tab', { name: /^disconnected \(0\)/i }); | ||
fireEvent.click(tabDisconnected); | ||
expect(screen.queryByRole('link', { name: /go to delete nodes/i })).toBeNull(); | ||
}) | ||
|
||
it('shows help message when clicking on help button', async () => { | ||
render(<NetworkNodesPage />); | ||
const helpButton = await screen.findByLabelText('help'); | ||
fireEvent.click(helpButton); | ||
expect(await screen.findByText("Connected Nodes")).toBeVisible(); | ||
expect(await screen.findByText("These are the nodes with which you have connectivity, " | ||
+ "i.e. there is a working path from your node to each of them.")).toBeVisible(); | ||
expect(await screen.findByText("Disconnected Nodes")).toBeVisible(); | ||
expect(await screen.findByText("These are the nodes with which you do not have connectivity, " | ||
+ "it is possible that they are not turned on or a link to reach them is down.")).toBeVisible(); | ||
}) | ||
}); | ||
|
||
|
||
describe('delete nodes page', () => { | ||
it('shows the list of disconnected nodes only', async () => { | ||
render(<DeleteNodesPage />); | ||
expect(await screen.findByText('node4')).toBeVisible(); | ||
expect(await screen.findByText('node5')).toBeVisible(); | ||
expect(await screen.findByText('node6')).toBeVisible(); | ||
expect(await screen.findByText('node7')).toBeVisible(); | ||
expect(screen.queryByText('node1')).toBeNull(); | ||
expect(screen.queryByText('node2')).toBeNull(); | ||
expect(screen.queryByText('node3')).toBeNull(); | ||
expect(screen.queryByText('node8')).toBeNull(); | ||
expect(screen.queryByText('node9')).toBeNull(); | ||
}) | ||
|
||
it('calls the markNodesAsGone api when deleting', async () => { | ||
markNodesAsGone.mockImplementation(async () => ['node6']); | ||
render(<DeleteNodesPage />); | ||
fireEvent.click(await screen.findByText('node6')); | ||
fireEvent.click(await screen.findByRole('button', { name: /delete/i })); | ||
await waitForExpect(() => { | ||
expect(markNodesAsGone).toBeCalledWith(['node6']); | ||
}) | ||
}) | ||
|
||
it('hide nodes from the list after deletion', async () => { | ||
markNodesAsGone.mockImplementation(async () => ['node6', 'node7']); | ||
render(<DeleteNodesPage />); | ||
fireEvent.click(await screen.findByText('node6')); | ||
fireEvent.click(await screen.findByText('node7')); | ||
fireEvent.click(await screen.findByRole('button', { name: /delete/i })); | ||
expect(await screen.findByText('node4')).toBeVisible(); | ||
expect(await screen.queryByText('node5')).toBeVisible(); | ||
expect(await screen.queryByText('node6')).toBeNull(); | ||
expect(await screen.queryByText('node7')).toBeNull(); | ||
}) | ||
|
||
it('show success message after deletion', async () => { | ||
markNodesAsGone.mockImplementation(async () => ['node6', 'node7']); | ||
render(<DeleteNodesPage />); | ||
fireEvent.click(await screen.findByText('node6')); | ||
fireEvent.click(await screen.findByText('node7')); | ||
fireEvent.click(await screen.findByRole('button', { name: /delete/i })); | ||
expect(await screen.findByText(/successfully deleted/i)).toBeVisible(); | ||
}) | ||
}) |
34 changes: 34 additions & 0 deletions
34
plugins/lime-plugin-reachable-nodes/networkNodes.stories.js
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,34 @@ | ||
import { NetworkNodesPage_ } from "./src/networkNodesPage"; | ||
import { DeleteNodesPage_ } from "./src/containers/deleteNodesPage"; | ||
|
||
export default { | ||
title: 'Containers/Network nodes', | ||
}; | ||
|
||
const nodes = [ | ||
{ hostname: "ql-czuk", status: "connected" }, | ||
{ hostname: "ql-irene", status: "connected" }, | ||
{ hostname: "ql-ipem", status: "connected" }, | ||
{ hostname: "ql-czuck-bbone", status: "connected" }, | ||
{ hostname: "ql-graciela", status: "connected" }, | ||
{ hostname: "ql-marisa", status: "connected" }, | ||
{ hostname: "ql-anaymarcos", status: "connected" }, | ||
{ hostname: "ql-quinteros", status: "connected" }, | ||
{ hostname: "ql-guada", status: "connected" }, | ||
{ hostname: "ql-refu-bbone", status: "disconnected" }, | ||
{ hostname: "si-soniam", status: "disconnected" }, | ||
{ hostname: "si-giordano", status: "disconnected" }, | ||
{ hostname: "si-mario", status: "disconnected" }, | ||
{ hostname: "si-manu", status: "disconnected" }, | ||
]; | ||
|
||
export const networkNodesPage = () => ( | ||
<NetworkNodesPage_ nodes={nodes} /> | ||
) | ||
|
||
export const deleteNodesPage = (args) => ( | ||
<DeleteNodesPage_ nodes={nodes} {...args} /> | ||
) | ||
deleteNodesPage.argTypes = { | ||
onDelete: { action: 'deleted' } | ||
} |
86 changes: 86 additions & 0 deletions
86
plugins/lime-plugin-reachable-nodes/src/containers/deleteNodesPage/deleteNodesPage.js
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,86 @@ | ||
import { h } from "preact"; | ||
import { List, ListItem } from 'components/list'; | ||
import Loading from 'components/loading'; | ||
import Toast from 'components/toast'; | ||
import { useEffect, useState } from 'preact/hooks'; | ||
import { useSet } from 'react-use'; | ||
import { useMarkNodesAsGone, useNetworkNodes } from '../../networkNodesQueries' | ||
import style from './style.less'; | ||
import I18n from 'i18n-js'; | ||
|
||
export const DeleteNodesPage_ = ({ nodes, onDelete, isSubmitting, isSuccess }) => { | ||
const [selectedNodes, { toggle, has, reset }] = useSet(new Set([])); | ||
const [showSuccess, setshowSuccess] = useState(false); | ||
const disconnectedNodes = nodes.filter(n => n.status === "disconnected"); | ||
|
||
useEffect(() => { | ||
if (isSuccess) { | ||
reset(); | ||
setshowSuccess(true); | ||
setTimeout(() => { | ||
setshowSuccess(false); | ||
}, 2000); | ||
} | ||
}, [isSuccess]) | ||
|
||
return ( | ||
<div class="d-flex flex-column flex-grow-1 overflow-auto "> | ||
<div class="d-flex flex-column flex-grow-1 overflow-auto container container-padded"> | ||
<h4>{I18n.t("Delete Nodes")}</h4> | ||
{disconnectedNodes.length > 0 && | ||
<p>{I18n.t("Select the nodes which no longer belong to the network and " | ||
+ "delete them from the list of disconnected nodes")}</p> | ||
} | ||
{disconnectedNodes.length === 0 && | ||
<p>{I18n.t("There are no left discconected nodes")}</p> | ||
} | ||
<List> | ||
{disconnectedNodes.map(node => | ||
<ListItem key={node.hostname} onClick={() => toggle(node.hostname)} > | ||
<div class={style.nodeItem} > | ||
<input type="checkbox" name="selected-nodes" id={node.hostname} | ||
checked={has(node.hostname)} /> | ||
{node.hostname} | ||
</div> | ||
</ListItem> | ||
)} | ||
</List> | ||
</div> | ||
{selectedNodes.size >= 1 && | ||
<div class={style.bottomAction}> | ||
<span> | ||
{[selectedNodes.size, | ||
I18n.t('selected-nodes', { count: selectedNodes.size }) | ||
].join(' ')} | ||
</span> | ||
{!isSubmitting && | ||
<button class="ml-auto" onClick={() => onDelete([...selectedNodes])}> | ||
{I18n.t("Delete")} | ||
</button> | ||
} | ||
{isSubmitting && | ||
<div class="ml-auto"> | ||
<Loading /> | ||
</div> | ||
} | ||
</div> | ||
} | ||
{showSuccess && | ||
<Toast type={"success"} text={I18n.t("Successfully deleted")} /> | ||
} | ||
</div> | ||
) | ||
}; | ||
|
||
const DeleteNodesPage = () => { | ||
const [deleteNodes, { isSubmitting, isSuccess }] = useMarkNodesAsGone(); | ||
const { data: nodes, isLoading } = useNetworkNodes(); | ||
if (isLoading) { | ||
return <div className="container container-center"><Loading /></div> | ||
} | ||
|
||
return <DeleteNodesPage_ nodes={nodes} onDelete={deleteNodes} | ||
isSubmitting={isSubmitting} isSuccess={isSuccess} /> | ||
} | ||
|
||
export default DeleteNodesPage; |
2 changes: 2 additions & 0 deletions
2
plugins/lime-plugin-reachable-nodes/src/containers/deleteNodesPage/index.js
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,2 @@ | ||
export * from './deleteNodesPage'; | ||
export { default } from './deleteNodesPage'; |
Oops, something went wrong.