Skip to content

Commit

Permalink
Add an ActivityLogOverview Components
Browse files Browse the repository at this point in the history
  • Loading branch information
nelsonkopliku committed Jul 16, 2024
1 parent 0efc387 commit 0ae7510
Show file tree
Hide file tree
Showing 6 changed files with 430 additions and 0 deletions.
149 changes: 149 additions & 0 deletions assets/js/common/ActivityLogOverview/ActivityLogOverview.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
import React from 'react';
import { format } from 'date-fns';
import Table from '@common/Table';

import {
ACTIVITY_TYPES,
API_KEY_GENERATION,
CHANGING_SUMA_SETTINGS,
CLEARING_SUMA_SETTINGS,
CLUSTER_CHECKS_EXECUTION_REQUEST,
LOGIN_ATTEMPT,
PROFILE_UPDATE,
RESOURCE_TAGGING,
RESOURCE_UNTAGGING,
SAVING_SUMA_SETTINGS,
USER_CREATION,
USER_DELETION,
USER_MODIFICATION,
} from '@lib/model/activityLog';

const activityTypesLabels = {
[LOGIN_ATTEMPT]: 'Login Attempt',
[RESOURCE_TAGGING]: 'Tag Added',
[RESOURCE_UNTAGGING]: 'Tag Removed',
[API_KEY_GENERATION]: 'API Key Generated',
[SAVING_SUMA_SETTINGS]: 'SUMA Settings Saved',
[CHANGING_SUMA_SETTINGS]: 'SUMA Settings Changed',
[CLEARING_SUMA_SETTINGS]: 'SUMA Settings Cleared',
[USER_CREATION]: 'User Created',
[USER_MODIFICATION]: 'User Modified',
[USER_DELETION]: 'User Deleted',
[PROFILE_UPDATE]: 'Profile Updated',
[CLUSTER_CHECKS_EXECUTION_REQUEST]: 'Checks Execution Requested',
};

const resourceTypesLabels = {
host: 'Host',
cluster: 'Cluster',
database: 'Database',
sap_system: 'SAP System',
};

const toResource = (activityLogEntry) => {
const { metadata, type } = activityLogEntry;
switch (type) {
case LOGIN_ATTEMPT:
return 'Application';
case RESOURCE_TAGGING:
case RESOURCE_UNTAGGING:
return resourceTypesLabels[metadata.resource_type];
case USER_CREATION:
case USER_MODIFICATION:
case USER_DELETION:
return 'User';
case PROFILE_UPDATE:
return 'Profile';
case SAVING_SUMA_SETTINGS:
case CHANGING_SUMA_SETTINGS:
case CLEARING_SUMA_SETTINGS:
return 'SUMA Settings';
case API_KEY_GENERATION:
return 'API Key';
case CLUSTER_CHECKS_EXECUTION_REQUEST:
return 'Cluster Checks';
default:
return 'Unrecognized resource';
}
};

const toMessage = (activityLogEntry) => {
const { metadata, type } = activityLogEntry;

switch (type) {
case LOGIN_ATTEMPT:
return metadata?.reason ? 'Login failed' : 'User logged in';
case RESOURCE_TAGGING:
return `Tag "${metadata.added_tag}" added to "${metadata.resource_id}"`;
case RESOURCE_UNTAGGING:
return `Tag "${metadata.removed_tag}" removed from "${metadata.resource_id}"`;
case USER_CREATION:
return 'User was created';
case USER_MODIFICATION:
return 'User was modified';
case USER_DELETION:
return 'User was deleted';
case PROFILE_UPDATE:
return 'User modified profile';
case SAVING_SUMA_SETTINGS:
return 'SUMA Settings was saved';
case CHANGING_SUMA_SETTINGS:
return 'SUMA Settings was changed';
case CLEARING_SUMA_SETTINGS:
return 'SUMA Settings was cleared';
case API_KEY_GENERATION:
return 'API Key was generated';
case CLUSTER_CHECKS_EXECUTION_REQUEST:
return 'Checks execution requested for cluster';
default:
return 'Unrecognized activity';
}
};

