Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[IOCOM-1338, IOCOM-1562, IOCOM-1563] FIMS history export feature #6091

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions locales/en/index.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3974,7 +3974,14 @@ permissionRequest:
FIMS:
history:
exportData:
alerts:
areYouSure: "Vuoi davvero esportare una copia di tutti gli accessi?"
alreadyExporting:
title: "Abbiamo già preso in carico una richiesta di esportazione."
body: "Al termine dell’elaborazione, riceverai una email con tutte le informazioni dello storico accessi."
CTA: "Richiedi una copia via email"
successToast: "Fatto! Controlla la tua casella di posta."
errorToast: "C’è stato un problema nell’invio della richiesta. Riprova"
profileCTA:
title: Accessi a servizi di terze parti
subTitle: Rivedi gli accessi effettuati a servizi di terze parti tramite IO
Expand Down
7 changes: 7 additions & 0 deletions locales/it/index.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3974,7 +3974,14 @@ permissionRequest:
FIMS:
history:
exportData:
alerts:
areYouSure: "Vuoi davvero esportare una copia di tutti gli accessi?"
alreadyExporting:
title: "Abbiamo già preso in carico una richiesta di esportazione."
body: "Al termine dell’elaborazione, riceverai una email con tutte le informazioni dello storico accessi."
CTA: "Richiedi una copia via email"
successToast: "Fatto! Controlla la tua casella di posta."
errorToast: "C’è stato un problema nell’invio della richiesta. Riprova"
profileCTA:
title: Accessi a servizi di terze parti
subTitle: Rivedi gli accessi effettuati a servizi di terze parti tramite IO
Expand Down
84 changes: 84 additions & 0 deletions ts/features/fims/history/hooks/useFimsHistoryResultToasts.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
/* eslint-disable functional/immutable-data */
import { IOToast } from "@pagopa/io-app-design-system";
import { constVoid } from "fp-ts/lib/function";
import * as React from "react";
import { Alert } from "react-native";
import * as RemoteValue from "../../../../common/model/RemoteValue";
import I18n from "../../../../i18n";
import { useIODispatch, useIOSelector } from "../../../../store/hooks";
import {
fimsHistoryExport,
resetFimsHistoryExportState
} from "../store/actions";
import { fimsHistoryExportStateSelector } from "../store/selectors";

const showFimsExportError = () =>
IOToast.error(I18n.t("FIMS.history.exportData.errorToast"));

const showFimsExportSuccess = () =>
IOToast.success(I18n.t("FIMS.history.exportData.successToast"));

const showFimsAlreadyExportingAlert = (onPress: () => void) =>
Alert.alert(
I18n.t("FIMS.history.exportData.alerts.alreadyExporting.title"),
I18n.t("FIMS.history.exportData.alerts.alreadyExporting.body"),
[{ text: I18n.t("global.buttons.ok"), onPress }]
);

export const useFimsHistoryExport = () => {
const historyExportState = useIOSelector(fimsHistoryExportStateSelector);
const dispatch = useIODispatch();
const isProcessing = React.useRef<boolean>(false);

React.useEffect(() => {
RemoteValue.fold(
historyExportState,
constVoid,
constVoid,
value => {
if (value === "SUCCESS") {
showFimsExportSuccess();
isProcessing.current = false;
} else {
showFimsAlreadyExportingAlert(() => (isProcessing.current = false));
}
},
() => {
showFimsExportError();
isProcessing.current = false;
}
);
}, [historyExportState, dispatch]);

// cleanup
React.useEffect(
() => () => {
dispatch(resetFimsHistoryExportState());
},
[dispatch]
);

const handleExportOnPress = () => {
if (!isProcessing.current) {
Alert.alert(
I18n.t("FIMS.history.exportData.alerts.areYouSure"),
undefined,
[
{ text: I18n.t("global.buttons.cancel"), style: "cancel" },
{
text: I18n.t("global.buttons.confirm"),
isPreferred: true,
onPress: () => {
isProcessing.current = true;
dispatch(fimsHistoryExport.request());
}
}
]
);
}
};

return {
handleExportOnPress
};
};
47 changes: 47 additions & 0 deletions ts/features/fims/history/saga/handleExportFimsHistorySaga.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import * as E from "fp-ts/lib/Either";
import { pipe } from "fp-ts/lib/function";
import { call, put } from "typed-redux-saga/macro";
import { ActionType } from "typesafe-actions";
import { SagaCallReturnType } from "../../../../types/utils";
import { withRefreshApiCall } from "../../../fastLogin/saga/utils";
import { FimsHistoryClient } from "../api/client";
import { fimsHistoryExport } from "../store/actions";

