refactor: added translations , removed redundent code, fixed tests

This commit is contained in:
AhtishamShahid
2023-01-13 11:40:37 +05:00
parent 5f563ab702
commit 5c75651481
11 changed files with 112 additions and 102 deletions

View File

@@ -24,7 +24,7 @@ import { EmptyTopic as InContextEmptyTopics } from '../in-context-topics/compone
import messages from '../messages';
import { LegacyBreadcrumbMenu, NavigationBar } from '../navigation';
import { selectPostEditorVisible } from '../posts/data/selectors';
import NotRespondedFilter from '../tours/NotRespondedFilter';
import NotRespondedFilterTour from '../tours/NotRespondedFilter';
import { postMessageToParent } from '../utils';
import BlackoutInformationBanner from './BlackoutInformationBanner';
import DiscussionContent from './DiscussionContent';
@@ -102,7 +102,6 @@ export default function DiscussionsHome() {
component={LegacyBreadcrumbMenu}
/>
)}
<NotRespondedFilter />
<div className="d-flex flex-row">
<DiscussionSidebar displaySidebar={displaySidebar} postActionBarRef={postActionBarRef} />
@@ -125,6 +124,7 @@ export default function DiscussionsHome() {
</Switch>
)}
</div>
<NotRespondedFilterTour />
</main>
{!enableInContextSidebar && <Footer />}
</DiscussionContext.Provider>

View File

@@ -148,12 +148,15 @@ function PostFilterBar({
cohort: capitalize(selectedCohort?.name),
})}
</span>
<Collapsible.Visible whenClosed>
<Icon src={Tune} />
</Collapsible.Visible>
<Collapsible.Visible whenOpen>
<Icon src={Tune} />
</Collapsible.Visible>
<span id="icon-tune">
<Collapsible.Visible whenClosed>
<Icon src={Tune} />
</Collapsible.Visible>
<Collapsible.Visible whenOpen>
<Icon src={Tune} />
</Collapsible.Visible>
</span>
</Collapsible.Trigger>
<Collapsible.Body className="collapsible-body px-4 pb-3 pt-0">
<Form>

View File