const activityLogTableConfig = {
pagination: true,
usePadding: false,
columns: [
{
title: 'Time',
key: 'occurred_on',
render: (time) => format(time, 'yyyy-MM-dd HH:mm:ss'),
},
{
title: 'Event Type',
key: 'type',
render: (type) => (
<span aria-label="activity-log-type">
{ACTIVITY_TYPES.includes(type)
? activityTypesLabels[type]
: 'Unknown'}
</span>
),
},
{
title: 'Resource',
key: 'metadata',
render: (_, activityLogEntry) => (
<span aria-label="activity-log-resource">
{toResource(activityLogEntry)}
</span>
),
},
{
title: 'User',
key: 'actor',
},
{
title: 'Message',
key: 'metadata',
render: (_, activityLogEntry) => toMessage(activityLogEntry),
},
],
collapsibleDetailRenderer: ({ metadata }) => (
<pre>{JSON.stringify(metadata, null, 2)}</pre>
),
};

export default function ActivityLogOverview({ activityLog }) {
return <Table config={activityLogTableConfig} data={activityLog} />;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { activityLogEntryFactory } from '@lib/test-utils/factories/activityLog';
import ActivityLogOverview from './ActivityLogOverview';

export default {
title: 'Components/ActivityLogOverview',
component: ActivityLogOverview,
argTypes: {
activityLog: {
description: 'List of the activity log entries',
control: {
type: 'array',
},
},
},
};

export const Default = {
args: {
activityLog: activityLogEntryFactory.buildList(20),
},
};

export const Empty = {
args: {
activityLog: [],
},
};
178 changes: 178 additions & 0 deletions assets/js/common/ActivityLogOverview/ActivityLogOverview.test.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
import React from 'react';
import { render, screen } from '@testing-library/react';
import {
activityLogEntryFactory,
taggingMetadataFactory,
untaggingMetadataFactory,
} from '@lib/test-utils/factories/activityLog';
import {
LOGIN_ATTEMPT,
RESOURCE_TAGGING,
RESOURCE_UNTAGGING,
API_KEY_GENERATION,
CHANGING_SUMA_SETTINGS,
CLEARING_SUMA_SETTINGS,
SAVING_SUMA_SETTINGS,
CLUSTER_CHECKS_EXECUTION_REQUEST,
PROFILE_UPDATE,
USER_CREATION,
USER_DELETION,
USER_MODIFICATION,
} from '@lib/model/activityLog';
import '@testing-library/jest-dom';

import ActivityLogOverview from '.';

describe('Activity Log Overview', () => {
it('should render an empty activity log', () => {
render(<ActivityLogOverview activityLog={[]} />);

expect(screen.getByText('No data available')).toBeVisible();
});

const scenarios = [
{
name: LOGIN_ATTEMPT,
entry: activityLogEntryFactory.build({
actor: 'admin',
type: LOGIN_ATTEMPT,
}),
expectedEventType: 'Login Attempt',
expectedResource: 'Application',
expectedUser: 'admin',
},
{
name: RESOURCE_TAGGING,
entry: activityLogEntryFactory.build({
actor: 'foo',
type: RESOURCE_TAGGING,
metadata: taggingMetadataFactory.build({
resource_type: 'host',
}),
}),
expectedEventType: 'Tag Added',
expectedResource: 'Host',
expectedUser: 'foo',
},
{
name: RESOURCE_UNTAGGING,
entry: activityLogEntryFactory.build({
actor: 'bar',
type: RESOURCE_UNTAGGING,
metadata: untaggingMetadataFactory.build({
resource_type: 'cluster',
}),
}),
expectedEventType: 'Tag Removed',
expectedResource: 'Cluster',
expectedUser: 'bar',
},
{
name: API_KEY_GENERATION,
entry: activityLogEntryFactory.build({
actor: 'baz',
type: API_KEY_GENERATION,
}),
expectedEventType: 'API Key Generated',
expectedResource: 'API Key',
expectedUser: 'baz',
},
{
name: SAVING_SUMA_SETTINGS,
entry: activityLogEntryFactory.build({
actor: 'user-1',
type: SAVING_SUMA_SETTINGS,
}),
expectedEventType: 'SUMA Settings Saved',
expectedResource: 'SUMA Settings',
expectedUser: 'user-1',
},
{
name: CHANGING_SUMA_SETTINGS,
entry: activityLogEntryFactory.build({
actor: 'user-2',
type: CHANGING_SUMA_SETTINGS,
}),
expectedEventType: 'SUMA Settings Changed',
expectedResource: 'SUMA Settings',
expectedUser: 'user-2',
},
{
name: CLEARING_SUMA_SETTINGS,
entry: activityLogEntryFactory.build({
actor: 'user-3',
type: CLEARING_SUMA_SETTINGS,
}),
expectedEventType: 'SUMA Settings Cleared',
expectedResource: 'SUMA Settings',
expectedUser: 'user-3',
},
{
name: USER_CREATION,
entry: activityLogEntryFactory.build({
actor: 'user-4',
type: USER_CREATION,
}),
expectedEventType: 'User Created',
expectedResource: 'User',
expectedUser: 'user-4',
},
{
name: USER_MODIFICATION,
entry: activityLogEntryFactory.build({
actor: 'user-5',
type: USER_MODIFICATION,
}),
expectedEventType: 'User Modified',
expectedResource: 'User',
expectedUser: 'user-5',
},
{
name: USER_DELETION,
entry: activityLogEntryFactory.build({
actor: 'user-6',
type: USER_DELETION,
}),
expectedEventType: 'User Deleted',
expectedResource: 'User',
expectedUser: 'user-6',
},
{
name: PROFILE_UPDATE,
entry: activityLogEntryFactory.build({
actor: 'user-7',
type: PROFILE_UPDATE,
}),
expectedEventType: 'Profile Updated',
expectedResource: 'Profile',
expectedUser: 'user-7',
},
{
name: CLUSTER_CHECKS_EXECUTION_REQUEST,
entry: activityLogEntryFactory.build({
actor: 'user-8',
type: CLUSTER_CHECKS_EXECUTION_REQUEST,
}),
expectedEventType: 'Checks Execution Requested',
expectedResource: 'Cluster Checks',
expectedUser: 'user-8',
},
];

it.each(scenarios)(
'should render log entry for activity `$name`',
({ entry, expectedEventType, expectedResource, expectedUser }) => {
render(<ActivityLogOverview activityLog={[entry]} />);

const eventType = screen.getByLabelText('activity-log-type');
expect(eventType).toBeVisible();
expect(eventType).toHaveTextContent(expectedEventType);

const resource = screen.getByLabelText('activity-log-resource');
expect(resource).toBeVisible();
expect(resource).toHaveTextContent(expectedResource);

expect(screen.getByText(expectedUser)).toBeVisible();
}
);
});
3 changes: 3 additions & 0 deletions assets/js/common/ActivityLogOverview/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import ActivityLogOverview from './ActivityLogOverview';

export default ActivityLogOverview;
28 changes: 28 additions & 0 deletions assets/js/lib/model/activityLog.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
export const LOGIN_ATTEMPT = 'login_attempt';
export const RESOURCE_TAGGING = 'resource_tagging';
export const RESOURCE_UNTAGGING = 'resource_untagging';
export const API_KEY_GENERATION = 'api_key_generation';
export const SAVING_SUMA_SETTINGS = 'saving_suma_settings';
export const CHANGING_SUMA_SETTINGS = 'changing_suma_settings';
export const CLEARING_SUMA_SETTINGS = 'clearing_suma_settings';
export const USER_CREATION = 'user_creation';
export const USER_MODIFICATION = 'user_modification';
export const USER_DELETION = 'user_deletion';
export const PROFILE_UPDATE = 'profile_update';
export const CLUSTER_CHECKS_EXECUTION_REQUEST =
'cluster_checks_execution_request';

export const ACTIVITY_TYPES = [
LOGIN_ATTEMPT,
RESOURCE_TAGGING,
RESOURCE_UNTAGGING,
API_KEY_GENERATION,
SAVING_SUMA_SETTINGS,
CHANGING_SUMA_SETTINGS,
CLEARING_SUMA_SETTINGS,
USER_CREATION,
USER_MODIFICATION,
USER_DELETION,
PROFILE_UPDATE,
CLUSTER_CHECKS_EXECUTION_REQUEST,
];
Loading

0 comments on commit 0ae7510

Please sign in to comment.