Skip to content

Commit

Permalink
Support analytics page in portal
Browse files Browse the repository at this point in the history
  • Loading branch information
carmenlau committed Mar 1, 2022
2 parents 9386ddc + d4cb52c commit 74795d9
Show file tree
Hide file tree
Showing 28 changed files with 1,611 additions and 55 deletions.
4 changes: 2 additions & 2 deletions pkg/lib/analytic/chart_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ type ChartService struct {
AnalyticConfig *config.AnalyticConfig
}

func (s *ChartService) GetActiveUserChat(
func (s *ChartService) GetActiveUserChart(
appID string,
periodical string,
rangeFrom time.Time,
Expand Down Expand Up @@ -67,7 +67,7 @@ func (s *ChartService) GetActiveUserChat(
}, nil
}

func (s *ChartService) GetTotalUserCountChat(appID string, rangeFrom time.Time, rangeTo time.Time) (*Chart, error) {
func (s *ChartService) GetTotalUserCountChart(appID string, rangeFrom time.Time, rangeTo time.Time) (*Chart, error) {
if s.Database == nil {
return &Chart{}, nil
}
Expand Down
4 changes: 2 additions & 2 deletions pkg/portal/graphql/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,8 @@ type AppResourceManagerFactory interface {
}

type AnalyticChartService interface {
GetActiveUserChat(appID string, periodical string, rangeFrom time.Time, rangeTo time.Time) (*analytic.Chart, error)
GetTotalUserCountChat(appID string, rangeFrom time.Time, rangeTo time.Time) (*analytic.Chart, error)
GetActiveUserChart(appID string, periodical string, rangeFrom time.Time, rangeTo time.Time) (*analytic.Chart, error)
GetTotalUserCountChart(appID string, rangeFrom time.Time, rangeTo time.Time) (*analytic.Chart, error)
GetSignupConversionRate(appID string, rangeFrom time.Time, rangeTo time.Time) (*analytic.SignupConversionRateData, error)
GetSignupByMethodsChart(appID string, rangeFrom time.Time, rangeTo time.Time) (*analytic.Chart, error)
}
Expand Down
28 changes: 26 additions & 2 deletions pkg/portal/graphql/query.go
Original file line number Diff line number Diff line change
Expand Up @@ -126,12 +126,18 @@ var query = graphql.NewObject(graphql.ObjectConfig{
return nil, err
}

// Access Control: collaborator.
_, err = ctx.AuthzService.CheckAccessOfViewer(appID)
if err != nil {
return nil, nil
}

err = checkChartDateRangeInput(rangeFrom, rangeTo)
if err != nil {
return nil, err
}

chart, err := ctx.AnalyticChartService.GetActiveUserChat(
chart, err := ctx.AnalyticChartService.GetActiveUserChart(
appID,
periodical,
*rangeFrom,
Expand All @@ -154,12 +160,18 @@ var query = graphql.NewObject(graphql.ObjectConfig{
return nil, err
}

// Access Control: collaborator.
_, err = ctx.AuthzService.CheckAccessOfViewer(appID)
if err != nil {
return nil, nil
}

err = checkChartDateRangeInput(rangeFrom, rangeTo)
if err != nil {
return nil, err
}

chart, err := ctx.AnalyticChartService.GetTotalUserCountChat(
chart, err := ctx.AnalyticChartService.GetTotalUserCountChart(
appID,
*rangeFrom,
*rangeTo,
Expand All @@ -181,6 +193,12 @@ var query = graphql.NewObject(graphql.ObjectConfig{
return nil, err
}

// Access Control: collaborator.
_, err = ctx.AuthzService.CheckAccessOfViewer(appID)
if err != nil {
return nil, nil
}

err = checkChartDateRangeInput(rangeFrom, rangeTo)
if err != nil {
return nil, err
Expand Down Expand Up @@ -208,6 +226,12 @@ var query = graphql.NewObject(graphql.ObjectConfig{
return nil, err
}

// Access Control: collaborator.
_, err = ctx.AuthzService.CheckAccessOfViewer(appID)
if err != nil {
return nil, nil
}

err = checkChartDateRangeInput(rangeFrom, rangeTo)
if err != nil {
return nil, err
Expand Down
42 changes: 42 additions & 0 deletions portal/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions portal/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@
"@monaco-editor/react": "3.7.2",
"@oursky/react-messageformat": "2.0.2",
"base64-js": "1.5.1",
"chart.js": "3.7.0",
"chartjs-plugin-datalabels": "2.0.0",
"classnames": "2.3.1",
"deep-equal": "2.0.5",
"google-libphonenumber": "3.2.27",
Expand All @@ -63,6 +65,7 @@
"monaco-editor": "0.21.3",
"postcss": "8.4.6",
"react": "17.0.2",
"react-chartjs-2": "4.0.1",
"react-code-blocks": "0.0.9-0",
"react-dom": "17.0.2",
"react-helmet-async": "1.2.2",
Expand Down
5 changes: 4 additions & 1 deletion portal/src/AppRoot.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { useAppAndSecretConfigQuery } from "./graphql/portal/query/appAndSecretC
import ScreenLayout from "./ScreenLayout";
import ShowLoading from "./ShowLoading";

import ProjectRootScreen from "./graphql/portal/ProjectRootScreen";
import UsersScreen from "./graphql/adminapi/UsersScreen";
import AddUserScreen from "./graphql/adminapi/AddUserScreen";
import UserDetailsScreen from "./graphql/adminapi/UserDetailsScreen";
Expand Down Expand Up @@ -43,6 +44,7 @@ import CustomAttributesConfigurationScreen from "./graphql/portal/CustomAttribut
import EditCustomAttributeScreen from "./graphql/portal/EditCustomAttributeScreen";
import CreateCustomAttributeScreen from "./graphql/portal/CreateCustomAttributeScreen";
import AccountDeletionConfigurationScreen from "./graphql/portal/AccountDeletionConfigurationScreen";
import AnalyticsScreen from "./graphql/portal/AnalyticsScreen";

const AppRoot: React.FC = function AppRoot() {
const { appID } = useParams();
Expand Down Expand Up @@ -70,7 +72,8 @@ const AppRoot: React.FC = function AppRoot() {
<ApolloProvider client={client}>
<ScreenLayout>
<Routes>
<Route path="/" element={<Navigate to="users/" replace={true} />} />
<Route path="/" element={<ProjectRootScreen />} />
<Route path="/analytics" element={<AnalyticsScreen />} />
<Route path="/users/" element={<UsersScreen />} />
<Route path="/users/add-user/" element={<AddUserScreen />} />
<Route
Expand Down
7 changes: 5 additions & 2 deletions portal/src/ScreenNav.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,13 +48,16 @@ const ScreenNav: React.FC = function ScreenNav() {
const location = useLocation();
const path = getAppRouterPath(location);

const { auditLogEnabled } = useSystemConfig();
const { auditLogEnabled, analyticEnabled } = useSystemConfig();

const label = renderToString("ScreenNav.label");
const [expandState, setExpandState] = useState<Record<string, boolean>>({});

const links: NavLinkProps[] = useMemo(() => {
const links = [
...(analyticEnabled
? [{ textKey: "ScreenNav.analytics", url: "analytics" }]
: []),
{ textKey: "ScreenNav.users", url: "users" },
{
textKey: "ScreenNav.authentication",
Expand Down Expand Up @@ -160,7 +163,7 @@ const ScreenNav: React.FC = function ScreenNav() {
];

return links;
}, [auditLogEnabled]);
}, [analyticEnabled, auditLogEnabled]);

const [selectedKeys, selectedKey] = useMemo(() => {
const matchedKeys: string[] = [];
Expand Down
2 changes: 2 additions & 0 deletions portal/src/graphql.d.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
declare type GQL_AuditLogData = unknown;

declare type GQL_Date = string;

declare type GQL_DateTime = string;

declare interface GQL_IdentityClaims extends Record<string, unknown> {
Expand Down
63 changes: 17 additions & 46 deletions portal/src/graphql/adminapi/AuditLogScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,6 @@ import { useParams } from "react-router-dom";
import {
ICommandBarItemProps,
IDropdownOption,
Dialog,
DialogFooter,
PrimaryButton,
DefaultButton,
DatePicker,
TextField,
MessageBar,
addDays,
} from "@fluentui/react";
Expand All @@ -24,6 +18,7 @@ import CommandBarDropdown, {
import CommandBarContainer from "../../CommandBarContainer";
import ScreenContent from "../../ScreenContent";
import ShowError from "../../ShowError";
import DateRangeDialog from "../portal/DateRangeDialog";
import { encodeOffsetToCursor } from "../../util/pagination";
import useTransactionalState from "../../hook/useTransactionalState";
import {
Expand Down Expand Up @@ -316,13 +311,6 @@ const AuditLogScreen: React.FC = function AuditLogScreen() {
[commitRangeFrom, commitRangeTo]
);

const dateRangeDialogContentProps = useMemo(() => {
const title = renderToString("AuditLogScreen.date-range.custom");
return {
title,
};
}, [renderToString]);

const onSelectRangeFrom = useCallback(
(value: Date | null | undefined) => {
if (value == null) {
Expand Down Expand Up @@ -389,41 +377,24 @@ const AuditLogScreen: React.FC = function AuditLogScreen() {
/>
</ScreenContent>
</CommandBarContainer>
<Dialog
<DateRangeDialog
hidden={dateRangeDialogHidden}
title={renderToString("AuditLogScreen.date-range.custom")}
fromDatePickerLabel={renderToString(
"AuditLogScreen.date-range.start-date"
)}
toDatePickerLabel={renderToString("AuditLogScreen.date-range.end-date")}
rangeFrom={uncommittedRangeFrom ?? undefined}
rangeTo={uncommittedRangeTo ?? undefined}
fromDatePickerMinDate={datePickerMinDate}
fromDatePickerMaxDate={today}
toDatePickerMinDate={datePickerMinDate}
toDatePickerMaxDate={today}
onSelectRangeFrom={onSelectRangeFrom}
onSelectRangeTo={onSelectRangeTo}
onCommitDateRange={commitDateRange}
onDismiss={onDismissDateRangeDialog}
dialogContentProps={dateRangeDialogContentProps}
/* https://developer.microsoft.com/en-us/fluentui#/controls/web/dialog
* Best practice says the max width is 340 */
minWidth={340}
>
{/* Dialog is based on Modal, which will focus the first child on open. *
However, we do not want the date picker to be opened at the same time. *
So we make the first focusable element a hidden TextField */}
<TextField className={styles.hidden} />
<DatePicker
label={renderToString("AuditLogScreen.date-range.start-date")}
value={uncommittedRangeFrom ?? undefined}
minDate={datePickerMinDate}
maxDate={today}
onSelectDate={onSelectRangeFrom}
/>
<DatePicker
label={renderToString("AuditLogScreen.date-range.end-date")}
value={uncommittedRangeTo ?? undefined}
minDate={datePickerMinDate}
maxDate={today}
onSelectDate={onSelectRangeTo}
/>
<DialogFooter>
<PrimaryButton onClick={commitDateRange}>
<FormattedMessage id="done" />
</PrimaryButton>
<DefaultButton onClick={onDismissDateRangeDialog}>
<FormattedMessage id="cancel" />
</DefaultButton>
</DialogFooter>
</Dialog>
/>
</>
);
};
Expand Down
23 changes: 23 additions & 0 deletions portal/src/graphql/portal/AnalyticsActivityWidget.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
.pivot {
text-align: right;
}

.loadingWrapper {
min-height: 500px;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
}

.chartContainer {
height: 350px;
margin: 20px 0;
}

.totalUserLabel {
display: flex;
flex-direction: column;
row-gap: 4px;
text-align: left;
}
Loading

0 comments on commit 74795d9

Please sign in to comment.