Skip to content

Commit

Permalink
Enhanced error handling for contacts import
Browse files Browse the repository at this point in the history
  • Loading branch information
mkurczewski committed Jul 19, 2024
1 parent 5534483 commit 827422c
Show file tree
Hide file tree
Showing 9 changed files with 144 additions and 55 deletions.
7 changes: 7 additions & 0 deletions libs/core/__deprecated__/renderer/locales/default/en-US.json
Original file line number Diff line number Diff line change
Expand Up @@ -907,13 +907,20 @@
"module.genericViews.importContacts.progress.title": "Importing",
"module.genericViews.importContacts.progress.description": "Please wait, this might take a few minutes.",
"module.genericViews.importContacts.progress.progressDetails": "Preparing contacts",
"module.genericViews.importContacts.progress.progressDetailsForFeature": "Importing {featureLabel}",
"module.genericViews.importContacts.progress.cancelButtonLabel": "Cancel",
"module.genericViews.importContacts.cancelConfirm.title": "Cancel import?",
"module.genericViews.importContacts.cancelConfirm.description": "We'll stop the import process and no changes will be made to your phone.",
"module.genericViews.importContacts.cancelConfirm.backButtonLabel": "Back",
"module.genericViews.importContacts.cancelConfirm.cancelButtonLabel": "Cancel import",
"module.genericViews.importContacts.success.title": "Import complete",
"module.genericViews.importContacts.success.description": "{count, plural, =-1 {All contacts} one {# contact} other {# contacts}} imported successfully.",
"module.genericViews.importContacts.success.closeButtonLabel": "Close",
"module.genericViews.importContacts.failure.title": "Import failed",
"module.genericViews.importContacts.failure.defaultErrorMessage": "The process was interrupted.",
"module.genericViews.importContacts.failure.fileErrorMessage": "We didn’t recognise some of the data in the file. Check that it only contains contact data and try again.",
"module.genericViews.importContacts.failure.closeButtonLabel": "Close",
"module.genericViews.importContacts.failure.notEnoughSpace": "Your phone’s storage is full.",
"module.genericViews.importContacts.cancellation.title": "Import cancelled",
"module.genericViews.importContacts.cancellation.message": "No changes were made to your phone’s contacts.",
"module.genericViews.importContacts.fileUploadDialog.title": "Import contacts",
Expand Down
4 changes: 2 additions & 2 deletions libs/generic-view/store/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export * from "./lib/imports/get-google-contacts.action"
export * from "./lib/imports/reducer"
export * from "./lib/imports/actions"
export * from "./lib/imports/import-contacts-from-external-source.action"
export * from "./lib/imports/start-data-transfer-to-device.action"
export * from "./lib/imports/start-import-to-device.action"
export * from "./lib/imports/import-contacts-from.file"
export { getDisplayName } from "./lib/imports/contacts-mappers/helpers"
export * from "./lib/external-providers/reducer"
Expand All @@ -40,7 +40,7 @@ export * from "./lib/external-providers/outlook/outlook.interface"
export * from "./lib/external-providers/outlook/outlook.constants"
export * from "./lib/external-providers/outlook/token-requester"
export * from "./lib/external-providers/external-providers.interface"
export * from "./lib/imports/start-data-transfer-to-device.action"
export * from "./lib/imports/start-import-to-device.action"
export * from "./lib/data-migration/reducer"
export * from "./lib/data-migration/actions"
export * from "./lib/data-migration/start-data.migration"
Expand Down
18 changes: 12 additions & 6 deletions libs/generic-view/store/src/lib/action-names.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,22 @@ export enum ActionName {
GetAny = "api-actions/get-any",
GetMenuConfig = "api-actions/get-menu-config",
GetOutboxData = "api-actions/get-outbox-data",

SetMenu = "generic-views/set-menu",
SetViewLayout = "generic-views/set-view-layout",
SetViewData = "generic-views/set-view-data",
SetLastRefresh = "generic-views/set-last-refresh",
AddDevice = "generic-views/add-device",
RemoveDevice = "generic-views/remove-device",
SetDeviceState = "generic-views/set-device-state",

OpenModal = "generic-modals/open-modal",
CloseModal = "generic-modals/close-modal",
CloseAllModals = "generic-modals/close-all-modals",
ReplaceModal = "generic-modals/replace-modal",
CloseDomainModals = "generic-modals/close-domain-modals",
SetDeviceErrorModalOpened = "generic-modals/set-device-error-modal-opened",

SetBackupProcess = "generic-backups/set-backup-process",
CleanBackupProcess = "generic-backups/clean-backup-process",
BackupProcessStatus = "generic-backups/backup-process-status",
Expand All @@ -33,6 +36,7 @@ export enum ActionName {
ChooseRestoreFile = "generic-backups/choose-restore-file",
RefreshBackupList = "generic-backups/refresh-backup-list",
LoadBackupMetadata = "generic-backups/load-backup-metadata",

FileTransferSend = "generic-file-transfer/send",
PreFileTransferSend = "generic-file-transfer/pre-send",
ChunkFileTransferSend = "generic-file-transfer/chunk-sent",
Expand All @@ -42,25 +46,27 @@ export enum ActionName {
ChunkFileTransferGet = "generic-file-transfer/chunk-get",
ClearFileTransferGetError = "generic-file-transfer/clear-get-errors",
TransferDataToDevice = "generic-file-transfer/transfer-data-to-device",

SetDataTransfer = "generic-data-transfer/set-data-transfer",
SetDataTransferStatus = "generic-data-transfer/set-data-transfer-status",
ClearDataTransfer = "generic-data-transfer/clear-data-transfer",
SetDataTransferAbort = "generic-data-transfer/set-data-transfer-abort",
AbortDataTransfer = "generic-data-transfer/abort-data-transfer",

StartGoogleAuthorization = "generic-imports/start-google-authorization",
ImportContactsFromExternalSource = "generic-imports/import-contacts-from-external-source",
StartDataTransferToDevice = "generic-imports/start-data-transfer-to-device",
SetDataTransferProcessStatus = "generic-imports/set-data-transfer-process-status",
SetDataTransferProcessFileStatus = "generic-imports/set-data-transfer-process-file-status",
SetDataTransferAbort = "generic-imports/set-data-transfer-abort",
AbortDataTransfer = "generic-imports/abort-data-transfer",
StartImportToDevice = "generic-imports/start-data-transfer-to-device",
SetImportProcessStatus = "generic-imports/set-data-transfer-process-status",
SetImportProcessFileStatus = "generic-imports/set-data-transfer-process-file-status",
CleanImportProcess = "generic-imports/clean-import-process",
StartContactsFileImport = "generic-imports/start-contacts-file-import",
GetContactsFromCSV = "generic-imports/get-contacts-from-csv",
GoogleAuthorizeProcess = "generic-imports/google-authorization-process",
GoogleGetContactsProcess = "generic-imports/google-get-contacts-process",
SetGoogleAuthDataProcess = "generic-imports/set-google-auth-data-process",
OutlookAuthorizeProcess = "generic-imports/outlook-authorization-process",
OutlookGetContactsProcess = "generic-imports/outlook-get-contacts-process",
SetOutlookAuthDataProcess = "generic-imports/set-outlook-auth-data-process",

SetDataMigrationSourceDevice = "data-migration/set-source-device",
SetDataMigrationFeatures = "data-migration/set-features",
setDataMigrationPureDbIndexing = "data-migration/set-pure-db-indexing",
Expand Down
8 changes: 4 additions & 4 deletions libs/generic-view/store/src/lib/imports/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@ import { ImportStatus, ProcessFileStatus } from "./reducer"

export const cleanImportProcess = createAction(ActionName.CleanImportProcess)

export const setDataTransferProcessStatus = createAction<{
export const setImportProcessStatus = createAction<{
status: ImportStatus
}>(ActionName.SetDataTransferProcessStatus)
}>(ActionName.SetImportProcessStatus)

export const setDataTransferProcessFileStatus = createAction<{
export const setImportProcessFileStatus = createAction<{
domain: string
status: ProcessFileStatus
}>(ActionName.SetDataTransferProcessFileStatus)
}>(ActionName.SetImportProcessFileStatus)
25 changes: 15 additions & 10 deletions libs/generic-view/store/src/lib/imports/reducer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@ import { createReducer } from "@reduxjs/toolkit"
import { startGoogleAuthorization } from "./get-google-contacts.action"
import {
cleanImportProcess,
setDataTransferProcessFileStatus,
setDataTransferProcessStatus,
setImportProcessFileStatus,
setImportProcessStatus,
} from "./actions"
import { importContactsFromExternalSource } from "./import-contacts-from-external-source.action"
import { UnifiedContact } from "device/models"
import { startDataTransferToDevice } from "./start-data-transfer-to-device.action"
import { ApiFileTransferError, UnifiedContact } from "device/models"
import { startImportToDevice } from "./start-import-to-device.action"
import { importContactsFromFile } from "./import-contacts-from.file"

interface ImportsState {
Expand Down Expand Up @@ -44,7 +44,7 @@ export interface ImportProviderState {
string,
{ transferId?: number; status: ProcessFileStatus }
>
error?: string
error?: string | ApiFileTransferError
}

const initialState: ImportsState = {
Expand Down Expand Up @@ -138,7 +138,7 @@ export const importsReducer = createReducer(initialState, (builder) => {
contacts: action.payload,
}
})
builder.addCase(setDataTransferProcessStatus, (state, action) => {
builder.addCase(setImportProcessStatus, (state, action) => {
const provider = state.currentImportProvider as ImportProvider
if (state.providers[provider]) {
state.providers[provider] = {
Expand All @@ -149,7 +149,7 @@ export const importsReducer = createReducer(initialState, (builder) => {
}
}
})
builder.addCase(startDataTransferToDevice.pending, (state, action) => {
builder.addCase(startImportToDevice.pending, (state, action) => {
const provider = state.currentImportProvider as ImportProvider
if (state.providers[provider]) {
state.providers[provider] = {
Expand All @@ -160,17 +160,22 @@ export const importsReducer = createReducer(initialState, (builder) => {
}
}
})
builder.addCase(startDataTransferToDevice.rejected, (state, action) => {
builder.addCase(startImportToDevice.rejected, (state, action) => {
const provider = state.currentImportProvider as ImportProvider
if (state.providers[provider]) {
state.providers[provider] = {
...state.providers[provider],
domainFilesTransfer: {},
status: "FAILED",
...(action.payload
? {
error: action.payload as ImportProviderState["error"],
}
: {}),
}
}
})
builder.addCase(startDataTransferToDevice.fulfilled, (state, action) => {
builder.addCase(startImportToDevice.fulfilled, (state, action) => {
const provider = state.currentImportProvider as ImportProvider
if (state.providers[provider]) {
state.providers[provider] = {
Expand All @@ -180,7 +185,7 @@ export const importsReducer = createReducer(initialState, (builder) => {
}
}
})
builder.addCase(setDataTransferProcessFileStatus, (state, action) => {
builder.addCase(setImportProcessFileStatus, (state, action) => {
const provider = state.currentImportProvider as ImportProvider
if (state.providers[provider]) {
state.providers[provider]!.domainFilesTransfer = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,11 @@

import { createAsyncThunk } from "@reduxjs/toolkit"
import { ReduxRootState } from "Core/__deprecated__/renderer/store"
import { DataTransferDomain, UnifiedContact } from "device/models"
import {
ApiFileTransferError,
DataTransferDomain,
UnifiedContact,
} from "device/models"
import {
cancelDataTransferRequest,
checkDataTransferRequest,
Expand All @@ -17,17 +21,14 @@ import {
import { ActionName } from "../action-names"
import { sendFile } from "../file-transfer/send-file.action"
import { selectActiveApiDeviceId } from "../selectors"
import {
setDataTransferProcessFileStatus,
setDataTransferProcessStatus,
} from "./actions"
import { setImportProcessFileStatus, setImportProcessStatus } from "./actions"

export const startDataTransferToDevice = createAsyncThunk<
export const startImportToDevice = createAsyncThunk<
undefined,
{ domains: DataTransferDomain[]; contactsIds: string[] },
{ state: ReduxRootState }
>(
ActionName.StartDataTransferToDevice,
ActionName.StartImportToDevice,
async (
{ domains, contactsIds },
{ getState, dispatch, rejectWithValue, signal }
Expand Down Expand Up @@ -131,22 +132,23 @@ export const startDataTransferToDevice = createAsyncThunk<
)

if (!preSendResponse.ok) {
console.log("cannot start pre send")
clearTransfers()
return rejectWithValue(undefined)
return rejectWithValue(
preSendResponse.error?.type as ApiFileTransferError
)
}

domainsPaths[i].transfer = preSendResponse.data
dispatch(
setDataTransferProcessFileStatus({
setImportProcessFileStatus({
domain: domain.domainKey,
status: "PENDING",
})
)
}

dispatch(
setDataTransferProcessStatus({
setImportProcessStatus({
status: "IMPORT-INTO-DEVICE-FILES-TRANSFER",
})
)
Expand All @@ -157,7 +159,7 @@ export const startDataTransferToDevice = createAsyncThunk<
}
const domain = domainsPaths[i]
dispatch(
setDataTransferProcessFileStatus({
setImportProcessFileStatus({
domain: domain.domainKey,
status: "IN_PROGRESS",
})
Expand All @@ -179,7 +181,7 @@ export const startDataTransferToDevice = createAsyncThunk<
}
if (!aborted) {
dispatch(
setDataTransferProcessFileStatus({
setImportProcessFileStatus({
domain: domain.domainKey,
status: "DONE",
})
Expand All @@ -189,9 +191,7 @@ export const startDataTransferToDevice = createAsyncThunk<

clearTransfers()

dispatch(
setDataTransferProcessStatus({ status: "IMPORT-DEVICE-DATA-TRANSFER" })
)
dispatch(setImportProcessStatus({ status: "IMPORT-DEVICE-DATA-TRANSFER" }))

if (aborted) {
return rejectWithValue(undefined)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,18 @@
* For licensing, see https://github.com/mudita/mudita-center/blob/master/LICENSE.md
*/

import React from "react"
import React, { useState } from "react"
import { FunctionComponent } from "Core/core/types/function-component.interface"
import styled from "styled-components"
import { IconType } from "generic-view/utils"
import { ButtonAction, IconType } from "generic-view/utils"
import { ProgressBar } from "../../interactive/progress-bar/progress-bar"
import { Modal } from "../../interactive/modal"
import { defineMessages } from "react-intl"
import { intl } from "Core/__deprecated__/renderer/utils/intl"
import { useSelector } from "react-redux"
import { importContactsProgress } from "generic-view/store"
import { ButtonSecondary } from "../../buttons/button-secondary"
import { ButtonPrimary } from "../../buttons/button-primary"

const messages = defineMessages({
title: {
Expand All @@ -23,12 +26,65 @@ const messages = defineMessages({
progressDetails: {
id: "module.genericViews.importContacts.progress.progressDetails",
},
cancelButtonLabel: {
id: "module.genericViews.importContacts.progress.cancelButtonLabel",
},
cancelTitle: {
id: "module.genericViews.importContacts.cancelConfirm.title",
},
cancelDescription: {
id: "module.genericViews.importContacts.cancelConfirm.description",
},
cancelBackButtonLabel: {
id: "module.genericViews.importContacts.cancelConfirm.backButtonLabel",
},
cancelAbortButtonLabel: {
id: "module.genericViews.importContacts.cancelConfirm.cancelButtonLabel",
},
})

export const ImportContactsProgress = () => {
interface Props {
cancelAction: ButtonAction
}

export const ImportContactsProgress: FunctionComponent<Props> = ({
cancelAction,
}) => {
const [cancelRequested, setCancelRequested] = useState(false)
const { progress } = useSelector(importContactsProgress)
const detailMessage = intl.formatMessage(messages.progressDetails)

const requestCancel = () => {
setCancelRequested(true)
}

if (cancelRequested) {
return (
<>
<Modal.TitleIcon config={{ type: IconType.Exclamation }} />
<Modal.Title>{intl.formatMessage(messages.title)}</Modal.Title>
<p>{intl.formatMessage(messages.cancelDescription)}</p>
<Modal.Buttons>
<ButtonSecondary
config={{
action: {
type: "custom",
callback: () => setCancelRequested(false),
},
text: intl.formatMessage(messages.cancelBackButtonLabel),
}}
/>
<ButtonPrimary
config={{
action: cancelAction,
text: intl.formatMessage(messages.cancelAbortButtonLabel),
}}
/>
</Modal.Buttons>
</>
)
}

return (
<>
<Modal.TitleIcon config={{ type: IconType.Import }} />
Expand All @@ -43,6 +99,17 @@ export const ImportContactsProgress = () => {
message: detailMessage,
}}
/>
<Modal.Buttons config={{ vertical: true }}>
<ButtonSecondary
config={{
action: {
type: "custom",
callback: requestCancel,
},
text: intl.formatMessage(messages.cancelButtonLabel),
}}
/>
</Modal.Buttons>
</>
)
}
Expand Down
Loading

0 comments on commit 827422c

Please sign in to comment.