From 840d5a54dddb10642b52ea918fc1e02954f51971 Mon Sep 17 00:00:00 2001 From: olav Date: Tue, 9 Aug 2022 15:37:26 +0200 Subject: [PATCH 1/3] refactor: remove UI bootstrap endpoint (#1212) --- .../admin/users/UserForm/UserForm.tsx | 6 +-- .../admin/users/hooks/useAddUserForm.ts | 8 ++-- .../getters/useUiBootstrap/useUiBootstrap.ts | 41 ------------------- frontend/src/interfaces/uiConfig.ts | 1 + 4 files changed, 8 insertions(+), 48 deletions(-) delete mode 100644 frontend/src/hooks/api/getters/useUiBootstrap/useUiBootstrap.ts diff --git a/frontend/src/component/admin/users/UserForm/UserForm.tsx b/frontend/src/component/admin/users/UserForm/UserForm.tsx index e4f5aa5c1a..f3678f489d 100644 --- a/frontend/src/component/admin/users/UserForm/UserForm.tsx +++ b/frontend/src/component/admin/users/UserForm/UserForm.tsx @@ -13,7 +13,7 @@ import React from 'react'; import { useUsers } from 'hooks/api/getters/useUsers/useUsers'; import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import { EDIT } from 'constants/misc'; -import useUiBootstrap from 'hooks/api/getters/useUiBootstrap/useUiBootstrap'; +import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig'; interface IUserForm { email: string; @@ -49,7 +49,7 @@ const UserForm: React.FC = ({ }) => { const { classes: styles } = useStyles(); const { roles } = useUsers(); - const { bootstrap } = useUiBootstrap(); + const { uiConfig } = useUiConfig(); // @ts-expect-error const sortRoles = (a, b) => { @@ -127,7 +127,7 @@ const UserForm: React.FC = ({ { - const { bootstrap } = useUiBootstrap(); + const { uiConfig } = useUiConfig(); const [name, setName] = useState(initialName); const [email, setEmail] = useState(initialEmail); const [sendEmail, setSendEmail] = useState(false); @@ -25,8 +25,8 @@ const useCreateUserForm = ( }, [initialEmail]); useEffect(() => { - setSendEmail(bootstrap?.email || false); - }, [bootstrap?.email]); + setSendEmail(uiConfig?.emailEnabled || false); + }, [uiConfig?.emailEnabled]); useEffect(() => { setRootRole(initialRootRole); diff --git a/frontend/src/hooks/api/getters/useUiBootstrap/useUiBootstrap.ts b/frontend/src/hooks/api/getters/useUiBootstrap/useUiBootstrap.ts deleted file mode 100644 index 57f67cc5f1..0000000000 --- a/frontend/src/hooks/api/getters/useUiBootstrap/useUiBootstrap.ts +++ /dev/null @@ -1,41 +0,0 @@ -import handleErrorResponses from '../httpErrorResponseHandler'; -import useSWR, { mutate, SWRConfiguration } from 'swr'; -import { useState, useEffect } from 'react'; -import { formatApiPath } from 'utils/formatPath'; - -const useUiBootstrap = (options: SWRConfiguration = {}) => { - // The point of the bootstrap is to get multiple datasets in one call. Therefore, - // this needs to be refactored to seed other hooks with the correct data. - const BOOTSTRAP_CACHE_KEY = `api/admin/ui-bootstrap`; - - const fetcher = () => { - const path = formatApiPath(`api/admin/ui-bootstrap`); - - return fetch(path, { - method: 'GET', - credentials: 'include', - }) - .then(handleErrorResponses('ui bootstrap')) - .then(res => res.json()); - }; - - const { data, error } = useSWR(BOOTSTRAP_CACHE_KEY, fetcher, options); - const [loading, setLoading] = useState(!error && !data); - - const refetchUiBootstrap = () => { - mutate(BOOTSTRAP_CACHE_KEY); - }; - - useEffect(() => { - setLoading(!error && !data); - }, [data, error]); - - return { - bootstrap: data, - error, - loading, - refetchUiBootstrap, - }; -}; - -export default useUiBootstrap; diff --git a/frontend/src/interfaces/uiConfig.ts b/frontend/src/interfaces/uiConfig.ts index ce930ae4e4..83dc4ad0ad 100644 --- a/frontend/src/interfaces/uiConfig.ts +++ b/frontend/src/interfaces/uiConfig.ts @@ -12,6 +12,7 @@ export interface IUiConfig { versionInfo?: IVersionInfo; links: ILinks[]; disablePasswordAuth?: boolean; + emailEnabled?: boolean; toast?: IProclamationToast; segmentValuesLimit?: number; strategySegmentsLimit?: number; From b3ac69a2c8ce87edac12c291c0cb330d476644a5 Mon Sep 17 00:00:00 2001 From: olav Date: Tue, 9 Aug 2022 16:00:51 +0200 Subject: [PATCH 2/3] feat: add search to event log pages (#1205) * feat: add search to event log pages * refactor: redesign event log pages * refactor: convert makeStyles to styled components --- .../src/component/common/Search/Search.tsx | 7 +- .../component/events/EventCard/EventCard.tsx | 130 ++++++++++++++++++ .../EventDiff/EventDiff.tsx | 52 +++---- .../EventJson/EventJson.tsx | 27 ++-- .../component/events/EventLog/EventLog.tsx | 108 +++++++++++++++ .../EventPage/EventPage.tsx} | 6 +- .../FeatureView/FeatureLog/FeatureLog.tsx | 9 +- .../history/EventHistory/EventHistory.tsx | 12 -- .../EventLog/EventCard/EventCard.styles.ts | 7 - .../history/EventLog/EventCard/EventCard.tsx | 57 -------- .../EventCard/EventDiff/EventDiff.styles.ts | 13 -- .../EventLog/EventJson/EventJson.styles.ts | 10 -- .../history/EventLog/EventLog.styles.ts | 40 ------ .../component/history/EventLog/EventLog.tsx | 90 ------------ .../src/component/history/EventLog/index.tsx | 28 ---- .../FeatureEventHistory.tsx | 18 --- .../FeatureEventHistoryPage.tsx | 9 -- .../__snapshots__/routes.test.tsx.snap | 8 -- frontend/src/component/menu/routes.ts | 13 +- .../getters/useEventSearch/useEventSearch.ts | 79 +++++++++++ .../hooks/api/getters/useEvents/useEvents.ts | 39 ------ .../useFeatureEvents/useFeatureEvents.ts | 42 ------ frontend/src/hooks/useOnVisible.ts | 24 ++++ frontend/src/themes/theme.ts | 7 +- frontend/src/themes/themeTypes.ts | 1 - 25 files changed, 399 insertions(+), 437 deletions(-) create mode 100644 frontend/src/component/events/EventCard/EventCard.tsx rename frontend/src/component/{history/EventLog/EventCard => events}/EventDiff/EventDiff.tsx (61%) rename frontend/src/component/{history/EventLog => events}/EventJson/EventJson.tsx (51%) create mode 100644 frontend/src/component/events/EventLog/EventLog.tsx rename frontend/src/component/{history/EventHistoryPage/EventHistoryPage.tsx => events/EventPage/EventPage.tsx} (77%) delete mode 100644 frontend/src/component/history/EventHistory/EventHistory.tsx delete mode 100644 frontend/src/component/history/EventLog/EventCard/EventCard.styles.ts delete mode 100644 frontend/src/component/history/EventLog/EventCard/EventCard.tsx delete mode 100644 frontend/src/component/history/EventLog/EventCard/EventDiff/EventDiff.styles.ts delete mode 100644 frontend/src/component/history/EventLog/EventJson/EventJson.styles.ts delete mode 100644 frontend/src/component/history/EventLog/EventLog.styles.ts delete mode 100644 frontend/src/component/history/EventLog/EventLog.tsx delete mode 100644 frontend/src/component/history/EventLog/index.tsx delete mode 100644 frontend/src/component/history/FeatureEventHistory/FeatureEventHistory.tsx delete mode 100644 frontend/src/component/history/FeatureEventHistoryPage/FeatureEventHistoryPage.tsx create mode 100644 frontend/src/hooks/api/getters/useEventSearch/useEventSearch.ts delete mode 100644 frontend/src/hooks/api/getters/useEvents/useEvents.ts delete mode 100644 frontend/src/hooks/api/getters/useFeatureEvents/useFeatureEvents.ts create mode 100644 frontend/src/hooks/useOnVisible.ts diff --git a/frontend/src/component/common/Search/Search.tsx b/frontend/src/component/common/Search/Search.tsx index fdfc5c0af7..6733984e7b 100644 --- a/frontend/src/component/common/Search/Search.tsx +++ b/frontend/src/component/common/Search/Search.tsx @@ -1,4 +1,4 @@ -import { useRef, useState } from 'react'; +import React, { useRef, useState } from 'react'; import { IconButton, InputBase, Tooltip } from '@mui/material'; import { Search as SearchIcon, Close } from '@mui/icons-material'; import classnames from 'classnames'; @@ -18,6 +18,7 @@ interface ISearchProps { disabled?: boolean; getSearchContext?: () => IGetSearchContextOutput; containerStyles?: React.CSSProperties; + debounceTime?: number; } export const Search = ({ @@ -29,14 +30,14 @@ export const Search = ({ disabled, getSearchContext, containerStyles, + debounceTime = 200, }: ISearchProps) => { const ref = useRef(); const { classes: styles } = useStyles(); const [showSuggestions, setShowSuggestions] = useState(false); const [value, setValue] = useState(initialValue); - - const debouncedOnChange = useAsyncDebounce(onChange, 200); + const debouncedOnChange = useAsyncDebounce(onChange, debounceTime); const onSearchChange = (value: string) => { debouncedOnChange(value); diff --git a/frontend/src/component/events/EventCard/EventCard.tsx b/frontend/src/component/events/EventCard/EventCard.tsx new file mode 100644 index 0000000000..ed63fcd66b --- /dev/null +++ b/frontend/src/component/events/EventCard/EventCard.tsx @@ -0,0 +1,130 @@ +import EventDiff from 'component/events/EventDiff/EventDiff'; +import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; +import { IEvent } from 'interfaces/event'; +import { useLocationSettings } from 'hooks/useLocationSettings'; +import { formatDateYMDHMS } from 'utils/formatDate'; +import { Link } from 'react-router-dom'; +import { styled } from '@mui/material'; + +interface IEventCardProps { + entry: IEvent; +} + +const StyledDefinitionTerm = styled('dt')(({ theme }) => ({ + color: theme.palette.text.secondary, +})); + +const StyledChangesTitle = styled('strong')(({ theme }) => ({ + fontWeight: 'inherit', + color: theme.palette.text.secondary, +})); + +const StyledContainerListItem = styled('li')(({ theme }) => ({ + display: 'grid', + backgroundColor: theme.palette.neutral.light, + borderRadius: theme.shape.borderRadiusLarge, + padding: theme.spacing(0.5), + [theme.breakpoints.up('md')]: { + gridTemplateColumns: 'auto minmax(0, 1fr)', + }, + + '& dl': { + display: 'grid', + gridTemplateColumns: 'auto 1fr', + alignContent: 'start', + gap: theme.spacing(1), + padding: theme.spacing(2), + [theme.breakpoints.up('md')]: { + padding: theme.spacing(4), + }, + }, +})); + +const StyledCodeSection = styled('div')(({ theme }) => ({ + backgroundColor: 'white', + overflowX: 'auto', + padding: theme.spacing(2), + borderBottomLeftRadius: theme.shape.borderRadiusLarge, + borderBottomRightRadius: theme.shape.borderRadiusLarge, + [theme.breakpoints.up('md')]: { + padding: theme.spacing(4), + borderRadius: 0, + borderTopRightRadius: theme.shape.borderRadiusLarge, + borderBottomRightRadius: theme.shape.borderRadiusLarge, + }, + + '& code': { + wordWrap: 'break-word', + whiteSpace: 'pre-wrap', + fontFamily: 'monospace', + lineHeight: 1.5, + fontSize: theme.fontSizes.smallBody, + }, +})); + +const EventCard = ({ entry }: IEventCardProps) => { + const { locationSettings } = useLocationSettings(); + + const createdAtFormatted = formatDateYMDHMS( + entry.createdAt, + locationSettings.locale + ); + + return ( + +
+ Event id: +
{entry.id}
+ Changed at: +
{createdAtFormatted}
+ Event: +
{entry.type}
+ Changed by: +
{entry.createdBy}
+ + + Project: + +
+ + {entry.project} + +
+ + } + /> + + + Feature: + +
+ + {entry.featureName} + +
+ + } + /> +
+ + Changes: + + + } + /> +
+ ); +}; + +export default EventCard; diff --git a/frontend/src/component/history/EventLog/EventCard/EventDiff/EventDiff.tsx b/frontend/src/component/events/EventDiff/EventDiff.tsx similarity index 61% rename from frontend/src/component/history/EventLog/EventCard/EventDiff/EventDiff.tsx rename to frontend/src/component/events/EventDiff/EventDiff.tsx index 99fb2f5aac..81f48258f3 100644 --- a/frontend/src/component/history/EventLog/EventCard/EventDiff/EventDiff.tsx +++ b/frontend/src/component/events/EventDiff/EventDiff.tsx @@ -1,8 +1,9 @@ import { diff } from 'deep-diff'; -import { useStyles } from './EventDiff.styles'; import { IEvent } from 'interfaces/event'; +import { useTheme } from '@mui/system'; +import { CSSProperties } from 'react'; -const DIFF_PREFIXES = { +const DIFF_PREFIXES: Record = { A: ' ', E: ' ', D: '-', @@ -14,13 +15,13 @@ interface IEventDiffProps { } const EventDiff = ({ entry }: IEventDiffProps) => { - const { classes: styles } = useStyles(); + const theme = useTheme(); - const KLASSES = { - A: styles.blue, // array edited - E: styles.blue, // edited - D: styles.negative, // deleted - N: styles.positive, // added + const styles: Record = { + A: { color: theme.palette.code.edited }, // array edited + E: { color: theme.palette.code.edited }, // edited + D: { color: theme.palette.code.diffSub }, // deleted + N: { color: theme.palette.code.diffAdd }, // added }; const diffs = @@ -32,18 +33,14 @@ const EventDiff = ({ entry }: IEventDiffProps) => { let change; if (diff.lhs !== undefined) { change = ( -
-
- - {key}: {JSON.stringify(diff.lhs)} -
+
+ - {key}: {JSON.stringify(diff.lhs)}
); } else if (diff.rhs !== undefined) { change = ( -
-
- + {key}: {JSON.stringify(diff.rhs)} -
+
+ + {key}: {JSON.stringify(diff.rhs)}
); } @@ -60,23 +57,19 @@ const EventDiff = ({ entry }: IEventDiffProps) => { } else if (diff.lhs !== undefined && diff.rhs !== undefined) { change = (
-
+
- {key}: {JSON.stringify(diff.lhs)}
-
+
+ {key}: {JSON.stringify(diff.rhs)}
); } else { - // @ts-expect-error - const spadenClass = KLASSES[diff.kind]; - // @ts-expect-error - const prefix = DIFF_PREFIXES[diff.kind]; - change = ( -
- {prefix} {key}: {JSON.stringify(diff.rhs || diff.item)} +
+ {DIFF_PREFIXES[diff.kind]} {key}:{' '} + {JSON.stringify(diff.rhs || diff.item)}
); } @@ -91,16 +84,15 @@ const EventDiff = ({ entry }: IEventDiffProps) => { } else { // Just show the data if there is no diff yet. const data = entry.data || entry.preData; - changes = ( -
+ changes = [ +
{JSON.stringify(data, null, 2)} -
- ); +
, + ]; } return (
-            {/* @ts-expect-error */}
             {changes.length === 0 ? '(no changes)' : changes}
         
); diff --git a/frontend/src/component/history/EventLog/EventJson/EventJson.tsx b/frontend/src/component/events/EventJson/EventJson.tsx similarity index 51% rename from frontend/src/component/history/EventLog/EventJson/EventJson.tsx rename to frontend/src/component/events/EventJson/EventJson.tsx index 7f15feeace..720f93ae50 100644 --- a/frontend/src/component/history/EventLog/EventJson/EventJson.tsx +++ b/frontend/src/component/events/EventJson/EventJson.tsx @@ -1,14 +1,25 @@ -import PropTypes from 'prop-types'; -import { useStyles } from './EventJson.styles'; import { IEvent } from 'interfaces/event'; +import { styled } from '@mui/material'; interface IEventJsonProps { entry: IEvent; } -const EventJson = ({ entry }: IEventJsonProps) => { - const { classes: styles } = useStyles(); +export const StyledJsonListItem = styled('li')(({ theme }) => ({ + padding: theme.spacing(4), + backgroundColor: theme.palette.neutral.light, + borderRadius: theme.shape.borderRadiusLarge, + fontSize: theme.fontSizes.smallBody, + '& code': { + wordWrap: 'break-word', + whiteSpace: 'pre', + fontFamily: 'monospace', + lineHeight: '100%', + }, +})); + +const EventJson = ({ entry }: IEventJsonProps) => { const localEventData = JSON.parse(JSON.stringify(entry)); delete localEventData.description; delete localEventData.name; @@ -17,16 +28,12 @@ const EventJson = ({ entry }: IEventJsonProps) => { const prettyPrinted = JSON.stringify(localEventData, null, 2); return ( -
  • +
    {prettyPrinted}
    -
  • + ); }; -EventJson.propTypes = { - entry: PropTypes.object, -}; - export default EventJson; diff --git a/frontend/src/component/events/EventLog/EventLog.tsx b/frontend/src/component/events/EventLog/EventLog.tsx new file mode 100644 index 0000000000..288f9ade28 --- /dev/null +++ b/frontend/src/component/events/EventLog/EventLog.tsx @@ -0,0 +1,108 @@ +import { Switch, FormControlLabel, useMediaQuery, Box } from '@mui/material'; +import EventJson from 'component/events/EventJson/EventJson'; +import { PageContent } from 'component/common/PageContent/PageContent'; +import { PageHeader } from 'component/common/PageHeader/PageHeader'; +import EventCard from 'component/events/EventCard/EventCard'; +import { useEventSettings } from 'hooks/useEventSettings'; +import React, { useState, useEffect } from 'react'; +import { Search } from 'component/common/Search/Search'; +import theme from 'themes/theme'; +import { useEventSearch } from 'hooks/api/getters/useEventSearch/useEventSearch'; +import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; +import { useOnVisible } from 'hooks/useOnVisible'; +import { IEvent } from 'interfaces/event'; +import { styled } from '@mui/system'; + +interface IEventLogProps { + title: string; + project?: string; + feature?: string; + displayInline?: boolean; +} + +const StyledEventsList = styled('ul')(({ theme }) => ({ + listStyleType: 'none', + margin: 0, + padding: 0, + display: 'grid', + gap: theme.spacing(2), +})); + +export const EventLog = ({ + title, + project, + feature, + displayInline, +}: IEventLogProps) => { + const [query, setQuery] = useState(''); + const { events, fetchNextPage } = useEventSearch(project, feature, query); + const fetchNextPageRef = useOnVisible(fetchNextPage); + const { eventSettings, setEventSettings } = useEventSettings(); + const isSmallScreen = useMediaQuery(theme.breakpoints.down('md')); + + // Cache the previous search results so that we can show those while + // fetching new results for a new search query in the background. + const [cache, setCache] = useState(); + useEffect(() => events && setCache(events), [events]); + + const onShowData = () => { + setEventSettings(prev => ({ showData: !prev.showData })); + }; + + const searchInputField = ; + + const showDataSwitch = ( + + } + /> + ); + + return ( + + {showDataSwitch} + {!isSmallScreen && searchInputField} + + } + > + {isSmallScreen && searchInputField} + + } + > + {displayInline && } +

    No events found.

    } + /> + 0)} + show={() => ( + + {cache?.map(entry => ( + } + elseShow={() => } + /> + ))} + + )} + /> +
    + + ); +}; diff --git a/frontend/src/component/history/EventHistoryPage/EventHistoryPage.tsx b/frontend/src/component/events/EventPage/EventPage.tsx similarity index 77% rename from frontend/src/component/history/EventHistoryPage/EventHistoryPage.tsx rename to frontend/src/component/events/EventPage/EventPage.tsx index 5030958f0b..9acc8d1557 100644 --- a/frontend/src/component/history/EventHistoryPage/EventHistoryPage.tsx +++ b/frontend/src/component/events/EventPage/EventPage.tsx @@ -2,16 +2,16 @@ import React, { useContext } from 'react'; import { ADMIN } from 'component/providers/AccessProvider/permissions'; import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import AccessContext from 'contexts/AccessContext'; -import { EventHistory } from '../EventHistory/EventHistory'; import { AdminAlert } from 'component/common/AdminAlert/AdminAlert'; +import { EventLog } from 'component/events/EventLog/EventLog'; -export const EventHistoryPage = () => { +export const EventPage = () => { const { hasAccess } = useContext(AccessContext); return ( } + show={() => } elseShow={} /> ); diff --git a/frontend/src/component/feature/FeatureView/FeatureLog/FeatureLog.tsx b/frontend/src/component/feature/FeatureView/FeatureLog/FeatureLog.tsx index 0c181c80ab..0bd399841b 100644 --- a/frontend/src/component/feature/FeatureView/FeatureLog/FeatureLog.tsx +++ b/frontend/src/component/feature/FeatureView/FeatureLog/FeatureLog.tsx @@ -1,7 +1,7 @@ import { useFeature } from 'hooks/api/getters/useFeature/useFeature'; import { useStyles } from './FeatureLog.styles'; -import { FeatureEventHistory } from 'component/history/FeatureEventHistory/FeatureEventHistory'; import { useRequiredPathParam } from 'hooks/useRequiredPathParam'; +import { EventLog } from 'component/events/EventLog/EventLog'; const FeatureLog = () => { const projectId = useRequiredPathParam('projectId'); @@ -15,7 +15,12 @@ const FeatureLog = () => { return (
    - +
    ); }; diff --git a/frontend/src/component/history/EventHistory/EventHistory.tsx b/frontend/src/component/history/EventHistory/EventHistory.tsx deleted file mode 100644 index 3bf42f1897..0000000000 --- a/frontend/src/component/history/EventHistory/EventHistory.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import EventLog from '../EventLog'; -import { useEvents } from 'hooks/api/getters/useEvents/useEvents'; - -export const EventHistory = () => { - const { events } = useEvents(); - - if (events.length < 0) { - return null; - } - - return ; -}; diff --git a/frontend/src/component/history/EventLog/EventCard/EventCard.styles.ts b/frontend/src/component/history/EventLog/EventCard/EventCard.styles.ts deleted file mode 100644 index 1ab5974ae0..0000000000 --- a/frontend/src/component/history/EventLog/EventCard/EventCard.styles.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { makeStyles } from 'tss-react/mui'; - -export const useStyles = makeStyles()({ - eventLogHeader: { - minWidth: '110px', - }, -}); diff --git a/frontend/src/component/history/EventLog/EventCard/EventCard.tsx b/frontend/src/component/history/EventLog/EventCard/EventCard.tsx deleted file mode 100644 index 55ec0ed2f3..0000000000 --- a/frontend/src/component/history/EventLog/EventCard/EventCard.tsx +++ /dev/null @@ -1,57 +0,0 @@ -import EventDiff from 'component/history/EventLog/EventCard/EventDiff/EventDiff'; -import { useStyles } from './EventCard.styles'; -import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; -import { IEvent } from 'interfaces/event'; - -interface IEventCardProps { - entry: IEvent; - timeFormatted: string; -} - -const EventCard = ({ entry, timeFormatted }: IEventCardProps) => { - const { classes: styles } = useStyles(); - - return ( -
    -
    -
    Event id:
    -
    {entry.id}
    -
    Changed at:
    -
    {timeFormatted}
    -
    Event:
    -
    {entry.type}
    -
    Changed by:
    -
    {entry.createdBy}
    - -
    Project:
    -
    {entry.project}
    - - } - /> - -
    Feature:
    -
    {entry.featureName}
    - - } - /> -
    - - Change - - - } - /> -
    - ); -}; - -export default EventCard; diff --git a/frontend/src/component/history/EventLog/EventCard/EventDiff/EventDiff.styles.ts b/frontend/src/component/history/EventLog/EventCard/EventDiff/EventDiff.styles.ts deleted file mode 100644 index 2c2db9f423..0000000000 --- a/frontend/src/component/history/EventLog/EventCard/EventDiff/EventDiff.styles.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { makeStyles } from 'tss-react/mui'; - -export const useStyles = makeStyles()(theme => ({ - blue: { - color: theme.palette.code.edited, - }, - negative: { - color: theme.palette.code.diffSub, - }, - positive: { - color: theme.palette.code.diffAdd, - }, -})); diff --git a/frontend/src/component/history/EventLog/EventJson/EventJson.styles.ts b/frontend/src/component/history/EventLog/EventJson/EventJson.styles.ts deleted file mode 100644 index 3ab7837ccc..0000000000 --- a/frontend/src/component/history/EventLog/EventJson/EventJson.styles.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { makeStyles } from 'tss-react/mui'; - -export const useStyles = makeStyles()(theme => ({ - historyItem: { - padding: '5px', - '&:nth-of-type(odd)': { - backgroundColor: theme.palette.code.background, - }, - }, -})); diff --git a/frontend/src/component/history/EventLog/EventLog.styles.ts b/frontend/src/component/history/EventLog/EventLog.styles.ts deleted file mode 100644 index cff9878cf4..0000000000 --- a/frontend/src/component/history/EventLog/EventLog.styles.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { makeStyles } from 'tss-react/mui'; - -export const useStyles = makeStyles()(theme => ({ - eventEntry: { - border: `1px solid ${theme.palette.neutral.light}`, - padding: '1rem', - margin: '1rem 0', - borderRadius: theme.shape.borderRadius, - }, - history: { - '& code': { - wordWrap: 'break-word', - whiteSpace: 'pre', - fontFamily: 'monospace', - lineHeight: '100%', - color: theme.palette.code.main, - }, - '& code > .diff-N': { - color: theme.palette.code.diffAdd, - }, - '& code > .diff-D': { - color: theme.palette.code.diffSub, - }, - '& code > .diff-A, .diff-E': { - color: theme.palette.code.diffNeutral, - }, - '& dl': { - padding: '0', - }, - '& dt': { - float: 'left', - clear: 'left', - fontWeight: 'bold', - }, - '& dd': { - margin: '0 0 0 83px', - padding: '0 0 0.5em 0', - }, - }, -})); diff --git a/frontend/src/component/history/EventLog/EventLog.tsx b/frontend/src/component/history/EventLog/EventLog.tsx deleted file mode 100644 index b4f9967e85..0000000000 --- a/frontend/src/component/history/EventLog/EventLog.tsx +++ /dev/null @@ -1,90 +0,0 @@ -import { List, Switch, FormControlLabel } from '@mui/material'; -import EventJson from 'component/history/EventLog/EventJson/EventJson'; -import { PageContent } from 'component/common/PageContent/PageContent'; -import { PageHeader } from 'component/common/PageHeader/PageHeader'; -import EventCard from 'component/history/EventLog/EventCard/EventCard'; -import { useStyles } from './EventLog.styles'; -import { formatDateYMDHMS } from 'utils/formatDate'; -import { IEventSettings } from 'hooks/useEventSettings'; -import { IEvent } from 'interfaces/event'; -import React from 'react'; -import { ILocationSettings } from 'hooks/useLocationSettings'; - -interface IEventLogProps { - title: string; - events: IEvent[]; - eventSettings: IEventSettings; - setEventSettings: React.Dispatch>; - locationSettings: ILocationSettings; - displayInline?: boolean; -} - -const EventLog = ({ - title, - events, - eventSettings, - setEventSettings, - locationSettings, - displayInline, -}: IEventLogProps) => { - const { classes: styles } = useStyles(); - const toggleShowDiff = () => { - setEventSettings({ showData: !eventSettings.showData }); - }; - const formatFulldateTime = (v: string) => { - return formatDateYMDHMS(v, locationSettings.locale); - }; - - if (!events || events.length < 0) { - return null; - } - - let entries; - - const renderListItemCards = (entry: IEvent) => ( -
  • - -
  • - ); - - if (eventSettings.showData) { - entries = events.map(entry => ( - - )); - } else { - entries = events.map(renderListItemCards); - } - - return ( - - } - label="Full events" - /> - } - /> - } - > -
    - {entries} -
    -
    - ); -}; - -export default EventLog; diff --git a/frontend/src/component/history/EventLog/index.tsx b/frontend/src/component/history/EventLog/index.tsx deleted file mode 100644 index 0f9e64c86e..0000000000 --- a/frontend/src/component/history/EventLog/index.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import EventLog from 'component/history/EventLog/EventLog'; -import { useEventSettings } from 'hooks/useEventSettings'; -import { useLocationSettings } from 'hooks/useLocationSettings'; -import { IEvent } from 'interfaces/event'; - -interface IEventLogContainerProps { - title: string; - events: IEvent[]; - displayInline?: boolean; -} - -const EventLogContainer = (props: IEventLogContainerProps) => { - const { locationSettings } = useLocationSettings(); - const { eventSettings, setEventSettings } = useEventSettings(); - - return ( - - ); -}; - -export default EventLogContainer; diff --git a/frontend/src/component/history/FeatureEventHistory/FeatureEventHistory.tsx b/frontend/src/component/history/FeatureEventHistory/FeatureEventHistory.tsx deleted file mode 100644 index 44764a4a58..0000000000 --- a/frontend/src/component/history/FeatureEventHistory/FeatureEventHistory.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import EventLog from '../EventLog'; -import { useFeatureEvents } from 'hooks/api/getters/useFeatureEvents/useFeatureEvents'; - -interface IFeatureEventHistoryProps { - featureId: string; -} - -export const FeatureEventHistory = ({ - featureId, -}: IFeatureEventHistoryProps) => { - const { events } = useFeatureEvents(featureId); - - if (events.length === 0) { - return null; - } - - return ; -}; diff --git a/frontend/src/component/history/FeatureEventHistoryPage/FeatureEventHistoryPage.tsx b/frontend/src/component/history/FeatureEventHistoryPage/FeatureEventHistoryPage.tsx deleted file mode 100644 index 01b61be817..0000000000 --- a/frontend/src/component/history/FeatureEventHistoryPage/FeatureEventHistoryPage.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import React from 'react'; -import { FeatureEventHistory } from 'component/history/FeatureEventHistory/FeatureEventHistory'; -import { useRequiredPathParam } from 'hooks/useRequiredPathParam'; - -export const FeatureEventHistoryPage = () => { - const toggleName = useRequiredPathParam('toggleName'); - - return ; -}; diff --git a/frontend/src/component/menu/__tests__/__snapshots__/routes.test.tsx.snap b/frontend/src/component/menu/__tests__/__snapshots__/routes.test.tsx.snap index f4c18236f7..a56a0190a8 100644 --- a/frontend/src/component/menu/__tests__/__snapshots__/routes.test.tsx.snap +++ b/frontend/src/component/menu/__tests__/__snapshots__/routes.test.tsx.snap @@ -319,14 +319,6 @@ exports[`returns all baseRoutes 1`] = ` "title": "Segments", "type": "protected", }, - { - "component": [Function], - "menu": {}, - "parent": "/history", - "path": "/history/:toggleName", - "title": ":toggleName", - "type": "protected", - }, { "component": [Function], "menu": { diff --git a/frontend/src/component/menu/routes.ts b/frontend/src/component/menu/routes.ts index a737ba31c8..9a6a1c79c5 100644 --- a/frontend/src/component/menu/routes.ts +++ b/frontend/src/component/menu/routes.ts @@ -39,8 +39,7 @@ import RedirectFeatureView from 'component/feature/RedirectFeatureView/RedirectF import { CreateAddon } from 'component/addons/CreateAddon/CreateAddon'; import { EditAddon } from 'component/addons/EditAddon/EditAddon'; import { CopyFeatureToggle } from 'component/feature/CopyFeature/CopyFeature'; -import { EventHistoryPage } from 'component/history/EventHistoryPage/EventHistoryPage'; -import { FeatureEventHistoryPage } from 'component/history/FeatureEventHistoryPage/FeatureEventHistoryPage'; +import { EventPage } from 'component/events/EventPage/EventPage'; import { CreateStrategy } from 'component/strategies/CreateStrategy/CreateStrategy'; import { EditStrategy } from 'component/strategies/EditStrategy/EditStrategy'; import { SplashPage } from 'component/splash/SplashPage/SplashPage'; @@ -363,18 +362,10 @@ export const routes: IRoute[] = [ }, // History - { - path: '/history/:toggleName', - title: ':toggleName', - parent: '/history', - component: FeatureEventHistoryPage, - type: 'protected', - menu: {}, - }, { path: '/history', title: 'Event log', - component: EventHistoryPage, + component: EventPage, type: 'protected', menu: { adminSettings: true }, }, diff --git a/frontend/src/hooks/api/getters/useEventSearch/useEventSearch.ts b/frontend/src/hooks/api/getters/useEventSearch/useEventSearch.ts new file mode 100644 index 0000000000..c5d1572990 --- /dev/null +++ b/frontend/src/hooks/api/getters/useEventSearch/useEventSearch.ts @@ -0,0 +1,79 @@ +import useSWR from 'swr'; +import { useCallback, useState, useEffect, useMemo } from 'react'; +import { formatApiPath } from 'utils/formatPath'; +import handleErrorResponses from '../httpErrorResponseHandler'; +import { IEvent } from 'interfaces/event'; + +const PATH = formatApiPath('api/admin/events/search'); + +export interface IUseEventSearchOutput { + events?: IEvent[]; + fetchNextPage: () => void; + loading: boolean; + error?: Error; +} + +interface IEventSearch { + type?: string; + project?: string; + feature?: string; + query?: string; + limit?: number; + offset?: number; +} + +export const useEventSearch = ( + project?: string, + feature?: string, + query?: string +): IUseEventSearchOutput => { + const [events, setEvents] = useState(); + const [offset, setOffset] = useState(0); + + const search: IEventSearch = useMemo( + () => ({ project, feature, query, limit: 50 }), + [project, feature, query] + ); + + const { data, error, isValidating } = useSWR<{ events: IEvent[] }>( + [PATH, search, offset], + () => searchEvents(PATH, { ...search, offset }) + ); + + // Reset the page when there are new search conditions. + useEffect(() => { + setOffset(0); + setEvents(undefined); + }, [search]); + + // Append results to the page when more data has been fetched. + useEffect(() => { + if (data) { + setEvents(prev => [...(prev ?? []), ...data.events]); + } + }, [data]); + + // Update the offset to fetch more results at the end of the page. + const fetchNextPage = useCallback(() => { + if (events && !isValidating) { + setOffset(events.length); + } + }, [events, isValidating]); + + return { + events, + loading: !error && !data, + fetchNextPage, + error, + }; +}; + +const searchEvents = (path: string, search: IEventSearch) => { + return fetch(path, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(search), + }) + .then(handleErrorResponses('Event history')) + .then(res => res.json()); +}; diff --git a/frontend/src/hooks/api/getters/useEvents/useEvents.ts b/frontend/src/hooks/api/getters/useEvents/useEvents.ts deleted file mode 100644 index c424e67300..0000000000 --- a/frontend/src/hooks/api/getters/useEvents/useEvents.ts +++ /dev/null @@ -1,39 +0,0 @@ -import useSWR, { mutate, SWRConfiguration } from 'swr'; -import { useCallback } from 'react'; -import { formatApiPath } from 'utils/formatPath'; -import handleErrorResponses from '../httpErrorResponseHandler'; -import { IEvent } from 'interfaces/event'; - -const PATH = formatApiPath('api/admin/events'); - -export interface IUseEventsOutput { - events: IEvent[]; - refetchEvents: () => void; - loading: boolean; - error?: Error; -} - -export const useEvents = (options?: SWRConfiguration): IUseEventsOutput => { - const { data, error } = useSWR<{ events: IEvent[] }>( - PATH, - fetchAllEvents, - options - ); - - const refetchEvents = useCallback(() => { - mutate(PATH).catch(console.warn); - }, []); - - return { - events: data?.events || [], - loading: !error && !data, - refetchEvents, - error, - }; -}; - -const fetchAllEvents = () => { - return fetch(PATH, { method: 'GET' }) - .then(handleErrorResponses('Event history')) - .then(res => res.json()); -}; diff --git a/frontend/src/hooks/api/getters/useFeatureEvents/useFeatureEvents.ts b/frontend/src/hooks/api/getters/useFeatureEvents/useFeatureEvents.ts deleted file mode 100644 index e5ea6d2b0d..0000000000 --- a/frontend/src/hooks/api/getters/useFeatureEvents/useFeatureEvents.ts +++ /dev/null @@ -1,42 +0,0 @@ -import useSWR, { SWRConfiguration } from 'swr'; -import { useCallback } from 'react'; -import { formatApiPath } from 'utils/formatPath'; -import handleErrorResponses from '../httpErrorResponseHandler'; -import { IEvent } from 'interfaces/event'; - -const PATH = formatApiPath('api/admin/events'); - -export interface IUseEventsOutput { - events: IEvent[]; - refetchEvents: () => void; - loading: boolean; - error?: Error; -} - -export const useFeatureEvents = ( - featureName: string, - options?: SWRConfiguration -): IUseEventsOutput => { - const { data, error, mutate } = useSWR<{ events: IEvent[] }>( - [PATH, featureName], - () => fetchFeatureEvents(featureName), - options - ); - - const refetchEvents = useCallback(() => { - mutate().catch(console.warn); - }, [mutate]); - - return { - events: data?.events || [], - loading: !error && !data, - refetchEvents, - error, - }; -}; - -const fetchFeatureEvents = (featureName: string) => { - return fetch(`${PATH}/${featureName}`, { method: 'GET' }) - .then(handleErrorResponses('Event history')) - .then(res => res.json()); -}; diff --git a/frontend/src/hooks/useOnVisible.ts b/frontend/src/hooks/useOnVisible.ts new file mode 100644 index 0000000000..3ca3864e08 --- /dev/null +++ b/frontend/src/hooks/useOnVisible.ts @@ -0,0 +1,24 @@ +import { useRef, useEffect } from 'react'; + +// Call `onVisible` when the `ref` element is fully visible in the viewport. +// Useful for detecting when the user has scrolled to the bottom of the page. +export const useOnVisible = (onVisible: () => void) => { + const ref = useRef(null); + + useEffect(() => { + if (ref.current) { + const handler = (entries: IntersectionObserverEntry[]) => { + if (entries[0].isIntersecting) { + onVisible(); + } + }; + + const observer = new IntersectionObserver(handler); + observer.observe(ref.current); + + return () => observer.disconnect(); + } + }, [onVisible]); + + return ref; +}; diff --git a/frontend/src/themes/theme.ts b/frontend/src/themes/theme.ts index 159b7940c4..62f718a053 100644 --- a/frontend/src/themes/theme.ts +++ b/frontend/src/themes/theme.ts @@ -120,11 +120,10 @@ export default createTheme({ }, code: { main: '#0b8c8f', - diffAdd: 'green', - diffSub: 'red', + diffAdd: '#3b6600', + diffSub: '#d11525', diffNeutral: 'black', - edited: 'blue', - background: '#efefef', + edited: 'black', }, activityIndicators: { unknown: colors.grey[100], diff --git a/frontend/src/themes/themeTypes.ts b/frontend/src/themes/themeTypes.ts index 61393262b8..f046800e00 100644 --- a/frontend/src/themes/themeTypes.ts +++ b/frontend/src/themes/themeTypes.ts @@ -42,7 +42,6 @@ declare module '@mui/material/styles' { diffSub: string; diffNeutral: string; edited: string; - background: string; }; /** * For 'Seen' column on feature toggles list and other. From b685931c9eef1740d22531425c464d916eb8b1f1 Mon Sep 17 00:00:00 2001 From: olav Date: Tue, 9 Aug 2022 16:08:14 +0200 Subject: [PATCH 3/3] 4.15.0-beta.0 --- frontend/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/package.json b/frontend/package.json index 09402e1479..31b5fefa15 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,7 +1,7 @@ { "name": "unleash-frontend", "description": "unleash your features", - "version": "4.14.3", + "version": "4.15.0-beta.0", "keywords": [ "unleash", "feature toggle",