Skip to content

Commit

Permalink
chore(delete-nodes): separate into its own plugin
Browse files Browse the repository at this point in the history
Currently each plugin match one to one with a menu item.
We want one menu item for deleting nodes, so a plugin for it
is created. In the future it will be usefull to keep related
functionality in the same plugin, and allow them to have more
than one menu item associated.
  • Loading branch information
germanferrero committed Oct 14, 2021
1 parent 5c70a0a commit 6388915
Show file tree
Hide file tree
Showing 13 changed files with 178 additions and 33 deletions.
1 change: 0 additions & 1 deletion bkp/src/networkFirmwaresStyle.less

This file was deleted.

5 changes: 3 additions & 2 deletions i18n/generic.json
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@
"reload_3e45154f": "Reload",
"reload_page_2d381199": "Reload page",
"remote_support_9ba7a3a7": "Remote Support",
"remove_nodes_2dd26e14": "Remove Nodes",
"rescan_dff042fc": "Rescan",
"retry_ebd5f8ba": "Retry",
"revert_702e7694": "Revert",
Expand All @@ -147,7 +148,7 @@
"select_file_71aa4113": "Select file",
"select_new_node_5b2e9165": "Select new node",
"select_one_b647b384": "Select one",
"select_the_nodes_which_no_longer_belong_to_the_net_8ed27f05": "Select the nodes which no longer belong to the network and delete them from the list of disconnected nodes",
"select_the_nodes_which_no_longer_belong_to_the_net_92f853ef": "Select the nodes which no longer belong to the network and delete them from the list of unreachable nodes",
"set_network_bcd0ea96": "Set network",
"setting_network_21ebac51": "Setting network",
"setting_up_new_password_4daf8f1c": "Setting up new password",
Expand All @@ -170,7 +171,7 @@
"the_selected_image_is_not_valid_for_the_target_dev_cea9b494": "The selected image is not valid for the target device",
"the_shared_password_has_been_chosen_by_the_communi_f9d30a92": "The shared password has been chosen by the community when the network was created. You can ask other community members for it.",
"the_upgrade_should_be_done_d66854": "The upgrade should be done",
"there_are_no_left_discconected_nodes_cd78852e": "There are no left discconected nodes",
"there_are_no_left_unreachable_nodes_c0bec63d": "There are no left unreachable nodes",
"there_s_an_active_remote_support_session_4a40a8bb": "There's an active remote support session",
"there_s_no_open_session_for_remote_support_click_a_efd0d415": "There's no open session for remote support. Click at Create Session to begin one",
"these_are_the_nodes_associated_on_this_radio_3d302167": "These are the nodes associated on this radio",
Expand Down
5 changes: 3 additions & 2 deletions i18n/translations/es.json
Original file line number Diff line number Diff line change
Expand Up @@ -207,10 +207,11 @@
"other": "nodos seleccionados"
},
"successfully_deleted_23ce0a20": "Eliminado/s correctamente",
"there_are_no_left_discconected_nodes_cd78852e": "No hay nodos desconectados",
"these_are_the_nodes_with_which_you_do_not_have_con_ef5cc209": "Son los nodos con los que no tienes conectividad, es posible que no estén encendidos o que algún enlace para llegar a ellos esté caído.",
"these_are_the_nodes_with_which_you_have_connectivi_ef11819b": "Son los nodos con los que tienes conectividad, es decir que hay un camino funcionando entre tu nodo y cada uno de ellos.",
"if_some_of_these_nodes_no_longer_belong_to_the_net_a75d316f": "Si alguno de estos nodos no pertence más a la red puedes eliminarlo desde Eliminar Nodos.",
"select_the_nodes_which_no_longer_belong_to_the_net_8ed27f05": "Selecciona los nodos que ya no pertenecen a la red y elimínalos de la lista de nodos desconectados"
"remove_nodes_2dd26e14": "Baja de Nodos",
"select_the_nodes_which_no_longer_belong_to_the_net_92f853ef": "Selecciona los nodos que ya no pertenecen a la red y elimínalos de la lista de nodos inalcanzables",
"there_are_no_left_unreachable_nodes_c0bec63d": "No hay nodos inalcanzables"
}

78 changes: 78 additions & 0 deletions plugins/lime-plugin-delete-nodes/deleteNodes.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import { h } from 'preact';
import { fireEvent, act, screen } from '@testing-library/preact';
import '@testing-library/jest-dom';
import waitForExpect from 'wait-for-expect';

import DeleteNodesPage from './src/deleteNodesPage';
import queryCache from 'utils/queryCache';
import { getNodes, markNodesAsGone } from 'plugins/lime-plugin-network-nodes/src/networkNodesApi';
import { render } from 'utils/test_utils';

jest.mock('plugins/lime-plugin-network-nodes/src/networkNodesApi');

