@craftercms/studio-ui
Version:
Services, components, models & utils to build CrafterCMS authoring extensions.
446 lines (444 loc) • 14.3 kB
JavaScript
/*
* Copyright (C) 2007-2022 Crafter Software Corporation. All Rights Reserved.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License version 3 as published by
* the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/*
* Copyright (C) 2007-2022 Crafter Software Corporation. All Rights Reserved.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as published by
* the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import Box from '@mui/material/Box';
import React, { useCallback, useMemo, useState } from 'react';
import { useStyles } from './styles';
import { DataGrid } from '@mui/x-data-grid';
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
import Tooltip from '@mui/material/Tooltip';
import IconButton from '@mui/material/IconButton';
import VisibilityRoundedIcon from '@mui/icons-material/VisibilityRounded';
import { getOffsetLeft, getOffsetTop } from '@mui/material/Popover';
import moment from 'moment-timezone';
import { Button, Typography } from '@mui/material';
import EmptyState from '../EmptyState/EmptyState';
import AuditGridFilterPopover from '../AuditGridFilterPopover';
import { useLocale } from '../../hooks/useLocale';
export const translations = defineMessages({
timestamp: {
id: 'auditGrid.timestamp',
defaultMessage: 'Timestamp'
},
siteName: {
id: 'auditGrid.siteName',
defaultMessage: 'Project'
},
operation: {
id: 'auditGrid.operation',
defaultMessage: 'Operation'
},
targetValue: {
id: 'auditGrid.targetValue',
defaultMessage: 'Target Value'
},
targetType: {
id: 'auditGrid.targetType',
defaultMessage: 'Target Type'
},
username: {
id: 'auditGrid.username',
defaultMessage: 'Username'
},
name: {
id: 'auditGrid.name',
defaultMessage: 'Name'
},
origin: {
id: 'auditGrid.origin',
defaultMessage: 'Origin'
},
parameters: {
id: 'auditGrid.parameters',
defaultMessage: 'Parameters'
}
});
export const fieldIdMapping = {
operationTimestamp: 'operationTimestamp',
siteName: 'siteId',
actorId: 'user',
origin: 'origin',
operation: 'operations',
primaryTargetValue: 'target'
};
export function AuditGridUI(props) {
const {
page,
auditLogs,
onPageChange,
onPageSizeChange,
onFilterChange,
onResetFilter,
onResetFilters,
filters,
sites,
users,
parametersLookup,
operations,
origins,
timezones,
onFetchParameters,
hasActiveFilters,
siteMode = false
} = props;
const { classes } = useStyles();
const { formatMessage } = useIntl();
const [anchorPosition, setAnchorPosition] = useState(null);
const [openedFilter, setOpenedFilter] = useState();
const [timezone, setTimezone] = useState(moment.tz.guess());
const [sortModel, setSortModel] = useState([{ field: 'operationTimestamp', sort: 'desc' }]);
const localeBranch = useLocale();
const onFilterSelected = (props) => {
if (props.open && anchorPosition === null) {
setTimeout(() => {
setOpenedFilter(props.currentColumn.field);
const element = document.getElementById(props.labelledby);
const anchorRect = element.getBoundingClientRect();
const top = anchorRect.top + getOffsetTop(anchorRect, 'top');
const left = anchorRect.left + getOffsetLeft(anchorRect, 'left');
setAnchorPosition({ top, left });
});
}
return null;
};
const onGetParameters = useCallback(
(params) => {
onFetchParameters(params.id);
},
[onFetchParameters]
);
const onTimestampSortChanges = (model) => {
const newSort = model.find((m) => m.field === 'operationTimestamp').sort;
const sort = sortModel.find((m) => m.field === 'operationTimestamp').sort;
if (newSort !== sort) {
setSortModel(model);
if (newSort === 'asc') {
onFilterChange({ id: 'order', value: newSort.toUpperCase() });
} else {
onFilterChange({ id: 'order', value: undefined });
}
}
};
const onTimezoneSelected = (timezone) => {
setTimezone(timezone);
};
const onPopoverFilterChanges = (id, value) => {
onFilterChange({ id, value: value === 'all' ? undefined : value });
};
const columns = useMemo(
() => [
{
field: 'operationTimestamp',
headerName: formatMessage(translations.timestamp),
width: 200,
cellClassName: classes.cellRoot,
renderCell: (params) => {
const date = new Intl.DateTimeFormat(
localeBranch.localeCode,
Object.assign(Object.assign({}, localeBranch.dateTimeFormatOptions), { timeZone: timezone })
).format(new Date(params.value));
return React.createElement(
Typography,
{
variant: 'body2',
className: classes.ellipsis,
title: date === null || date === void 0 ? void 0 : date.toString()
},
date
);
},
headerClassName: (filters['dateFrom'] || filters['dateTo']) && classes.activeFilter
},
{
field: 'siteName',
headerName: formatMessage(translations.siteName),
width: 150,
sortable: false,
cellClassName: classes.cellRoot,
headerClassName: filters[fieldIdMapping['siteName']] && classes.activeFilter,
hide: siteMode,
renderCell: (params) => {
var _a;
return React.createElement(
Typography,
{
variant: 'body2',
className: classes.ellipsis,
title: (_a = params.value) === null || _a === void 0 ? void 0 : _a.toString()
},
params.value
);
}
},
{
field: 'actorId',
headerName: formatMessage(translations.username),
width: 150,
sortable: false,
cellClassName: classes.cellRoot,
headerClassName: filters[fieldIdMapping['actorId']] && classes.activeFilter,
renderCell: (params) => {
var _a;
return React.createElement(
Typography,
{
variant: 'body2',
className: classes.ellipsis,
title: (_a = params.value) === null || _a === void 0 ? void 0 : _a.toString()
},
params.value
);
}
},
{
field: 'operation',
headerName: formatMessage(translations.operation),
width: 150,
sortable: false,
cellClassName: classes.cellRoot,
headerClassName: filters[fieldIdMapping['operation']] && classes.activeFilter,
renderCell: (params) => {
var _a;
return React.createElement(
Typography,
{
variant: 'body2',
className: classes.ellipsis,
title: (_a = params.value) === null || _a === void 0 ? void 0 : _a.toString()
},
params.value
);
}
},
{
field: 'primaryTargetValue',
headerName: formatMessage(translations.targetValue),
width: 300,
sortable: false,
cellClassName: classes.cellRoot,
headerClassName: filters[fieldIdMapping['primaryTargetValue']] && classes.activeFilter,
renderCell: (params) => {
var _a;
return React.createElement(
Typography,
{
variant: 'body2',
className: classes.ellipsis,
title: (_a = params.value) === null || _a === void 0 ? void 0 : _a.toString()
},
params.value
);
}
},
{
field: 'primaryTargetType',
headerName: formatMessage(translations.targetType),
width: 150,
disableColumnMenu: true,
sortable: false,
cellClassName: classes.cellRoot,
renderCell: (params) => {
var _a;
return React.createElement(
Typography,
{
variant: 'body2',
className: classes.ellipsis,
title: (_a = params.value) === null || _a === void 0 ? void 0 : _a.toString()
},
params.value
);
}
},
{
field: 'actorDetails',
headerName: formatMessage(translations.name),
width: 100,
disableColumnMenu: true,
sortable: false,
cellClassName: classes.cellRoot,
renderCell: (params) => {
var _a;
return React.createElement(
Typography,
{
variant: 'body2',
className: classes.ellipsis,
title: (_a = params.value) === null || _a === void 0 ? void 0 : _a.toString()
},
params.value
);
}
},
{
field: 'origin',
headerName: formatMessage(translations.origin),
width: 100,
sortable: false,
cellClassName: classes.cellRoot,
headerClassName: filters[fieldIdMapping['origin']] && classes.activeFilter,
renderCell: (params) => {
var _a;
return React.createElement(
Typography,
{
variant: 'body2',
className: classes.ellipsis,
title: (_a = params.value) === null || _a === void 0 ? void 0 : _a.toString()
},
params.value
);
}
},
{
field: 'parameters',
headerName: formatMessage(translations.parameters),
width: 105,
disableColumnMenu: true,
renderCell: (params) => {
var _a;
return parametersLookup[params.id] === undefined ||
((_a = parametersLookup[params.id]) === null || _a === void 0 ? void 0 : _a.length)
? React.createElement(
Tooltip,
{
title: React.createElement(FormattedMessage, {
id: 'auditGrid.showParameters',
defaultMessage: 'Show parameters'
})
},
React.createElement(
IconButton,
{ onClick: () => onGetParameters(params), size: 'large' },
React.createElement(VisibilityRoundedIcon, null)
)
)
: React.createElement(
Typography,
{ color: 'textSecondary', variant: 'caption' },
'(',
React.createElement(FormattedMessage, {
id: 'auditGrid.noParameters',
defaultMessage: 'No parameters'
}),
')'
);
},
sortable: false,
cellClassName: classes.cellRoot
}
],
[
classes.activeFilter,
classes.cellRoot,
classes.ellipsis,
filters,
formatMessage,
localeBranch.dateTimeFormatOptions,
localeBranch.localeCode,
onGetParameters,
parametersLookup,
siteMode,
timezone
]
);
return React.createElement(
Box,
{ display: 'flex' },
React.createElement(DataGrid, {
sortingOrder: ['desc', 'asc'],
sortModel: sortModel,
sortingMode: 'server',
autoHeight: Boolean(auditLogs.length),
disableColumnFilter: true,
className: classes.gridRoot,
components: {
ColumnMenu: onFilterSelected,
NoRowsOverlay: () =>
React.createElement(
Box,
null,
React.createElement(
EmptyState,
{
styles: { root: { position: 'relative', zIndex: 1, paddingTop: 10, paddingBottom: 10, margin: 0 } },
title: React.createElement(FormattedMessage, {
id: 'auditGrid.emptyStateMessage',
defaultMessage: 'No Logs Found'
})
},
hasActiveFilters &&
React.createElement(
Button,
{ variant: 'text', color: 'primary', onClick: onResetFilters },
React.createElement(FormattedMessage, {
id: 'auditGrid.clearFilters',
defaultMessage: 'Clear filters'
})
)
)
)
},
disableSelectionOnClick: true,
disableColumnSelector: true,
rows: auditLogs,
columns: columns,
page: page,
pageSize: auditLogs.limit,
onSortModelChange: onTimestampSortChanges,
onPageSizeChange: onPageSizeChange,
rowsPerPageOptions: [5, 10, 15],
paginationMode: 'server',
onPageChange: onPageChange,
rowCount: auditLogs.total
}),
React.createElement(AuditGridFilterPopover, {
open: Boolean(anchorPosition),
anchorPosition: anchorPosition,
onClose: () => setAnchorPosition(null),
filterId: fieldIdMapping[openedFilter],
onResetFilter: onResetFilter,
onFilterChange: onPopoverFilterChanges,
value: filters[fieldIdMapping[openedFilter]],
dateFrom: filters['dateFrom'],
dateTo: filters['dateTo'],
timezone: timezone,
onTimezoneSelected: onTimezoneSelected,
options: {
users,
sites,
operations,
origins,
timezones
}
})
);
}
export default AuditGridUI;