Skip to content

Commit

Permalink
Add new filters, fix missing terms on pagination and reload
Browse files Browse the repository at this point in the history
Signed-off-by: Tomás Castillo <[email protected]>
  • Loading branch information
tomrndom committed Jul 11, 2024
1 parent 4fe8f82 commit 0d2c5ea
Show file tree
Hide file tree
Showing 4 changed files with 181 additions and 28 deletions.
47 changes: 37 additions & 10 deletions src/actions/audit-log-actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,34 +19,32 @@ import {
authErrorHandler,
escapeFilterValue
} from 'openstack-uicore-foundation/lib/utils/actions';
import {getAccessTokenSafely} from '../utils/methods';
import {getAccessTokenSafely,isNumericString, parseDateRangeFilter} from '../utils/methods';

export const CLEAR_LOG_PARAMS = 'CLEAR_LOG_PARAMS';
export const REQUEST_LOG = 'REQUEST_LOG';
export const RECEIVE_LOG = 'RECEIVE_LOG';

export const getAuditLog = (entityFilter = [], term = null, page = 1, perPage = 100, order = null, orderDir = 1) => async (dispatch, getState) => {
export const getAuditLog = (entityFilter = [], term = null, page = 1, perPage = 100, order = null, orderDir = 1, filters = {}) => async (dispatch, getState) => {

const {currentSummitState} = getState();
const accessToken = await getAccessTokenSafely();
const {currentSummit} = currentSummitState;
const filter = [`summit_id==${currentSummit.id}`];
const summitTZ = currentSummit.time_zone.name;
const summitFilter = [`summit_id==${currentSummit.id}`];

dispatch(startLoading());

if (term) {
const escapedTerm = escapeFilterValue(term);
filter.push(`user_email=@${escapedTerm},user_full_name=@${escapedTerm},action=@${escapedTerm}`);
}

const params = {
page: page,
per_page: perPage,
expand: 'user',
access_token: accessToken,
};

params['filter[]'] = [...filter, ...entityFilter];
const parsedFilters = [...summitFilter, ...entityFilter, ...parseFilters(filters, term)];

params['filter[]'] = parsedFilters;

// order
if (order != null && orderDir != null) {
Expand All @@ -59,13 +57,42 @@ export const getAuditLog = (entityFilter = [], term = null, page = 1, perPage =
createAction(RECEIVE_LOG),
`${window.API_BASE_URL}/api/v1/audit-logs`,
authErrorHandler,
{page, perPage, order, orderDir, term}
{page, perPage, order, orderDir, term, summitTZ, filters}
)(params)(dispatch).then(() => {
dispatch(stopLoading());
}
);
};

const parseFilters = (filters, term = null) => {
const filter = [];

if (filters.created_date_filter) {
parseDateRangeFilter(filter, filters.created_date_filter, 'created');
}

if (filters.hasOwnProperty('user_id_filter') && Array.isArray(filters.user_id_filter)
&& filters.user_id_filter.length > 0) {
filter.push(`user_id==${filters.user_id_filter.map(t => t.id).join('||')}`);
}

if (term) {
const escapedTerm = escapeFilterValue(term);
let searchString = '';

if (isNumericString(term)) {
searchString += `entity_id==${term}`;
} else {
searchString += `action=@${escapedTerm}`
}

filter.push(searchString);
}


return filter;
}

export const clearAuditLogParams = () => async (dispatch, getState) => {
dispatch(createAction(CLEAR_LOG_PARAMS)());
};
142 changes: 131 additions & 11 deletions src/components/audit-logs/index.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,26 @@
import React, {useEffect} from "react";
import {FreeTextSearch, Table} from "openstack-uicore-foundation/lib/components";
import React, { useEffect, useState } from "react";
import { FreeTextSearch, Table, Dropdown, MemberInput, DateTimePicker } from "openstack-uicore-foundation/lib/components";
import T from "i18n-react";
import {Pagination} from "react-bootstrap";
import {connect} from "react-redux";
import {clearAuditLogParams, getAuditLog} from "../../actions/audit-log-actions";
import { epochToMomentTimeZone } from 'openstack-uicore-foundation/lib/utils/methods'
import { Pagination } from "react-bootstrap";
import { connect } from "react-redux";
import { clearAuditLogParams, getAuditLog } from "../../actions/audit-log-actions";

const AuditLogs = ({ entityFilter = [], term, logEntries, perPage, lastPage, currentPage, order, orderDir, columns, getAuditLog, clearAuditLogParams, filters }) => {

const defaultFilters = {
user_id_filter: [],
created_date_filter: Array(2).fill(null)
}

const [enabledFilters, setEnabledFilters] = useState(Object.keys(filters).filter(e => Array.isArray(filters[e]) ? filters[e]?.some(e => e !== null) : filters[e]?.length > 0));
const [auditLogFilters, setAuditLogFilters] = useState({ ...defaultFilters, ...filters });

const filters_ddl = [
{ label: 'Created', value: 'created_date_filter' },
{ label: 'Member', value: 'user_id_filter' },
]

const AuditLogs = ({entityFilter = [], term, logEntries, perPage, lastPage, currentPage, order, orderDir, columns, getAuditLog, clearAuditLogParams}) => {
const audit_log_table_options = {
sortCol: order,
sortDir: orderDir,
Expand All @@ -22,19 +37,57 @@ const AuditLogs = ({entityFilter = [], term, logEntries, perPage, lastPage, curr
const show_columns = columns ? audit_log_columns.filter(c => columns.includes(c.columnKey)) : audit_log_columns;

const handleSort = (index, key, dir, func) => {
getAuditLog(entityFilter, term, currentPage, perPage, key, dir);
getAuditLog(entityFilter, term, currentPage, perPage, key, dir, auditLogFilters);
};

const handlePageChange = (page) => {
getAuditLog(entityFilter, term, page, perPage, order, orderDir);
getAuditLog(entityFilter, term, page, perPage, order, orderDir, auditLogFilters);
};

const handleSearch = (newTerm) => {
getAuditLog(entityFilter, newTerm, currentPage, perPage, order, orderDir);
getAuditLog(entityFilter, newTerm, currentPage, perPage, order, orderDir, auditLogFilters);
};

const handleDDLSortByLabel = (ddlArray) => {
return ddlArray.sort((a, b) => a.label.localeCompare(b.label));
}

const handleFiltersChange = (ev) => {
const { value } = ev.target;
if (value.length < enabledFilters.length) {
if (value.length === 0) {
setEnabledFilters(value);
setAuditLogFilters(defaultFilters);
} else {
const removedFilter = enabledFilters.filter(e => !value.includes(e))[0];
const defaultValue = Array.isArray(auditLogFilters[removedFilter]) ? [] : '';
let newEventFilters = { ...auditLogFilters, [removedFilter]: defaultValue };
setEnabledFilters(value);
setAuditLogFilters(newEventFilters);
}
} else {
setEnabledFilters(value);
}
}

const handleChangeDateFilter = (ev, lastDate) => {
const { value, id } = ev.target;
const newDateFilter = auditLogFilters[id];

setAuditLogFilters({ ...auditLogFilters, [id]: lastDate ? [newDateFilter[0], value.unix()] : [value.unix(), newDateFilter[1]] })
}

const handleAuditLogFilterChange = (ev) => {
let { value, type, id } = ev.target;
setAuditLogFilters({ ...auditLogFilters, [id]: value });
}

const handleApplyAuditLogFilters = () => {
getAuditLog(entityFilter, term, currentPage, perPage, order, orderDir, auditLogFilters);
}

useEffect(() => {
getAuditLog(entityFilter, term, 1, perPage, order, orderDir);
getAuditLog(entityFilter, term, 1, perPage, order, orderDir, filters);

return () => {
clearAuditLogParams();
Expand All @@ -54,6 +107,73 @@ const AuditLogs = ({entityFilter = [], term, logEntries, perPage, lastPage, curr
</div>
</div>

<hr />

<div className={'row'}>
<div className={'col-md-6'}>
<Dropdown
id="enabled_filters"
placeholder={'Enabled Filters'}
value={enabledFilters}
onChange={handleFiltersChange}
options={handleDDLSortByLabel(filters_ddl)}
isClearable={true}
isMulti={true}
/>
</div>
<div className={'col-md-6'}>
<button className="btn btn-primary right-space" onClick={handleApplyAuditLogFilters}>
{T.translate("audit_log.apply_filters")}
</button>
</div>
</div>
<div className='filters-row'>
{enabledFilters.includes('user_id_filter') &&
<div className="col-md-6">
<MemberInput
id="user_id_filter"
getOptionLabel={
(member) => {
return member.hasOwnProperty("email") ?
`${member.first_name} ${member.last_name} (${member.email})` :
`${member.first_name} ${member.last_name} (${member.id})`;
}
}
placeholder={T.translate("audit_log.placeholders.user_id")}
value={auditLogFilters.user_id_filter}
isMulti
isClearable
onChange={handleAuditLogFilterChange}
/>
</div>}
{enabledFilters.includes('created_date_filter') &&
<>
<div className={'col-md-3'}>
<DateTimePicker
id="created_date_filter"
format={{ date: "YYYY-MM-DD", time: "HH:mm" }}
inputProps={{ placeholder: T.translate("audit_log.placeholders.created_date_from") }}
onChange={(ev) => handleChangeDateFilter(ev, false)}
timezone={'UTC'}
value={epochToMomentTimeZone(auditLogFilters.created_date_filter[0], 'UTC')}
className={'event-list-date-picker'}
/>
</div>
<div className={'col-md-3'}>
<DateTimePicker
id="created_date_filter"
format={{ date: "YYYY-MM-DD", time: "HH:mm" }}
inputProps={{ placeholder: T.translate("audit_log.placeholders.created_date_to") }}
onChange={(ev) => handleChangeDateFilter(ev, true)}
timezone={'UTC'}
value={epochToMomentTimeZone(auditLogFilters.created_date_filter[1], 'UTC')}
className={'event-list-date-picker'}
/>
</div>
</>
}
</div>

{logEntries.length === 0 &&
<div>{T.translate("audit_log.no_log_entries")}</div>
}
Expand Down Expand Up @@ -85,7 +205,7 @@ const AuditLogs = ({entityFilter = [], term, logEntries, perPage, lastPage, curr
)
}

const mapStateToProps = ({auditLogState}) => ({
const mapStateToProps = ({ auditLogState }) => ({
...auditLogState,
});

Expand Down
8 changes: 6 additions & 2 deletions src/i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -2825,12 +2825,16 @@
"sponsorship_type": "Tier",
"no_log_entries": "No log entries found for current search criteria.",
"action": "Action",
"date": "Date",
"date": "Created",
"user": "User",
"event": "Event",
"order": "Order",
"apply_filters": "Apply Filters",
"placeholders": {
"search_log": "Search By Action / User Full Name"
"search_log": "Search By Action / User Full Name",
"user_id": "Filter by User",
"created_date_from": "Filter by Created Date From",
"created_date_to": "Filter by Created Date To"
}
},
"signage": {
Expand Down
12 changes: 7 additions & 5 deletions src/reducers/audit_log/audit-log-reducer.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,16 @@ import { LOGOUT_USER } from 'openstack-uicore-foundation/lib/security/actions';
import { formatAuditLog, parseSpeakerAuditLog } from '../../utils/methods';

const DEFAULT_STATE = {
term : '',
logEntries : [],
currentPage : 1,
lastPage : 1,
perPage : 10,
order : 'created',
orderDir : 1,
totalLogEntries : 0
totalLogEntries : 0,
summitTZ : '',
filters : {}
};

const auditLogReducer = (state = DEFAULT_STATE, action) => {
Expand All @@ -43,20 +46,19 @@ const auditLogReducer = (state = DEFAULT_STATE, action) => {
return DEFAULT_STATE;
}
case REQUEST_LOG: {
let {order, orderDir} = payload;
return {...state, order, orderDir}
let {term, order, orderDir, summitTZ} = payload;
return {...state, term, order, orderDir, summitTZ}
}
case RECEIVE_LOG: {
let { current_page, total, last_page } = payload.response;

let logEntries = payload.response.data.map(e => {
const logEntryAction = e.action.startsWith('Speaker') ? parseSpeakerAuditLog(e.action) : e.action
const userTimeZone = moment.tz.guess();
return {
...e,
event: e.event_id,
user: `${e.user.first_name} ${e.user.last_name} (${e.user.id})`,
created: moment(epochToMomentTimeZone(e.created, userTimeZone)).format('MMMM Do YYYY, h:mm a'),
created: moment(epochToMomentTimeZone(e.created, state.summitTZ)).format('MMMM Do YYYY, h:mm a'),
action: formatAuditLog(logEntryAction)
};
});
Expand Down

0 comments on commit 0d2c5ea

Please sign in to comment.