1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-02-09 00:18:00 +01:00

refactor: port EventHistory to TS/SWR (#669)

* refactor: port EventHistory to TS/SWR

* refactor: fix interface type prefix

* refactor: split useEvents and useFeatureEvents hooks

Co-authored-by: Fredrik Strand Oseberg <fredrik.no@gmail.com>
This commit is contained in:
olav 2022-02-04 14:25:56 +01:00 committed by GitHub
parent 25ca7b7216
commit 72acf2309c
14 changed files with 117 additions and 158 deletions

View File

@ -2,16 +2,20 @@ import { useParams } from 'react-router';
import useFeature from '../../../../hooks/api/getters/useFeature/useFeature';
import { useStyles } from './FeatureLog.styles';
import { IFeatureViewParams } from '../../../../interfaces/params';
import HistoryComponent from '../../../history/FeatureEventHistory';
import { FeatureEventHistory } from '../../../history/FeatureEventHistory/FeatureEventHistory';
const FeatureLog = () => {
const styles = useStyles();
const { projectId, featureId } = useParams<IFeatureViewParams>();
const { feature } = useFeature(projectId, featureId);
if (!feature.name) {
return null
}
return (
<div className={styles.container}>
<HistoryComponent toggleName={feature.name} />
<FeatureEventHistory toggleName={feature.name} />
</div>
);
};

View File

@ -1,22 +1,12 @@
import { useEffect } from 'react';
import EventLog from '../EventLog';
import { useEvents } from '../../../hooks/api/getters/useEvents/useEvents';
interface IEventLogProps {
fetchHistory: () => void;
history: History;
}
export const EventHistory = () => {
const { events } = useEvents();
const EventHistory = ({ fetchHistory, history }: IEventLogProps) => {
useEffect(() => {
fetchHistory();
}, [fetchHistory]);
if (history.length < 0) {
if (events.length < 0) {
return null;
}
return <EventLog history={history} title="Recent changes" />;
return <EventLog history={events} title="Recent changes" />;
};
export default EventHistory;

View File

@ -1,16 +0,0 @@
import { connect } from 'react-redux';
import EventHistory from './EventHistory';
import { fetchHistory } from '../../../store/history/actions';
const mapStateToProps = state => {
const history = state.history.get('list').toArray();
return {
history,
};
};
const EventHistoryContainer = connect(mapStateToProps, { fetchHistory })(
EventHistory
);
export default EventHistoryContainer;

View File

@ -1,29 +1,19 @@
import PropTypes from 'prop-types';
import { useEffect } from 'react';
import EventLog from '../EventLog';
import { useFeatureEvents } from '../../../hooks/api/getters/useFeatureEvents/useFeatureEvents';
const FeatureEventHistory = ({
toggleName,
history,
fetchHistoryForToggle,
}) => {
useEffect(() => {
fetchHistoryForToggle(toggleName);
}, [fetchHistoryForToggle, toggleName]);
export const FeatureEventHistory = ({ toggleName }) => {
const { events } = useFeatureEvents(toggleName);
if (!history || history.length === 0) {
return <span>fetching..</span>;
if (events.length === 0) {
return null;
}
return (
<EventLog history={history} hideName title="Change log" displayInline />
<EventLog history={events} hideName title="Change log" displayInline />
);
};
FeatureEventHistory.propTypes = {
toggleName: PropTypes.string.isRequired,
history: PropTypes.array,
fetchHistoryForToggle: PropTypes.func.isRequired,
};
export default FeatureEventHistory;

View File

@ -1,25 +0,0 @@
import { connect } from 'react-redux';
import FeatureEventHistory from './FeatureEventHistory';
import { fetchHistoryForToggle } from '../../../store/history/actions';
function getHistoryFromToggle(state, toggleName) {
if (!toggleName) {
return [];
}
if (state.history.hasIn(['toggles', toggleName])) {
return state.history.getIn(['toggles', toggleName]).toArray();
}
return [];
}
const mapStateToProps = (state, props) => ({
history: getHistoryFromToggle(state, props.toggleName),
});
const FeatureEventHistoryContainer = connect(mapStateToProps, {
fetchHistoryForToggle,
})(FeatureEventHistory);
export default FeatureEventHistoryContainer;

View File

@ -0,0 +1,39 @@
import useSWR, { mutate, SWRConfiguration } from 'swr';
import { useCallback } from 'react';
import { formatApiPath } from '../../../../utils/format-path';
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());
};

View File

@ -0,0 +1,42 @@
import useSWR, { mutate, SWRConfiguration } from 'swr';
import { useCallback } from 'react';
import { formatApiPath } from '../../../../utils/format-path';
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 } = useSWR<{ events: IEvent[] }>(
[PATH, featureName],
() => fetchFeatureEvents(featureName),
options
);
const refetchEvents = useCallback(() => {
mutate(PATH).catch(console.warn);
}, []);
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());
};

