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:
parent
25ca7b7216
commit
72acf2309c
@ -2,16 +2,20 @@ import { useParams } from 'react-router';
|
|||||||
import useFeature from '../../../../hooks/api/getters/useFeature/useFeature';
|
import useFeature from '../../../../hooks/api/getters/useFeature/useFeature';
|
||||||
import { useStyles } from './FeatureLog.styles';
|
import { useStyles } from './FeatureLog.styles';
|
||||||
import { IFeatureViewParams } from '../../../../interfaces/params';
|
import { IFeatureViewParams } from '../../../../interfaces/params';
|
||||||
import HistoryComponent from '../../../history/FeatureEventHistory';
|
import { FeatureEventHistory } from '../../../history/FeatureEventHistory/FeatureEventHistory';
|
||||||
|
|
||||||
const FeatureLog = () => {
|
const FeatureLog = () => {
|
||||||
const styles = useStyles();
|
const styles = useStyles();
|
||||||
const { projectId, featureId } = useParams<IFeatureViewParams>();
|
const { projectId, featureId } = useParams<IFeatureViewParams>();
|
||||||
const { feature } = useFeature(projectId, featureId);
|
const { feature } = useFeature(projectId, featureId);
|
||||||
|
|
||||||
|
if (!feature.name) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.container}>
|
<div className={styles.container}>
|
||||||
<HistoryComponent toggleName={feature.name} />
|
<FeatureEventHistory toggleName={feature.name} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -1,22 +1,12 @@
|
|||||||
import { useEffect } from 'react';
|
|
||||||
|
|
||||||
import EventLog from '../EventLog';
|
import EventLog from '../EventLog';
|
||||||
|
import { useEvents } from '../../../hooks/api/getters/useEvents/useEvents';
|
||||||
|
|
||||||
interface IEventLogProps {
|
export const EventHistory = () => {
|
||||||
fetchHistory: () => void;
|
const { events } = useEvents();
|
||||||
history: History;
|
|
||||||
}
|
|
||||||
|
|
||||||
const EventHistory = ({ fetchHistory, history }: IEventLogProps) => {
|
if (events.length < 0) {
|
||||||
useEffect(() => {
|
|
||||||
fetchHistory();
|
|
||||||
}, [fetchHistory]);
|
|
||||||
|
|
||||||
if (history.length < 0) {
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return <EventLog history={history} title="Recent changes" />;
|
return <EventLog history={events} title="Recent changes" />;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default EventHistory;
|
|
||||||
|
@ -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;
|
|
@ -1,29 +1,19 @@
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { useEffect } from 'react';
|
|
||||||
import EventLog from '../EventLog';
|
import EventLog from '../EventLog';
|
||||||
|
import { useFeatureEvents } from '../../../hooks/api/getters/useFeatureEvents/useFeatureEvents';
|
||||||
|
|
||||||
const FeatureEventHistory = ({
|
export const FeatureEventHistory = ({ toggleName }) => {
|
||||||
toggleName,
|
const { events } = useFeatureEvents(toggleName);
|
||||||
history,
|
|
||||||
fetchHistoryForToggle,
|
|
||||||
}) => {
|
|
||||||
useEffect(() => {
|
|
||||||
fetchHistoryForToggle(toggleName);
|
|
||||||
}, [fetchHistoryForToggle, toggleName]);
|
|
||||||
|
|
||||||
if (!history || history.length === 0) {
|
if (events.length === 0) {
|
||||||
return <span>fetching..</span>;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<EventLog history={history} hideName title="Change log" displayInline />
|
<EventLog history={events} hideName title="Change log" displayInline />
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
FeatureEventHistory.propTypes = {
|
FeatureEventHistory.propTypes = {
|
||||||
toggleName: PropTypes.string.isRequired,
|
toggleName: PropTypes.string.isRequired,
|
||||||
history: PropTypes.array,
|
|
||||||
fetchHistoryForToggle: PropTypes.func.isRequired,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default FeatureEventHistory;
|
|
||||||
|
@ -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;
|
|
39
frontend/src/hooks/api/getters/useEvents/useEvents.ts
Normal file
39
frontend/src/hooks/api/getters/useEvents/useEvents.ts
Normal 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());
|
||||||
|
};
|
@ -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());
|
||||||
|
};
|
14
frontend/src/interfaces/event.ts
Normal file
14
frontend/src/interfaces/event.ts
Normal 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[];
|
||||||
|
}
|
@ -2,16 +2,16 @@ import { Alert } from '@material-ui/lab';
|
|||||||
import React, { useContext } from 'react';
|
import React, { useContext } from 'react';
|
||||||
import { ADMIN } from '../../component/providers/AccessProvider/permissions';
|
import { ADMIN } from '../../component/providers/AccessProvider/permissions';
|
||||||
import ConditionallyRender from '../../component/common/ConditionallyRender';
|
import ConditionallyRender from '../../component/common/ConditionallyRender';
|
||||||
import HistoryComponent from '../../component/history/EventHistory';
|
import { EventHistory } from '../../component/history/EventHistory/EventHistory';
|
||||||
import AccessContext from '../../contexts/AccessContext';
|
import AccessContext from '../../contexts/AccessContext';
|
||||||
|
|
||||||
const HistoryPage = ({ history }) => {
|
const HistoryPage = () => {
|
||||||
const { hasAccess } = useContext(AccessContext);
|
const { hasAccess } = useContext(AccessContext);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ConditionallyRender
|
<ConditionallyRender
|
||||||
condition={hasAccess(ADMIN)}
|
condition={hasAccess(ADMIN)}
|
||||||
show={<HistoryComponent />}
|
show={<EventHistory />}
|
||||||
elseShow={
|
elseShow={
|
||||||
<Alert severity="error">
|
<Alert severity="error">
|
||||||
You need instance admin to access this section.
|
You need instance admin to access this section.
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import HistoryListToggle from '../../component/history/FeatureEventHistory';
|
import { FeatureEventHistory } from '../../component/history/FeatureEventHistory/FeatureEventHistory';
|
||||||
|
|
||||||
const render = ({ match: { params } }) => (
|
const render = ({ match: { params } }) => (
|
||||||
<HistoryListToggle toggleName={params.toggleName} />
|
<FeatureEventHistory toggleName={params.toggleName} />
|
||||||
);
|
);
|
||||||
|
|
||||||
render.propTypes = {
|
render.propTypes = {
|
@ -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));
|
|
||||||
}
|
|
@ -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,
|
|
||||||
};
|
|
@ -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;
|
|
@ -6,7 +6,6 @@ import featureTags from './feature-tags';
|
|||||||
import tagTypes from './tag-type';
|
import tagTypes from './tag-type';
|
||||||
import tags from './tag';
|
import tags from './tag';
|
||||||
import strategies from './strategy';
|
import strategies from './strategy';
|
||||||
import history from './history'; // eslint-disable-line
|
|
||||||
import archive from './archive';
|
import archive from './archive';
|
||||||
import error from './error';
|
import error from './error';
|
||||||
import settings from './settings';
|
import settings from './settings';
|
||||||
@ -29,7 +28,6 @@ const unleashStore = combineReducers({
|
|||||||
tagTypes,
|
tagTypes,
|
||||||
tags,
|
tags,
|
||||||
featureTags,
|
featureTags,
|
||||||
history,
|
|
||||||
archive,
|
archive,
|
||||||
error,
|
error,
|
||||||
settings,
|
settings,
|
||||||
|
Loading…
Reference in New Issue
Block a user