export function* handleExportFimsHistorySaga(
exportHistory: FimsHistoryClient["exports"],
bearerToken: string,
action: ActionType<typeof fimsHistoryExport.request>
) {
const exportHistoryRequest = exportHistory({
Bearer: bearerToken
});

try {
const exportHistoryResult = (yield* call(
withRefreshApiCall,
exportHistoryRequest,
action
)) as SagaCallReturnType<typeof exportHistory>;

const resultAction = pipe(
exportHistoryResult,
E.foldW(
_failure => fimsHistoryExport.failure(),
success => {
switch (success.status) {
case 202:
return fimsHistoryExport.success("SUCCESS");
case 409:
return fimsHistoryExport.success("ALREADY_EXPORTING");
default:
return fimsHistoryExport.failure();
}
}
)
);

yield* put(resultAction);
} catch (e: any) {
yield* put(fimsHistoryExport.failure());
}
}
9 changes: 8 additions & 1 deletion ts/features/fims/history/saga/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { takeLatest } from "typed-redux-saga/macro";
import { FimsHistoryClient } from "../api/client";
import { fimsHistoryGet } from "../store/actions";
import { fimsHistoryExport, fimsHistoryGet } from "../store/actions";
import { handleGetFimsHistorySaga } from "./handleGetFimsHistorySaga";
import { handleExportFimsHistorySaga } from "./handleExportFimsHistorySaga";

export function* watchFimsHistorySaga(
client: FimsHistoryClient,
Expand All @@ -13,4 +14,10 @@ export function* watchFimsHistorySaga(
client.getConsents,
bearerToken
);
yield* takeLatest(
fimsHistoryExport.request,
handleExportFimsHistorySaga,
client.exports,
bearerToken
);
}
50 changes: 33 additions & 17 deletions ts/features/fims/history/screens/HistoryScreen.tsx
Original file line number Diff line number Diff line change
@@ -1,34 +1,34 @@
import { Body, Divider, IOStyles, VSpacer } from "@pagopa/io-app-design-system";
import { constNull } from "fp-ts/lib/function";
import * as React from "react";
import { FlatList, SafeAreaView, View } from "react-native";
import { OperationResultScreenContent } from "../../../../components/screens/OperationResultScreenContent";
import { FooterActions } from "../../../../components/ui/FooterActions";
import { useHeaderSecondLevel } from "../../../../hooks/useHeaderSecondLevel";
import I18n from "../../../../i18n";
import { useIODispatch, useIOSelector } from "../../../../store/hooks";
import { fimsRequiresAppUpdateSelector } from "../../../../store/reducers/backendStatus";
import { openAppStoreUrl } from "../../../../utils/url";
import { FimsHistoryListItem } from "../components/FimsHistoryListItem";
import { LoadingFimsHistoryItemsFooter } from "../components/FimsHistoryLoaders";
import { fimsHistoryGet } from "../store/actions";
import {
fimsHistoryExportStateSelector,
fimsHistoryToUndefinedSelector,
isFimsHistoryLoadingSelector
} from "../store/selectors";
import { fimsRequiresAppUpdateSelector } from "../../../../store/reducers/backendStatus";
import { OperationResultScreenContent } from "../../../../components/screens/OperationResultScreenContent";
import { openAppStoreUrl } from "../../../../utils/url";
import { useFimsHistoryExport } from "../hooks/useFimsHistoryResultToasts";
import * as RemoteValue from "../../../../common/model/RemoteValue";