View File

@ -0,0 +1,14 @@
import { ITag } from './tags';
export interface IEvent {
id: number;
createdAt: string;
type: string;
createdBy: string;
project?: string;
environment?: string;
featureName?: string;
data?: any;
preData?: any;
tags?: ITag[];
}

View File

@ -2,16 +2,16 @@ import { Alert } from '@material-ui/lab';
import React, { useContext } from 'react';
import { ADMIN } from '../../component/providers/AccessProvider/permissions';
import ConditionallyRender from '../../component/common/ConditionallyRender';
import HistoryComponent from '../../component/history/EventHistory';
import { EventHistory } from '../../component/history/EventHistory/EventHistory';
import AccessContext from '../../contexts/AccessContext';
const HistoryPage = ({ history }) => {
const HistoryPage = () => {
const { hasAccess } = useContext(AccessContext);
return (
<ConditionallyRender
condition={hasAccess(ADMIN)}
show={<HistoryComponent />}
show={<EventHistory />}
elseShow={
<Alert severity="error">
You need instance admin to access this section.

View File

@ -1,9 +1,9 @@
import React from 'react';
import PropTypes from 'prop-types';
import HistoryListToggle from '../../component/history/FeatureEventHistory';
import { FeatureEventHistory } from '../../component/history/FeatureEventHistory/FeatureEventHistory';
const render = ({ match: { params } }) => (
<HistoryListToggle toggleName={params.toggleName} />
<FeatureEventHistory toggleName={params.toggleName} />
);
render.propTypes = {

View File

@ -1,33 +0,0 @@
import api from './api';
import { dispatchError } from '../util';
export const RECEIVE_HISTORY = 'RECEIVE_HISTORY';
export const ERROR_RECEIVE_HISTORY = 'ERROR_RECEIVE_HISTORY';
export const RECEIVE_HISTORY_FOR_TOGGLE = 'RECEIVE_HISTORY_FOR_TOGGLE';
const receiveHistory = json => ({
type: RECEIVE_HISTORY,
value: json.events,
});
const receiveHistoryforToggle = json => ({
type: RECEIVE_HISTORY_FOR_TOGGLE,
value: json,
});
export function fetchHistory() {
return dispatch =>
api
.fetchAll()
.then(json => dispatch(receiveHistory(json)))
.catch(dispatchError(dispatch, ERROR_RECEIVE_HISTORY));
}
export function fetchHistoryForToggle(toggleName) {
return dispatch =>
api
.fetchHistoryForToggle(toggleName)
.then(json => dispatch(receiveHistoryforToggle(json)))
.catch(dispatchError(dispatch, ERROR_RECEIVE_HISTORY));
}

View File

@ -1,21 +0,0 @@
import { formatApiPath } from '../../utils/format-path';
import { throwIfNotSuccess } from '../api-helper';
const URI = formatApiPath('api/admin/events');
function fetchAll() {
return fetch(URI, { credentials: 'include' })
.then(throwIfNotSuccess)
.then(response => response.json());
}
function fetchHistoryForToggle(toggleName) {
return fetch(`${URI}/${toggleName}`, { credentials: 'include' })
.then(throwIfNotSuccess)
.then(response => response.json());
}
export default {
fetchAll,
fetchHistoryForToggle,
};

View File

@ -1,23 +0,0 @@
import { List, Map as $Map } from 'immutable';
import { RECEIVE_HISTORY, RECEIVE_HISTORY_FOR_TOGGLE } from './actions';
import { USER_LOGOUT, USER_LOGIN } from '../user/actions';
function getInitState() {
return new $Map({ list: new List(), toggles: new $Map() });
}
const historyStore = (state = getInitState(), action) => {
switch (action.type) {
case RECEIVE_HISTORY_FOR_TOGGLE:
return state.setIn(['toggles', action.value.toggleName], new List(action.value.events));
case RECEIVE_HISTORY:
return state.set('list', new List(action.value));
case USER_LOGOUT:
case USER_LOGIN:
return getInitState();
default:
return state;
}
};
export default historyStore;

View File

@ -6,7 +6,6 @@ import featureTags from './feature-tags';
import tagTypes from './tag-type';
import tags from './tag';
import strategies from './strategy';
import history from './history'; // eslint-disable-line
import archive from './archive';
import error from './error';
import settings from './settings';
@ -29,7 +28,6 @@ const unleashStore = combineReducers({
tagTypes,
tags,
featureTags,
history,
archive,
error,
settings,