describe('delete nodes page', () => {
beforeEach(() => {
getNodes.mockImplementation(async () => [
{ hostname: 'node1', status: 'recently_connected' },
{ hostname: 'node2', status: 'recently_connected' },
{ hostname: 'node3', status: 'recently_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());
getNodes.mockClear();
markNodesAsGone.mockClear();
});

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();
})
})
21 changes: 21 additions & 0 deletions plugins/lime-plugin-delete-nodes/deleteNodes.stories.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { DeleteNodesPage_ } from './src/deleteNodesPage';

export default {
title: 'Containers/Remove Nodes'
};

const nodes = [
{ hostname: "ql-refu-bbone", status: "unreachable" },
{ hostname: "si-soniam", status: "unreachable" },
{ hostname: "si-giordano", status: "unreachable" },
{ hostname: "si-mario", status: "unreachable" },
{ hostname: "si-manu", status: "unreachable" },
];

export const deleteNodesPage = (args) => (
<DeleteNodesPage_ nodes={nodes} {...args} />
);

deleteNodesPage.argTypes = {
onDelete: { action: 'deleted' }
};
9 changes: 9 additions & 0 deletions plugins/lime-plugin-delete-nodes/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import Page from './src/deleteNodesPage';
import Menu from './src/deleteNodesMenu';

export default {
name: 'deleteNodes',
page: Page,
menu: Menu,
isCommunityProtected: false
};
8 changes: 8 additions & 0 deletions plugins/lime-plugin-delete-nodes/src/deleteNodesMenu.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { h } from 'preact';
import I18n from 'i18n-js';

const Menu = () => (
<a href={'#/deletenodes'}>{I18n.t('Remove Nodes')}</a>
);

export default Menu;
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ 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 { useMarkNodesAsGone, useNetworkNodes } from 'plugins/lime-plugin-network-nodes/src/networkNodesQueries'
import style from './style.less';
import I18n from 'i18n-js';

Expand All @@ -29,10 +29,10 @@ export const DeleteNodesPage_ = ({ nodes, onDelete, isSubmitting, isSuccess }) =
<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>
+ "delete them from the list of unreachable nodes")}</p>
}
{disconnectedNodes.length === 0 &&
<p>{I18n.t("There are no left discconected nodes")}</p>
<p>{I18n.t("There are no left unreachable nodes")}</p>
}
<List>
{disconnectedNodes.map(node =>
Expand All @@ -46,25 +46,25 @@ export const DeleteNodesPage_ = ({ nodes, onDelete, isSubmitting, isSuccess }) =
)}
</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>
}
<div class={style.bottomAction}>
<span>
{[selectedNodes.size,
I18n.t('selected-nodes', { count: selectedNodes.size })
].join(' ')}
</span>
{!isSubmitting &&
<button class="ml-auto"
onClick={() => onDelete([...selectedNodes])}
disabled={selectedNodes.size < 1}>
{I18n.t("Delete")}
</button>
}
{isSubmitting &&
<div class="ml-auto">
<Loading />
</div>
}
</div>
{showSuccess &&
<Toast type={"success"} text={I18n.t("Successfully deleted")} />
}
Expand Down
8 changes: 6 additions & 2 deletions plugins/lime-plugin-network-nodes/src/networkNodesApi.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@ import api from 'utils/uhttpd.service';

export const getNodes = () =>
api.call('network-nodes', 'get_nodes', {}).toPromise()
.then(res => res.nodes);
.then(res => {
return res.nodes;
});

export const markNodesAsGone = () => api.call('network-nodes', 'mark_nodes_as_gone', {}).toPromise();
export const markNodesAsGone = (hostnames) =>
api.call('network-nodes', 'mark_nodes_as_gone', { hostnames: hostnames }).toPromise()
.then(() => hostnames);
14 changes: 14 additions & 0 deletions plugins/lime-plugin-network-nodes/src/networkNodesApi.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,17 @@ describe('getNodes', () => {
expect(await getNodes()).toEqual(nodes);
});
});

describe('markNodesAsGone', () => {
it('calls the expected endpoint', async () => {
api.call.mockImplementation(() => of({ status: 'ok' }))
await markNodesAsGone(['node1']);
expect(api.call).toBeCalledWith('network-nodes', 'mark_nodes_as_gone', { hostnames: ['node1'] })
})

it('resolve to hostnames passed as parameters on success', async() => {
api.call.mockImplementation(() => of({status: 'ok'}))
const result = await markNodesAsGone(['node1', 'node2'])
expect(result).toEqual(['node1', 'node2'])
})
});
16 changes: 14 additions & 2 deletions plugins/lime-plugin-network-nodes/src/networkNodesQueries.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
import { useQuery } from 'react-query';
import { getNodes } from './networkNodesApi';
import { useQuery, useMutation } from 'react-query';
import { getNodes, markNodesAsGone } from './networkNodesApi';
import queryCache from 'utils/queryCache';

export const useNetworkNodes = () =>
useQuery(['network-nodes', 'get_nodes'], getNodes);

export const useMarkNodesAsGone = () => useMutation(markNodesAsGone, {
onSuccess: hostnames => queryCache.setQueryData(['network-nodes', 'get_nodes'],
old => {
const result = old.map(
node => hostnames.indexOf(node.hostname) != -1 ? { ...node, status: "gone" } : node
)
return result;
}
)
})

This file was deleted.

0 comments on commit 6388915

Please sign in to comment.