@@ -1,32 +1,36 @@
import { useEffect } from 'react';
import { useContext, useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { ProductTour } from '@edx/paragon';
import { DiscussionContext } from '../common/context';
import { notRespondedFilterTour } from './data/selectors';
import { fetchDiscussionTours, updateTourShowStatus } from './data/thunks';
import messages from './messages';
export default () => {
function NotRespondedFilterTour({ intl }) {
const dispatch = useDispatch();
useEffect(() => {
dispatch(fetchDiscussionTours());
}, []);
const tourData = useSelector(notRespondedFilterTour);
const { enableInContextSidebar } = useContext(DiscussionContext);
const config = {
tourId: 'notRespondedTour',
advanceButtonText: 'Next',
dismissButtonText: 'Dismiss',
endButtonText: 'Okay',
enabled: tourData ? tourData.showTour : false,
advanceButtonText: intl.formatMessage(messages.advanceButtonText),
dismissButtonText: intl.formatMessage(messages.dismissButtonText),
endButtonText: intl.formatMessage(messages.endButtonText),
enabled: tourData ? tourData.showTour && !enableInContextSidebar : false,
onDismiss: () => dispatch(updateTourShowStatus(tourData.id)),
onEnd: () => dispatch(updateTourShowStatus(tourData.id)),
checkpoints: [
{
body: 'Now you can filter discussions .',
body: intl.formatMessage(messages.notRespondedFilterTourBody),
placement: 'right',
target: '#icon-tune',
title: 'New filtering option!',
title: intl.formatMessage(messages.notRespondedFilterTourTitle),
},
],
@@ -39,4 +43,10 @@ export default () => {
/>
</>
);
}
NotRespondedFilterTour.propTypes = {
intl: intlShape.isRequired,
};
export default injectIntl(NotRespondedFilterTour);

View File

@@ -0,0 +1 @@
export * from './slices';

View File

@@ -3,16 +3,15 @@ import MockAdapter from 'axios-mock-adapter';
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
import { initializeMockApp } from '@edx/frontend-platform/testing';
import { RequestStatus } from '../../../data/constants';
import { initializeStore } from '../../../store';
import { getDiscussionTourUrl } from './api';
import { notRespondedFilterTour } from './selectors';
import {
fetchUserDiscussionsToursError,
fetchUserDiscussionsToursRequest,
discussionsTourRequest,
discussionsToursRequestError,
fetchUserDiscussionsToursSuccess,
toursReducer,
updateUserDiscussionsTourError,
updateUserDiscussionsTourRequest,
updateUserDiscussionsTourSuccess,
} from './slices';
import { fetchDiscussionTours, updateTourShowStatus } from './thunks';
@@ -55,7 +54,7 @@ describe('DiscussionToursThunk', () => {
const expectedActions = [
{
payload: undefined,
type: 'userDiscussionsTours/fetchUserDiscussionsToursRequest',
type: 'userDiscussionsTours/discussionsTourRequest',
},
{
type: 'userDiscussionsTours/fetchUserDiscussionsToursSuccess',
@@ -72,10 +71,10 @@ describe('DiscussionToursThunk', () => {
.reply(500);
const errorAction = [{
payload: undefined,
type: 'userDiscussionsTours/fetchUserDiscussionsToursRequest',
type: 'userDiscussionsTours/discussionsTourRequest',
}, {
payload: undefined,
type: 'userDiscussionsTours/fetchUserDiscussionsToursError',
type: 'userDiscussionsTours/discussionsToursRequestError',
}];
await fetchDiscussionTours()(dispatch);
@@ -91,7 +90,7 @@ describe('DiscussionToursThunk', () => {
const expectedActions = [
{
payload: undefined,
type: 'userDiscussionsTours/updateUserDiscussionsTourRequest',
type: 'userDiscussionsTours/discussionsTourRequest',
},
{
type: 'userDiscussionsTours/updateUserDiscussionsTourSuccess',
@@ -108,10 +107,10 @@ describe('DiscussionToursThunk', () => {
.reply(500);
const errorAction = [{
payload: undefined,
type: 'userDiscussionsTours/updateUserDiscussionsTourRequest',
type: 'userDiscussionsTours/discussionsTourRequest',
}, {
payload: undefined,
type: 'userDiscussionsTours/updateUserDiscussionsTourError',
type: 'userDiscussionsTours/discussionsToursRequestError',
}];
await updateTourShowStatus(1)(dispatch);
@@ -121,17 +120,17 @@ describe('DiscussionToursThunk', () => {
});
describe('toursReducer', () => {
it('handles the fetchUserDiscussionsToursRequest action', () => {
it('handles the discussionsToursRequest action', () => {
const initialState = {
tours: [],
loading: false,
error: null,
};
const state = toursReducer(initialState, fetchUserDiscussionsToursRequest());
const state = toursReducer(initialState, discussionsTourRequest());
expect(state)
.toEqual({
tours: [],
loading: true,
loading: RequestStatus.IN_PROGRESS,
error: null,
});
});
@@ -147,38 +146,7 @@ describe('toursReducer', () => {
expect(state)
.toEqual({
tours: mockData,
loading: false,
error: null,
});
});
it('handles the fetchUserDiscussionsToursError action', () => {
const initialState = {
tours: [],
loading: true,
error: null,
};
const mockError = new Error('Something went wrong');
const state = toursReducer(initialState, fetchUserDiscussionsToursError(mockError));
expect(state)
.toEqual({
tours: [],
loading: false,
error: mockError,
});
});
it('handles the updateUserDiscussionsTourRequest action', () => {
const initialState = {
tours: [],
loading: false,
error: null,
};
const state = toursReducer(initialState, updateUserDiscussionsTourRequest());
expect(state)
.toEqual({
tours: [],
loading: true,
loading: RequestStatus.SUCCESSFUL,
error: null,
});
});
@@ -199,23 +167,23 @@ describe('toursReducer', () => {
expect(state.tours)
.toEqual({
tours: [{ id: 1 }, updatedTour],
loading: false,
loading: RequestStatus.SUCCESSFUL,
error: null,
});
});
it('handles the updateUserDiscussionsTourError action', () => {
it('handles the discussionsToursRequestError action', () => {
const initialState = {
tours: [],
loading: true,
error: null,
};
const mockError = new Error('Something went wrong');
const state = toursReducer(initialState, updateUserDiscussionsTourError(mockError));
const state = toursReducer(initialState, discussionsToursRequestError(mockError));
expect(state)
.toEqual({
tours: [],
loading: false,
loading: RequestStatus.FAILED,
error: mockError,
});
});
@@ -236,8 +204,13 @@ describe('notRespondedFilterTour', () => {
});
it('returns an empty object if the tours state is not defined', () => {
const state = {};
expect(notRespondedFilterTour(state)).toEqual({});
const state = {
tours: {
tours: [],
},
};
expect(notRespondedFilterTour(state))
.toEqual(null);
});
it('returns an empty object if the tours state does not contain not_responded_filter', () => {
@@ -249,6 +222,6 @@ describe('notRespondedFilterTour', () => {
],
},
};
expect(notRespondedFilterTour(state)).toEqual({});
expect(notRespondedFilterTour(state)).toEqual(null);
});
});

View File

@@ -1,11 +1,10 @@
// eslint-disable-next-line import/prefer-default-export
export const notRespondedFilterTour = ({ tours }) => {
export const selectTours = (state) => state.tours.tours;
export const notRespondedFilterTour = (state) => {
// This function filters the tours list in the state by the tour_name 'not_responded_filter'
// and returns the filtered list. This can be useful for displaying only the 'not_responded_filter'
// tours to the user, for example in a list or table.
if (!tours) {
return {};
}
const response = tours.tours.find(tour => tour.tourName === 'not_responded_filter');
return response || {};
const response = selectTours(state)
.find(tour => tour.tourName === 'not_responded_filter');
return response || null;
};

View File

@@ -2,6 +2,8 @@
import { createSlice } from '@reduxjs/toolkit';
import { RequestStatus } from '../../../data/constants';
const userDiscussionsToursSlice = createSlice({
name: 'userDiscussionsTours',
initialState: {
@@ -10,43 +12,33 @@ const userDiscussionsToursSlice = createSlice({
error: null,
},
reducers: {
fetchUserDiscussionsToursRequest: (state) => {
state.loading = true;
discussionsTourRequest: (state) => {
state.loading = RequestStatus.IN_PROGRESS;
state.error = null;
},
fetchUserDiscussionsToursSuccess: (state, action) => {
state.tours = action.payload;
state.loading = false;
state.loading = RequestStatus.SUCCESSFUL;
state.error = null;
},
fetchUserDiscussionsToursError: (state, action) => {
state.loading = false;
discussionsToursRequestError: (state, action) => {
state.loading = RequestStatus.FAILED;
state.error = action.payload;
},
updateUserDiscussionsTourRequest: (state) => {
state.loading = true;
state.error = null;
},
updateUserDiscussionsTourSuccess: (state, action) => {
const tourIndex = state.tours.tours.findIndex(tour => tour.id === action.payload.id);
state.tours.tours[tourIndex] = action.payload;
state.tours.loading = false;
state.tours.loading = RequestStatus.SUCCESSFUL;
state.tours.error = null;
},
updateUserDiscussionsTourError: (state, action) => {
state.loading = false;
state.error = action.payload;
},
},
});
export const {
fetchUserDiscussionsToursRequest,
discussionsTourRequest,
fetchUserDiscussionsToursSuccess,
fetchUserDiscussionsToursError,
updateUserDiscussionsTourRequest,
discussionsToursRequestError,
updateUserDiscussionsTourSuccess,
updateUserDiscussionsTourError,
} = userDiscussionsToursSlice.actions;
export const toursReducer = userDiscussionsToursSlice.reducer;

View File

@@ -3,11 +3,9 @@ import { logError } from '@edx/frontend-platform/logging';
import { getDiscssionTours, updateDiscussionTour } from './api';
import {
fetchUserDiscussionsToursError,
fetchUserDiscussionsToursRequest,
discussionsTourRequest,
discussionsToursRequestError,
fetchUserDiscussionsToursSuccess,
updateUserDiscussionsTourError,
updateUserDiscussionsTourRequest,
updateUserDiscussionsTourSuccess,
} from './slices';
@@ -18,11 +16,11 @@ import {
export function fetchDiscussionTours() {
return async (dispatch) => {
try {
dispatch(fetchUserDiscussionsToursRequest());
dispatch(discussionsTourRequest());
const data = await getDiscssionTours();
dispatch(fetchUserDiscussionsToursSuccess(camelCaseObject(data)));
} catch (error) {
dispatch(fetchUserDiscussionsToursError());
dispatch(discussionsToursRequestError());
logError(error);
}
};
@@ -37,11 +35,11 @@ export function fetchDiscussionTours() {
export function updateTourShowStatus(tourId) {
return async (dispatch) => {
try {
dispatch(updateUserDiscussionsTourRequest());
dispatch(discussionsTourRequest());
const data = await updateDiscussionTour(tourId);
dispatch(updateUserDiscussionsTourSuccess(camelCaseObject(data)));
} catch (error) {
dispatch(updateUserDiscussionsTourError());
dispatch(discussionsToursRequestError());
logError(error);
}
};

View File

@@ -0,0 +1,31 @@
import { defineMessages } from '@edx/frontend-platform/i18n';
const messages = defineMessages({
advanceButtonText: {
id: 'tour.action.advance',
defaultMessage: 'Next',
description: 'Action to go to next step of tour',
},
dismissButtonText: {
id: 'tour.action.dismiss',
defaultMessage: 'Dismiss',
description: 'Action to dismiss current tour',
},
endButtonText: {
id: 'tour.action.end',
defaultMessage: 'Okay',
description: 'Action to end current tour',
},
notRespondedFilterTourBody: {
id: 'tour.body.notRespondedFilter',
defaultMessage: 'Now you can filter discussions to find posts with no response.',
description: 'Body of the tour for the not responded filter',
},
notRespondedFilterTourTitle: {
id: 'tour.title.notRespondedFilter',
defaultMessage: 'New filtering option!',
description: 'Title of the tour for the not responded filter',
},
});
export default messages;

View File

@@ -283,3 +283,6 @@ header {
padding: 1px 5px !important;
}
.pgn__checkpoint {
max-width: 340px !important;
}

View File

@@ -9,7 +9,7 @@ import { inContextTopicsReducer } from './discussions/in-context-topics/data';
import { learnersReducer } from './discussions/learners/data';
import { threadsReducer } from './discussions/posts/data';
import { topicsReducer } from './discussions/topics/data';
import { toursReducer } from './discussions/tours/data/slices';
import { toursReducer } from './discussions/tours/data';
export function initializeStore(preloadedState = undefined) {
return configureStore({