Skip to content
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

[NMS] Add subscriber deletion code #2504

Merged
merged 1 commit into from Aug 28, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -31,7 +31,7 @@ export type SubscriberContextType = {
state: {[string]: subscriber},
metrics?: {[string]: Metrics},
gwSubscriberMap: {[gateway_id]: Array<subscriber_id>},
setState?: (key: string, val: mutable_subscriber) => Promise<void>,
setState?: (key: string, val?: mutable_subscriber) => Promise<void>,
};

export default React.createContext<SubscriberContextType>({});
Expand Up @@ -359,4 +359,102 @@ describe('use Lte Context testing', () => {
);
}
});

test('verify subscriber context', async () => {
MagmaAPIBindings.getLteByNetworkIdSubscribers.mockResolvedValue(
mockSubscribers,
);

const {result, waitForNextUpdate} = renderHook(
() => useLteContext('network1', LTE, false),
{},
);
await act(async () => {
// State is updated when we wait for the update, so we need this wrapped
// in act
await waitForNextUpdate();
});
expect(MagmaAPIBindings.getLteByNetworkIdSubscribers).toHaveBeenCalledTimes(
1,
);

if (!result.current) {
throw 'result invalid';
}
let {subscriberCtx} = result.current;
expect(subscriberCtx.state).toStrictEqual(mockSubscribers);

// verify subscriber add
const newSubscriber = {
active_apns: ['oai.ipv4'],
id: 'IMSI722070171001003',
lte: {
auth_algo: 'MILENAGE',
auth_key: 'i69HPy+P0JSHzMvXCXxoYg==',
auth_opc: 'jie2rw5pLnUPMmZ6OxRgXQ==',
state: 'ACTIVE',
sub_profile: 'default',
},
};
MagmaAPIBindings.getLteByNetworkIdSubscribersBySubscriberId.mockResolvedValue(
newSubscriber,
);
MagmaAPIBindings.postLteByNetworkIdSubscribers.mockResolvedValue({
success: true,
});

// create subscriber
subscriberCtx.setState?.('IMSI722070171001003', newSubscriber);
await waitForNextUpdate();
expect(
MagmaAPIBindings.postLteByNetworkIdSubscribers,
).toHaveBeenCalledTimes(1);
expect(
MagmaAPIBindings.getLteByNetworkIdSubscribersBySubscriberId,
).toHaveBeenCalledTimes(1);

if (!result.current) {
throw 'result invalid';
}
subscriberCtx = result.current.subscriberCtx;
expect(subscriberCtx?.state['IMSI722070171001003']).toBe(newSubscriber);
MagmaAPIBindings.getLteByNetworkIdSubscribersBySubscriberId.mockClear();

// update subscriber
newSubscriber.lte.state = 'INACTIVE';
subscriberCtx.setState?.('IMSI722070171001003', newSubscriber);
MagmaAPIBindings.putLteByNetworkIdSubscribersBySubscriberId.mockResolvedValue(
{success: true},
);
MagmaAPIBindings.getLteByNetworkIdSubscribersBySubscriberId.mockResolvedValue(
newSubscriber,
);
await waitForNextUpdate();
expect(
MagmaAPIBindings.putLteByNetworkIdSubscribersBySubscriberId,
).toHaveBeenCalledTimes(1);
expect(
MagmaAPIBindings.getLteByNetworkIdSubscribersBySubscriberId,
).toHaveBeenCalledTimes(1);
if (!result.current) {
throw 'result invalid';
}
subscriberCtx = result.current.subscriberCtx;
expect(subscriberCtx?.state['IMSI722070171001003']).toBe(newSubscriber);

// delete subscriber
subscriberCtx.setState?.('IMSI722070171001003');
MagmaAPIBindings.deleteLteByNetworkIdSubscribersBySubscriberId.mockResolvedValue(
{success: true},
);
await waitForNextUpdate();
expect(
MagmaAPIBindings.deleteLteByNetworkIdSubscribersBySubscriberId,
).toHaveBeenCalledTimes(1);
if (!result.current) {
throw 'result invalid';
}
subscriberCtx = result.current.subscriberCtx;
expect('IMSI722070171001003' in subscriberCtx?.state).toBeFalse;
});
});
3 changes: 2 additions & 1 deletion nms/app/packages/magmalte/app/state/SubscriberState.js
Expand Up @@ -119,7 +119,8 @@ export async function setSubscriberState(props: SubscriberStateProps) {
},
);
if (subscriber) {
setSubscriberMap({...subscriberMap, [key]: subscriber});
const newSubscriberMap = {...subscriberMap, [key]: subscriber};
setSubscriberMap(newSubscriberMap);
}
} else {
await MagmaV1API.deleteLteByNetworkIdSubscribersBySubscriberId({
Expand Down
196 changes: 116 additions & 80 deletions nms/app/packages/magmalte/app/views/subscriber/SubscriberOverview.js
Expand Up @@ -13,6 +13,8 @@
* @flow strict-local
* @format
*/
import type {WithAlert} from '@fbcnms/ui/components/Alert/withAlert';

import ActionTable from '../../components/ActionTable';
import AddSubscriberButton from './SubscriberAddDialog';
import Button from '@material-ui/core/Button';
Expand All @@ -24,11 +26,13 @@ import React from 'react';
import SubscriberContext from '../../components/context/SubscriberContext';
import SubscriberDetail from './SubscriberDetail';
import TopBar from '../../components/TopBar';
import withAlert from '@fbcnms/ui/components/Alert/withAlert';

import {Redirect, Route, Switch} from 'react-router-dom';
import {colors, typography} from '../../theme/default';
import {makeStyles} from '@material-ui/styles';
import {useContext, useState} from 'react';
import {useEnqueueSnackbar} from '@fbcnms/ui/hooks/useSnackbar';
import {useRouter} from '@fbcnms/ui/hooks';

const TITLE = 'Subscribers';
Expand Down Expand Up @@ -84,12 +88,6 @@ type SubscriberRowType = {

function SubscriberDashboardInternal() {
const classes = useStyles();
const {history, relativeUrl} = useRouter();
const [currRow, setCurrRow] = useState<SubscriberRowType>({});
const ctx = useContext(SubscriberContext);
const subscriberMap = ctx.state;
const subscriberMetrics = ctx.metrics;

return (
<>
<TopBar
Expand Down Expand Up @@ -121,85 +119,123 @@ function SubscriberDashboardInternal() {
},
]}
/>
<SubscriberTable />
</>
);
}

<div className={classes.dashboardRoot}>
<Grid container spacing={4}>
<Grid item xs={12}>
<CardTitleRow
icon={PeopleIcon}
label={TITLE}
filter={AddSubscriberButton}
/>
function SubscriberTableRaw(props: WithAlert) {
const classes = useStyles();
const {history, relativeUrl} = useRouter();
const [currRow, setCurrRow] = useState<SubscriberRowType>({});
const ctx = useContext(SubscriberContext);
const subscriberMap = ctx.state;
const subscriberMetrics = ctx.metrics;
const enqueueSnackbar = useEnqueueSnackbar();

{subscriberMap ? (
<ActionTable
data={Object.keys(subscriberMap).map((imsi: string) => {
const subscriberInfo = subscriberMap[imsi];
const metrics = subscriberMetrics?.[`${imsi}`];
return {
name: subscriberInfo.name ?? imsi,
imsi: imsi,
service: subscriberInfo.lte.state,
currentUsage: metrics?.currentUsage ?? '0',
dailyAvg: metrics?.dailyAvg ?? '0',
lastReportedTime: new Date(
subscriberInfo.monitoring?.icmp?.last_reported_time ?? 0,
),
};
})}
columns={[
{title: 'Name', field: 'name'},
{
title: 'IMSI',
field: 'imsi',
render: currRow => (
<Link
variant="body2"
component="button"
onClick={() =>
history.push(relativeUrl('/' + currRow.imsi))
}>
{currRow.imsi}
</Link>
),
},
{title: 'Service', field: 'service', width: 100},
{title: 'Current Usage', field: 'currentUsage', width: 175},
{title: 'Daily Average', field: 'dailyAvg', width: 175},
{
title: 'Last Reported Time',
field: 'lastReportedTime',
type: 'datetime',
width: 200,
return (
<div className={classes.dashboardRoot}>
<Grid container spacing={4}>
<Grid item xs={12}>
<CardTitleRow
icon={PeopleIcon}
label={TITLE}
filter={AddSubscriberButton}
/>

{subscriberMap ? (
<ActionTable
data={Object.keys(subscriberMap).map((imsi: string) => {
const subscriberInfo = subscriberMap[imsi];
const metrics = subscriberMetrics?.[`${imsi}`];
return {
name: subscriberInfo.name ?? imsi,
imsi: imsi,
service: subscriberInfo.lte.state,
currentUsage: metrics?.currentUsage ?? '0',
dailyAvg: metrics?.dailyAvg ?? '0',
lastReportedTime: new Date(
subscriberInfo.monitoring?.icmp?.last_reported_time ?? 0,
),
};
})}
columns={[
{title: 'Name', field: 'name'},
{
title: 'IMSI',
field: 'imsi',
render: currRow => (
<Link
variant="body2"
component="button"
onClick={() =>
history.push(relativeUrl('/' + currRow.imsi))
}>
{currRow.imsi}
</Link>
),
},
{title: 'Service', field: 'service', width: 100},
{title: 'Current Usage', field: 'currentUsage', width: 175},
{title: 'Daily Average', field: 'dailyAvg', width: 175},
{
title: 'Last Reported Time',
field: 'lastReportedTime',
type: 'datetime',
width: 200,
},
]}
handleCurrRow={(row: SubscriberRowType) => setCurrRow(row)}
menuItems={[
{
name: 'View',
handleFunc: () => {
history.push(relativeUrl('/' + currRow.imsi));
},
]}
handleCurrRow={(row: SubscriberRowType) => setCurrRow(row)}
menuItems={[
{
name: 'View',
handleFunc: () => {
history.push(relativeUrl('/' + currRow.imsi));
},
},
{
name: 'Edit',
handleFunc: () => {
history.push(relativeUrl('/' + currRow.imsi + '/config'));
},
{
name: 'Edit',
handleFunc: () => {
history.push(relativeUrl('/' + currRow.imsi + '/config'));
},
},
{
name: 'Remove',
handleFunc: () => {
props
.confirm(
`Are you sure you want to delete ${currRow.imsi}?`,
)
.then(async confirmed => {
if (!confirmed) {
return;
}

try {
await ctx.setState?.(currRow.imsi);
} catch (e) {
enqueueSnackbar(
'failed deleting subscriber ' + currRow.imsi,
{
variant: 'error',
},
);
}
});
},
{name: 'Remove'},
]}
options={{
actionsColumnIndex: -1,
pageSizeOptions: [5, 10],
}}
/>
) : (
'<Text>No Subscribers Found</Text>'
)}
</Grid>
},
]}
options={{
actionsColumnIndex: -1,
pageSizeOptions: [10, 20],
}}
/>
) : (
'<Text>No Subscribers Found</Text>'
)}
</Grid>
</div>
</>
</Grid>
</div>
);
}
const SubscriberTable = withAlert(SubscriberTableRaw);