export const FimsHistoryScreen = () => {
const dispatch = useIODispatch();

const requiresAppUpdate = useIOSelector(fimsRequiresAppUpdateSelector);
const isLoading = useIOSelector(isFimsHistoryLoadingSelector);
const isHistoryLoading = useIOSelector(isFimsHistoryLoadingSelector);
const consents = useIOSelector(fimsHistoryToUndefinedSelector);
const historyExportState = useIOSelector(fimsHistoryExportStateSelector);
const isHistoryExporting = RemoteValue.isLoading(historyExportState);

React.useEffect(() => {
if (!requiresAppUpdate) {
dispatch(fimsHistoryGet.request({ shouldReloadFromScratch: true }));
}
}, [dispatch, requiresAppUpdate]);
// ---------- HOOKS

const fetchMore = React.useCallback(() => {
if (consents?.continuationToken) {
Expand All @@ -40,16 +40,21 @@ export const FimsHistoryScreen = () => {
}
}, [consents?.continuationToken, dispatch]);

const renderLoadingFooter = () =>
isLoading ? (
<LoadingFimsHistoryItemsFooter
showFirstDivider={(consents?.items.length ?? 0) > 0}
/>
) : null;
useHeaderSecondLevel({
title: I18n.t("FIMS.history.historyScreen.header"),
supportRequest: true
});

React.useEffect(() => {
if (!requiresAppUpdate) {
dispatch(fimsHistoryGet.request({ shouldReloadFromScratch: true }));
}
}, [dispatch, requiresAppUpdate]);

const { handleExportOnPress } = useFimsHistoryExport();

// ---------- APP UPDATE

if (requiresAppUpdate) {
return (
<OperationResultScreenContent
Expand All @@ -63,6 +68,16 @@ export const FimsHistoryScreen = () => {
/>
);
}

// ---------- RENDER

const renderLoadingFooter = () =>
isHistoryLoading ? (
<LoadingFimsHistoryItemsFooter
showFirstDivider={(consents?.items.length ?? 0) > 0}
/>
) : null;

return (
<>
<SafeAreaView>
Expand All @@ -86,8 +101,9 @@ export const FimsHistoryScreen = () => {
actions={{
type: "SingleButton",
primary: {
loading: isHistoryExporting,
label: I18n.t("FIMS.history.exportData.CTA"),
onPress: constNull // full export functionality coming soon
onPress: handleExportOnPress
}
}}
/>
Expand Down
21 changes: 19 additions & 2 deletions ts/features/fims/history/store/actions/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
import { ActionType, createAsyncAction } from "typesafe-actions";
import {
ActionType,
createAsyncAction,
createStandardAction
} from "typesafe-actions";
import { ConsentsResponseDTO } from "../../../../../../definitions/fims/ConsentsResponseDTO";
import { FimsExportSuccessStates } from "../reducer";

export type FimsHistoryGetPayloadType = {
shouldReloadFromScratch?: boolean;
Expand All @@ -12,4 +17,16 @@ export const fimsHistoryGet = createAsyncAction(
"FIMS_GET_HISTORY_FAILURE"
)<FimsHistoryGetPayloadType, ConsentsResponseDTO, string>();

export type FimsHistoryActions = ActionType<typeof fimsHistoryGet>;
export const fimsHistoryExport = createAsyncAction(
"FIMS_HISTORY_EXPORT_REQUEST",
"FIMS_HISTORY_EXPORT_SUCCESS",
"FIMS_HISTORY_EXPORT_FAILURE"
)<void, FimsExportSuccessStates, void>();

export const resetFimsHistoryExportState =
createStandardAction("RESET_FIMS_HISTORY")<void>();

export type FimsHistoryActions =
| ActionType<typeof fimsHistoryGet>
| ActionType<typeof fimsHistoryExport>
| ActionType<typeof resetFimsHistoryExportState>;
41 changes: 40 additions & 1 deletion ts/features/fims/history/store/reducer/index.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,29 @@
import * as pot from "@pagopa/ts-commons/lib/pot";
import { getType } from "typesafe-actions";
import { ConsentsResponseDTO } from "../../../../../../definitions/fims/ConsentsResponseDTO";
import {
remoteError,
remoteLoading,
remoteReady,
remoteUndefined,
RemoteValue
} from "../../../../../common/model/RemoteValue";
import { Action } from "../../../../../store/actions/types";
import { fimsHistoryGet } from "../actions";
import {
fimsHistoryExport,
fimsHistoryGet,
resetFimsHistoryExportState
} from "../actions";

export type FimsExportSuccessStates = "SUCCESS" | "ALREADY_EXPORTING";

export type FimsHistoryState = {
historyExportState: RemoteValue<FimsExportSuccessStates, null>;
consentsList: pot.Pot<ConsentsResponseDTO, string>;
};

const INITIAL_STATE: FimsHistoryState = {
historyExportState: remoteUndefined,
consentsList: pot.none
};

Expand All @@ -20,24 +35,48 @@ const reducer = (
case getType(fimsHistoryGet.request):
return action.payload.shouldReloadFromScratch
? {
...state,
consentsList: pot.noneLoading
}
: {
...state,
consentsList: pot.toLoading(state.consentsList)
};
case getType(fimsHistoryGet.success):
const currentHistoryItems =
pot.toUndefined(state.consentsList)?.items ?? [];
return {
...state,
consentsList: pot.some({
...action.payload,
items: [...currentHistoryItems, ...action.payload.items]
})
};
case getType(fimsHistoryGet.failure):
return {
...state,
consentsList: pot.toError(state.consentsList, action.payload)
};
case getType(fimsHistoryExport.request):
return {
...state,
historyExportState: remoteLoading
};
case getType(fimsHistoryExport.success):
return {
...state,
historyExportState: remoteReady(action.payload)
};
case getType(fimsHistoryExport.failure):
return {
...state,
historyExportState: remoteError(null)
};
case getType(resetFimsHistoryExportState):
return {
...state,
historyExportState: remoteUndefined
};
}
return state;
};
Expand Down
Loading
Loading