Skip to content

Commit

Permalink
RN-506 lesmis favourite dashboard (#4167)
Browse files Browse the repository at this point in the history
  • Loading branch information
biaoli0 authored Sep 20, 2022
1 parent f080e66 commit 511980d
Show file tree
Hide file tree
Showing 51 changed files with 1,135 additions and 263 deletions.
23 changes: 0 additions & 23 deletions packages/central-server/src/apiV2/deleteMyAccount.js

This file was deleted.

3 changes: 2 additions & 1 deletion packages/central-server/src/apiV2/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ import {
GETSyncGroupLogsCount,
ManuallySyncSyncGroup,
} from './syncGroups';

import { POSTUpdateUserFavouriteDashboardItem } from './userFavouriteDashboardItem';
// quick and dirty permission wrapper for open endpoints
const allowAnyone = routeHandler => (req, res, next) => {
req.assertPermissions(allowNoPermissions);
Expand Down Expand Up @@ -259,6 +259,7 @@ apiV2.post('/dashboardRelations', useRouteHandler(CreateDashboardRelation));
apiV2.post('/dashboardVisualisations', useRouteHandler(CreateDashboardVisualisation));
apiV2.post('/mapOverlayVisualisations', useRouteHandler(CreateMapOverlayVisualisation));
apiV2.post('/mapOverlayGroupRelations', useRouteHandler(CreateMapOverlayGroupRelation));
apiV2.post('/userFavouriteDashboardItems', useRouteHandler(POSTUpdateUserFavouriteDashboardItem));
apiV2.post('/projects', useRouteHandler(CreateProject));
apiV2.post('/dataServiceSyncGroups', useRouteHandler(CreateSyncGroups));
apiV2.post('/dataServiceSyncGroups/:recordId/sync', useRouteHandler(ManuallySyncSyncGroup));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/**
* Tupaia
* Copyright (c) 2017 - 2022 Beyond Essential Systems Pty Ltd
*/

import { respond } from '@tupaia/utils';
import { assertAnyPermissions } from '../../permissions';
import { CRUDHandler } from '../CRUDHandler';

/**
* Handles POST endpoints:
* - /userFavouriteDashboardItem
*/

export class POSTUpdateUserFavouriteDashboardItem extends CRUDHandler {
async assertUserHasAccess() {
const assertUserIsLoggedIn = () => {
const { userId } = this.req;
if (!userId) {
throw new Error('User should be logged in');
}
};

return this.assertPermissions(assertAnyPermissions([assertUserIsLoggedIn]));
}

async handleRequest() {
const { models, body, userId } = this.req;
const { dashboardItemCode, state } = body;

const user = await models.user.findOne({ id: userId });
const dashboardItemCodeToId = await models.dashboardItem.findIdByCode(dashboardItemCode);
const dashboardItemId = dashboardItemCodeToId[dashboardItemCode];
if (!user || !dashboardItemId) {
throw new Error(`user or dashboard item not found`);
}

if (state === 'favourite') {
await models.userFavouriteDashboardItem.favourite(userId, dashboardItemId);
}
if (state === 'unfavourite') {
await models.userFavouriteDashboardItem.unfavourite(userId, dashboardItemId);
}

respond(this.res, { message: 'successfully modified' });
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './POSTUpdateUserFavouriteDashboardItem';
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
'use strict';

var dbm;
var type;
var seed;

/**
* We receive the dbmigrate dependency from dbmigrate initially.
* This enables us to not have to rely on NODE_PATH.
*/
exports.setup = function (options, seedLink) {
dbm = options.dbmigrate;
type = dbm.dataType;
seed = seedLink;
};

exports.up = function (db) {
return db.runSql(`
CREATE TABLE user_favourite_dashboard_item (
id TEXT PRIMARY KEY,
user_id TEXT NOT NULL,
dashboard_item_id TEXT NOT NULL,
UNIQUE (user_id, dashboard_item_id),
FOREIGN KEY (user_id) REFERENCES user_account (id) ON UPDATE CASCADE ON DELETE CASCADE,
FOREIGN KEY (dashboard_item_id) REFERENCES dashboard_item (id) ON UPDATE CASCADE ON DELETE CASCADE
);
CREATE INDEX user_favourite_dashboard_item_user_id_idx ON user_favourite_dashboard_item USING btree (user_id);
`);
};

exports.down = function (db) {
return db.runSql(`
DROP TABLE public.user_favourite_dashboard_item;
`);
};

exports._meta = {
version: 1,
};
36 changes: 36 additions & 0 deletions packages/database/src/modelClasses/UserFavouriteDashboardItem.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/**
* Tupaia
* Copyright (c) 2017 - 2022 Beyond Essential Systems Pty Ltd
*/

import { DatabaseModel } from '../DatabaseModel';
import { DatabaseType } from '../DatabaseType';
import { TYPES } from '../types';

class UserFavouriteDashboardItemType extends DatabaseType {
static databaseType = TYPES.USER_FAVOURITE_DASHBOARD_ITEM;

static joins = [
{
fields: {
code: 'dashboard_item_code',
},
joinWith: TYPES.DASHBOARD_ITEM,
joinCondition: ['dashboard_item.id', 'user_favourite_dashboard_item.dashboard_item_id'],
},
];
}

export class UserFavouriteDashboardItemModel extends DatabaseModel {
get DatabaseTypeClass() {
return UserFavouriteDashboardItemType;
}

async favourite(userId, dashboardItemId) {
return this.findOrCreate({ dashboard_item_id: dashboardItemId, user_id: userId });
}

async unfavourite(userId, dashboardItemId) {
return this.delete({ dashboard_item_id: dashboardItemId, user_id: userId });
}
}
2 changes: 2 additions & 0 deletions packages/database/src/modelClasses/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import { FeedItemModel } from './FeedItem';
import { GeographicalAreaModel } from './GeographicalArea';
import { IndicatorModel } from './Indicator';
import { LegacyReportModel } from './LegacyReport';
import { UserFavouriteDashboardItemModel } from './UserFavouriteDashboardItem';
import { MapOverlayGroupModel } from './MapOverlayGroup';
import { MapOverlayGroupRelationModel } from './MapOverlayGroupRelation';
import { MapOverlayModel } from './MapOverlay';
Expand Down Expand Up @@ -111,6 +112,7 @@ export const modelClasses = {
SyncGroupLog: SyncGroupLogModel,
User: UserModel,
UserEntityPermission: UserEntityPermissionModel,
UserFavouriteDashboardItem: UserFavouriteDashboardItemModel,
UserSession: UserSessionModel,
};

Expand Down
1 change: 1 addition & 0 deletions packages/database/src/types.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,5 +62,6 @@ export const TYPES = {
SYNC_GROUP_LOG: 'sync_group_log',
USER_ACCOUNT: 'user_account',
USER_ENTITY_PERMISSION: 'user_entity_permission',
USER_FAVOURITE_DASHBOARD_ITEM: 'user_favourite_dashboard_item',
USER_SESSION: 'userSession',
};
1 change: 1 addition & 0 deletions packages/lesmis/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
"camelcase": "^6.2.0",
"dom-to-image": "^2.6.0",
"downloadjs": "^1.4.7",
"lodash.debounce": "^4.0.8",
"lodash.keyby": "^4.6.0",
"prop-types": "^15.7.2",
"react": "^16.13.1",
Expand Down
1 change: 1 addition & 0 deletions packages/lesmis/src/api/mutations/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ export * from './useLogin';
export * from './useLogout';
export * from './useRegisterUser';
export * from './useUpdateSurveyResponseStatus';
export * from './useUpdateFavouriteDashboardItem';
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/*
* Tupaia
* Copyright (c) 2017 - 2022 Beyond Essential Systems Pty Ltd
*/
import { useMutation, useQueryClient } from 'react-query';
import { post } from '../api';

const updateExistingDashboardData = async (queryClient, { entityCode }) => {
return queryClient.refetchQueries(['dashboard', entityCode]);
};

const useFavouriteDashboardItem = queryClient => {
return useMutation(
({ dashboardItemCode }) =>
post('userFavouriteDashboardItems', {
data: {
state: 'favourite',
dashboardItemCode,
},
}),
{
onSuccess: async (data, variables) => {
return updateExistingDashboardData(queryClient, variables);
},
},
);
};

const useUnfavouriteDashboardItem = queryClient =>
useMutation(
({ dashboardItemCode }) =>
post('userFavouriteDashboardItems', {
data: {
state: 'unfavourite',
dashboardItemCode,
},
}),
{
onSuccess: async (data, variables) => {
return updateExistingDashboardData(queryClient, variables);
},
},
);

export const useUpdateFavouriteDashboardItem = () => {
const queryClient = useQueryClient();
const favouriteMutation = useFavouriteDashboardItem(queryClient);
const unfavouriteMutation = useUnfavouriteDashboardItem(queryClient);

const mutate = ({ newFavouriteStatus, dashboardItemCode, entityCode }) => {
const mutation = newFavouriteStatus ? favouriteMutation : unfavouriteMutation;
mutation.mutate({ dashboardItemCode, entityCode });
};

return mutate;
};
6 changes: 4 additions & 2 deletions packages/lesmis/src/api/queries/useDashboardData.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ const getDrillDownCodes = dashboardItems =>
return codes;
}, []);

export const useDashboardData = ({ entityCode, includeDrillDowns = true }) => {
export const useDashboardData = ({ entityCode, includeDrillDowns = true, isFavouriteOnly }) => {
const query = useQuery(
['dashboard', entityCode],
() => get(`dashboard/${entityCode}`),
Expand All @@ -35,7 +35,9 @@ export const useDashboardData = ({ entityCode, includeDrillDowns = true }) => {
const data = query.data?.map(dashboard => {
const drillDownItemCodes = getDrillDownCodes(dashboard.items);

let dashboardItems = dashboard.items.filter(({ type }) => type === 'chart' || type === 'list'); // Only show supported chart types
let dashboardItems = dashboard.items
.filter(({ type }) => type === 'chart' || type === 'list') // Only show supported chart types
.filter(({ isFavourite }) => (isFavouriteOnly ? isFavourite : true));

if (!includeDrillDowns) {
// Remove the drill downs from the main dashboard items list
Expand Down
6 changes: 5 additions & 1 deletion packages/lesmis/src/api/queries/useDashboardReportData.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,11 @@ export const useDashboardReportDataWithConfig = ({
...query,
data: {
reportData: query.data?.reportData,
dashboardItemConfig: { ...dashboardItem, dashboardName: dashboard?.dashboardName },
dashboardItemConfig: {
...dashboardItem,
dashboardName: dashboard?.dashboardName,
dashboardCode: dashboard?.dashboardCode,
},
reportCodes,
},
};
Expand Down
3 changes: 2 additions & 1 deletion packages/lesmis/src/api/queries/useUser.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export const useUser = options => {
const user = query.data;
const isLoggedIn = user && Object.keys(user).length > 0;
const isLesmisAdmin = user && user.isLesmisAdmin;
const userId = user && user.id;

return { ...query, isLoggedIn, isLesmisAdmin };
return { ...query, isLoggedIn, isLesmisAdmin, userId };
};
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@ import { Divider, Typography } from '@material-ui/core';

import { EntityDetails, DashboardTitleContainer } from '../components';
import { DashboardReport } from '../../DashboardReport';
import { yearToApiDates } from '../../../api/queries/utils';
import { useUrlSearchParam } from '../../../utils';
import { useIsFavouriteDashboardSelected, useUrlSearchParam } from '../../../utils';
import { DEFAULT_DATA_YEAR } from '../../../constants';
import Header from '../components/Header';

Expand All @@ -20,8 +19,12 @@ export const DashboardReportPage = ({
PageContent,
...configs
}) => {
const [selectedYear] = useUrlSearchParam('year', DEFAULT_DATA_YEAR);
const { startDate, endDate } = useYearSelector ? yearToApiDates(selectedYear) : yearToApiDates();
// TODO: will be removed when implementing year selector for favourite dashboard, currently use default year.
const isFavouriteDashboardSelected = useIsFavouriteDashboardSelected();
const [selectedYear] = isFavouriteDashboardSelected
? [DEFAULT_DATA_YEAR]
: useUrlSearchParam('year', DEFAULT_DATA_YEAR);

return (
<PageContainer {...configs}>
<Header
Expand All @@ -39,11 +42,10 @@ export const DashboardReportPage = ({
<DashboardReport
reportCode={item.reportCode}
name={item.name}
startDate={startDate}
endDate={endDate}
exportOptions={exportOptions}
isExporting // render exporting format
isEnlarged // render exporting format
useYearSelector={useYearSelector}
/>
</PageContent>
</PageContainer>
Expand Down
Loading

0 comments on commit 511980d

Please sign in to comment.