Release v2.3.0 - to stage (#1840)

This commit is contained in:
Daniel Karski
2024-05-22 11:39:41 +02:00
committed by GitHub
parent 3fb95c0eb0
commit 8d08787941
77 changed files with 230 additions and 914 deletions

View File

@@ -17,7 +17,7 @@ jobs:
node-version: 18.16.1
- name: Setup environment variables
env:
CI: "true"
E2ECI: "true"
TEST_GITHUB_TOKEN: ${{ secrets.MC_GITHUB_ACCESS_TOKEN }}
TEST_BINARY_PATH: "../mudita-center/release/linux-unpacked/Mudita Center"
PHRASE_API_KEY: ${{ secrets.PHRASE_API_KEY }}

View File

@@ -18,7 +18,7 @@ jobs:
node-version: 18.16.1
- name: Setup environment variables
env:
CI: "true"
E2ECI: "true"
TEST_GITHUB_TOKEN: ${{ secrets.MC_GITHUB_ACCESS_TOKEN }}
TEST_BINARY_PATH: "../mudita-center/release/linux-unpacked/Mudita Center"
PHRASE_API_KEY: ${{ secrets.PHRASE_API_KEY }}

View File

@@ -19,7 +19,7 @@ jobs:
node-version: 18.16.1
- name: Setup environment variables
env:
CI: "true"
E2ECI: "true"
TEST_GITHUB_TOKEN: ${{ secrets.MC_GITHUB_ACCESS_TOKEN }}
TEST_BINARY_PATH: "../mudita-center/release/linux-unpacked/Mudita Center"
PHRASE_API_KEY: ${{ secrets.PHRASE_API_KEY }}

View File

@@ -19,7 +19,7 @@ jobs:
node-version: 18.16.1
- name: Setup environment variables
env:
CI: "true"
E2ECI: "true"
TEST_GITHUB_TOKEN: ${{ secrets.MC_GITHUB_ACCESS_TOKEN }}
TEST_BINARY_PATH: "../mudita-center/release/linux-unpacked/Mudita Center"
PHRASE_API_KEY: ${{ secrets.PHRASE_API_KEY }}

View File

@@ -21,6 +21,7 @@ jobs:
- name: Setup Env for Windows
if: matrix.os == 'Windows'
env:
E2ECI: "false"
PHRASE_API_KEY: ${{ secrets.PHRASE_API_KEY }}
PHRASE_API_URL: ${{ secrets.PHRASE_API_URL }}
PHRASE_API_KEY_DEV: ${{ secrets.PHRASE_API_KEY_DEV }}

View File

@@ -21,6 +21,7 @@ jobs:
- name: Setup Env for Windows
if: matrix.os == 'Windows'
env:
E2ECI: "false"
PHRASE_API_KEY: ${{ secrets.PHRASE_API_KEY }}
PHRASE_API_URL: ${{ secrets.PHRASE_API_URL }}
PHRASE_API_KEY_DEV: ${{ secrets.PHRASE_API_KEY_DEV }}

View File

@@ -23,6 +23,7 @@ jobs:
- name: Setup Env for Windows
if: matrix.os == 'Windows'
env:
E2ECI: "false"
PHRASE_API_KEY: ${{ secrets.PHRASE_API_KEY }}
PHRASE_API_URL: ${{ secrets.PHRASE_API_URL }}
PHRASE_API_KEY_DEV: ${{ secrets.PHRASE_API_KEY_DEV }}
@@ -142,13 +143,11 @@ jobs:
run: |
$env:NODE_OPTIONS="--max-old-space-size=4096"
$env:LOCALAPPDATA=""
cd apps/mudita-center/
npm run app:dist
- name: Build App for Linux/Mac
if: matrix.os != 'Windows'
run: |
export NODE_OPTIONS="--max-old-space-size=4096"
cd apps/mudita-center/
npm run app:dist
- name: Verify apple sign
if: matrix.os == 'macOS'

View File

@@ -24,6 +24,7 @@ jobs:
- name: Setup Env for Windows
if: matrix.os == 'Windows'
env:
E2ECI: "false"
PHRASE_API_KEY: ${{ secrets.PHRASE_API_KEY }}
PHRASE_API_URL: ${{ secrets.PHRASE_API_URL }}
PHRASE_API_KEY_DEV: ${{ secrets.PHRASE_API_KEY_DEV }}
@@ -165,13 +166,11 @@ jobs:
run: |
$env:NODE_OPTIONS="--max-old-space-size=4096"
$env:LOCALAPPDATA=""
cd apps/mudita-center/
npm run app:dist
- name: Build App for Linux/Mac
if: matrix.os != 'Windows'
run: |
export NODE_OPTIONS="--max-old-space-size=4096"
cd apps/mudita-center/
npm run app:dist
- name: Verify apple sign
if: matrix.os == 'macOS'

View File

@@ -23,6 +23,7 @@ jobs:
- name: Setup Env for Windows
if: matrix.os == 'Windows'
env:
E2ECI: "false"
PHRASE_API_KEY: ${{ secrets.PHRASE_API_KEY }}
PHRASE_API_URL: ${{ secrets.PHRASE_API_URL }}
PHRASE_API_KEY_DEV: ${{ secrets.PHRASE_API_KEY_DEV }}
@@ -90,13 +91,11 @@ jobs:
run: |
$env:NODE_OPTIONS="--max-old-space-size=4096"
$env:LOCALAPPDATA=""
cd apps/mudita-center/
npm run app:dist
- name: Build App for Linux/Mac
if: matrix.os != 'Windows'
run: |
export NODE_OPTIONS="--max-old-space-size=4096"
cd apps/mudita-center/
npm run app:dist
- name: Verify apple sign
if: matrix.os == 'macOS'

View File

@@ -23,6 +23,7 @@ jobs:
- name: Setup Env for Windows
if: matrix.os == 'Windows'
env:
E2ECI: "false"
PHRASE_API_KEY: ${{ secrets.PHRASE_API_KEY }}
PHRASE_API_URL: ${{ secrets.PHRASE_API_URL }}
PHRASE_API_KEY_DEV: ${{ secrets.PHRASE_API_KEY_DEV }}
@@ -142,13 +143,11 @@ jobs:
run: |
$env:NODE_OPTIONS="--max-old-space-size=4096"
$env:LOCALAPPDATA=""
cd apps/mudita-center/
npm run app:dist
- name: Build App for Linux/Mac
if: matrix.os != 'Windows'
run: |
export NODE_OPTIONS="--max-old-space-size=4096"
cd apps/mudita-center/
npm run app:dist
- name: Verify apple sign
if: matrix.os == 'macOS'

View File

@@ -23,6 +23,7 @@ jobs:
- name: Setup Env for Windows
if: matrix.os == 'Windows'
env:
E2ECI: "false"
PHRASE_API_KEY: ${{ secrets.PHRASE_API_KEY }}
PHRASE_API_URL: ${{ secrets.PHRASE_API_URL }}
PHRASE_API_KEY_DEV: ${{ secrets.PHRASE_API_KEY_DEV }}
@@ -90,13 +91,11 @@ jobs:
run: |
$env:NODE_OPTIONS="--max-old-space-size=4096"
$env:LOCALAPPDATA=""
cd apps/mudita-center/
npm run app:dist
- name: Build App for Linux/Mac
if: matrix.os != 'Windows'
run: |
export NODE_OPTIONS="--max-old-space-size=4096"
cd apps/mudita-center/
npm run app:dist
- name: Verify apple sign
if: matrix.os == 'macOS'

View File

@@ -1,7 +1,7 @@
# Software License Agreement
Mudita Center https://github.com/mudita/mudita-center
Copyright (c) 2017-2021, Mudita Sp. z o.o. All rights reserved.
Copyright (c) 2017-2024, Mudita Sp. z o.o. All rights reserved.
## Sources of Intellectual Property Included in Mudita Center

View File

@@ -1,12 +1,12 @@
{
"name": "@mudita/mudita-center-app",
"version": "2.2.8",
"version": "2.3.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@mudita/mudita-center-app",
"version": "2.2.8",
"version": "2.3.0",
"license": "GPL-3.0",
"dependencies": {
"serialport": "10.1.0"

View File

@@ -1,6 +1,6 @@
{
"name": "mudita-center",
"version": "2.2.8",
"version": "2.3.0",
"description": "Mudita Center",
"main": "./dist/main.js",
"productName": "Mudita Center",
@@ -39,7 +39,7 @@
"build": {
"productName": "Mudita Center",
"appId": "com.mudita.center",
"copyright": "Copyright (c) 2017-2023, Mudita sp. z o.o. All rights reserved.",
"copyright": "Copyright (c) 2017-2024, Mudita sp. z o.o. All rights reserved.",
"mac": {
"category": "public.app-category.utilities",
"icon": "./icons/mac/icon.icns",

View File

@@ -436,7 +436,7 @@ const InputSearchComponent: FunctionComponent<InputSearchProps> = ({
useEffect(() => {
if (listRef.current) {
listRef.current.children[activeItemIndex]?.scrollIntoView({
behavior: "smooth",
behavior: "auto",
block: "nearest",
})
}

View File

@@ -107,8 +107,6 @@ export const ModalSubTitle = styled(Text)`
`
export const Close = styled(Button)`
margin-top: -0.6rem;
margin-right: -0.8rem;
grid-area: Close;
justify-self: end;
width: 2.8rem;

View File

@@ -300,33 +300,6 @@ exports[`Device: Mudita harmony matches snapshot 1`] = `
/>
</span>
</div>
<div
class="c4"
>
<a
class="c5"
displaystyle="7"
href="#/onboarding"
size="1"
>
<span
class="c6 c7"
data-testid="icon-Send"
height="3.2"
width="3.2"
>
<test-file-stub
image="test-file-stub"
/>
</span>
<p
class="c8 c9"
color="primary"
>
[value] module.onboarding
</p>
</a>
</div>
<div
class="c4"
>
@@ -724,33 +697,6 @@ exports[`Device: Mudita pure matches snapshot 1`] = `
/>
</span>
</div>
<div
class="c4"
>
<a
class="c5"
displaystyle="7"
href="#/onboarding"
size="1"
>
<span
class="c6 c7"
data-testid="icon-Send"
height="3.2"
width="3.2"
>
<test-file-stub
image="test-file-stub"
/>
</span>
<p
class="c8 c9"
color="primary"
>
[value] module.onboarding
</p>
</a>
</div>
<div
class="c4"
>

View File

@@ -93,16 +93,6 @@ export interface MenuElement {
}
export const baseMenuElements: MenuElement[] = [
{
items: [
{
button: views[View.Onboarding],
icon: IconType.Send,
visibleOn: [DeviceType.MuditaPure, DeviceType.MuditaHarmony],
},
],
viewKey: View.Onboarding,
},
{
items: [
{

View File

@@ -257,11 +257,6 @@
"module.connecting.criticalBatteryLevelModalDescription": "Charge your Pure, then disconnect and reconnect your phone to use Mudita Center.",
"module.connecting.criticalBatteryLevelModalHeaderTitle": "MuditaOS",
"module.connecting.criticalBatteryLevelModalTitle": "The battery in your Pure is flat.",
"module.connecting.errorConnectingDescription": "If you still have problems connecting to your device, have a look at our <link>help pages.</link>",
"module.connecting.errorConnectingModalHeaderSubTitle": "We couldn't connect to your device",
"module.connecting.errorConnectingModalHeaderTitle": "Error",
"module.connecting.errorConnectingModalSecondaryButton": "Cancel",
"module.connecting.errorConnectingModalTitle": "Make sure your device is plugged in using a USB C cable and restart Mudita Center or your computer to try again",
"module.connecting.errorSyncModalButton": "Try again",
"module.connecting.errorSyncModalDescription": "Please try to reconnect your device",
"module.connecting.errorSyncModalHeaderTitle": "Error",

View File

@@ -14,14 +14,9 @@ import { Settings } from "Core/settings/dto"
export const fakeAppSettings: Settings = {
applicationId: "app-Nr8uiSV7KmWxX3WOFqZPF7uB",
autostart: false,
tethering: false,
tray: true,
osBackupLocation: `fake/path/pure/phone/backups/`,
osDownloadLocation: `fake/path/pure/os/downloads/`,
language: "en-US",
neverConnected: true,
collectingData: undefined,
privacyPolicyAccepted: false,
diagnosticSentTimestamp: 0,
ignoredCrashDumps: [],

View File

@@ -61,7 +61,7 @@ const AppUpdateStepModal: FunctionComponent<Props> = ({
})
return () => unregister()
})
}, [appCurrentVersion, appLatestVersion])
useEffect(() => {
const unregister = registerErrorAppUpdateListener(() => {
@@ -70,10 +70,15 @@ const AppUpdateStepModal: FunctionComponent<Props> = ({
toCenterVersion: appLatestVersion,
state: TrackCenterUpdateState.Fail,
})
setAppUpdateStep(AppUpdateStep.Error)
setAppUpdateStep((prevAppUpdateStep) => {
// allow user to try updating before throw error to handle no network connection
return prevAppUpdateStep === AppUpdateStep.Updating
? AppUpdateStep.Error
: prevAppUpdateStep
})
})
return () => unregister()
})
}, [appCurrentVersion, appLatestVersion])
const handleProcessDownload = () => {
void trackCenterUpdate({

View File

@@ -91,7 +91,9 @@ export class AnalyticDataTrackerService implements AnalyticDataTrackerClass {
this.visitorMetadata = visitorMetadata
}
private trackRequest(event: TrackEvent): Promise<AxiosResponse | undefined> {
private async trackRequest(
event: TrackEvent
): Promise<AxiosResponse | undefined> {
const params: AxiosRequestConfig["params"] = {
rec: 1,
apiv: 1,
@@ -101,10 +103,12 @@ export class AnalyticDataTrackerService implements AnalyticDataTrackerClass {
...event,
}
return this.httpClient.post(this.apiUrl, undefined, {
// AUTO DISABLED - fix me if you like :)
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
try {
return await this.httpClient.post(this.apiUrl, undefined, {
params,
})
} catch {
return undefined
}
}
}

View File

@@ -6,11 +6,17 @@
import { createSelector } from "@reduxjs/toolkit"
import { isAppUpdateProcessPassed } from "Core/app-initialization/selectors/is-app-update-process-passed.selector"
import { isUsbAccessGrantedSelector } from "Core/settings/selectors/is-usb-access-granted.selector"
import { settingsStateSelector } from "Core/settings/selectors"
export const isAppInitializationFinishedSelector = createSelector(
isAppUpdateProcessPassed,
isUsbAccessGrantedSelector,
(appUpdateProcessPassed, usbAccessGranted): boolean => {
return appUpdateProcessPassed && usbAccessGranted
settingsStateSelector,
(
appUpdateProcessPassed,
usbAccessGranted,
{ privacyPolicyAccepted }
): boolean => {
return appUpdateProcessPassed && usbAccessGranted && Boolean(privacyPolicyAccepted)
}
)

View File

@@ -5,11 +5,37 @@
import { createSelector } from "@reduxjs/toolkit"
import { settingsStateSelector } from "Core/settings/selectors"
import { shouldAppUpdateFlowVisible } from "Core/app-initialization/selectors/should-app-update-flow-visible.selector"
export const isAppUpdateProcessPassed = createSelector(
const selectNoDataAboutUpdateAvaible = createSelector(
settingsStateSelector,
(settingsState): boolean => {
const { updateAvailable, updateAvailableSkipped, checkingForUpdateFailed } = settingsState
return checkingForUpdateFailed || updateAvailableSkipped || updateAvailable === false
({
updateAvailable,
updateAvailableSkipped,
checkingForUpdateFailed,
updateRequired,
}): boolean => {
return (
updateAvailableSkipped === undefined &&
updateAvailable === undefined &&
checkingForUpdateFailed &&
!updateRequired
)
}
)
const selectAppUpdateFlowPassed = createSelector(
settingsStateSelector,
shouldAppUpdateFlowVisible,
({ updateAvailable }, appUpdateFlowVisible): boolean => {
return updateAvailable !== undefined && !appUpdateFlowVisible
}
)
export const isAppUpdateProcessPassed = createSelector(
selectNoDataAboutUpdateAvaible,
selectAppUpdateFlowPassed,
(noDataAboutUpdateAvaible, appUpdateFlowPassed): boolean => {
return appUpdateFlowPassed || noDataAboutUpdateAvaible
}
)

View File

@@ -8,8 +8,7 @@ import { settingsStateSelector } from "Core/settings/selectors"
export const shouldAppUpdateFlowVisible = createSelector(
settingsStateSelector,
(settingsState): boolean => {
const { updateAvailable, updateAvailableSkipped } = settingsState
return updateAvailable === true && !updateAvailableSkipped
({ updateAvailable, updateAvailableSkipped, updateRequired }): boolean => {
return (updateAvailable && !updateAvailableSkipped) || updateRequired
}
)

View File

@@ -1,8 +0,0 @@
/**
* Copyright (c) Mudita sp. z o.o. All rights reserved.
* For licensing, see https://github.com/mudita/mudita-center/blob/master/LICENSE.md
*/
export enum ErrorConnectingModalTestIds {
Container = "error-connecting-modal-container"
}

View File

@@ -1,27 +0,0 @@
/**
* Copyright (c) Mudita sp. z o.o. All rights reserved.
* For licensing, see https://github.com/mudita/mudita-center/blob/master/LICENSE.md
*/
import React from "react"
import { Meta } from "@storybook/react"
import Story from "Core/__deprecated__/renderer/components/storybook/story.component"
import { action } from "@storybook/addon-actions"
import ErrorConnectingModal from "Core/connecting/components/error-connecting-modal"
export const ErrorConnectingModalStory = (): JSX.Element => {
return (
<Story transparentMode>
<ErrorConnectingModal
open
closeModal={action("Close Backup Modal")}
onCloseButton={action("Cancel Backup Action")}
/>
</Story>
)
}
export default {
title: "Views|Connecting/Backup Modal Dialogs",
component: ErrorConnectingModalStory,
} as Meta

View File

@@ -1,97 +0,0 @@
/**
* Copyright (c) Mudita sp. z o.o. All rights reserved.
* For licensing, see https://github.com/mudita/mudita-center/blob/master/LICENSE.md
*/
import { FunctionComponent } from "Core/core/types/function-component.interface"
import { intl } from "Core/__deprecated__/renderer/utils/intl"
import Icon from "Core/__deprecated__/renderer/components/core/icon/icon.component"
import Text, {
TextDisplayStyle,
} from "Core/__deprecated__/renderer/components/core/text/text.component"
import React, { ComponentProps, ReactNode } from "react"
import { defineMessages } from "react-intl"
import {
ModalContent,
ModalDialog,
ModalLink,
RoundIconWrapper,
} from "Core/ui/components/modal-dialog"
import { ModalSize } from "Core/__deprecated__/renderer/components/core/modal/modal.interface"
import { Size } from "Core/__deprecated__/renderer/components/core/button/button.config"
import { ErrorConnectingModalTestIds } from "Core/connecting/components/error-connecting-modal-test-ids.enum"
import { IconType } from "Core/__deprecated__/renderer/components/core/icon/icon-type"
import { ModalLayers } from "Core/modals-manager/constants/modal-layers.enum"
import { ipcRenderer } from "electron-better-ipc"
import { HelpActions } from "Core/__deprecated__/common/enums/help-actions.enum"
const messages = defineMessages({
errorConnectingModalHeaderTitle: {
id: "module.connecting.errorConnectingModalHeaderTitle",
},
errorConnectingModalHeaderSubtitle: {
id: "module.connecting.errorConnectingModalHeaderSubTitle",
},
errorConnectingModalSecondaryButton: {
id: "module.connecting.errorConnectingModalSecondaryButton",
},
errorConnectingModalTitle: {
id: "module.connecting.errorConnectingModalTitle",
},
errorConnectingDescription: {
id: "module.connecting.errorConnectingDescription",
},
})
const ErrorConnectingModal: FunctionComponent<
ComponentProps<typeof ModalDialog>
> = ({ closeModal, onClose, ...props }) => {
const openHelpWindow = () => ipcRenderer.callMain(HelpActions.OpenWindow)
return (
<ModalDialog
testId={ErrorConnectingModalTestIds.Container}
size={ModalSize.Small}
title={intl.formatMessage(messages.errorConnectingModalHeaderTitle)}
actionButtonSize={Size.FixedMedium}
layer={ModalLayers.ErrorConnecting}
closeButton={false}
actionButtonLabel={intl.formatMessage(
messages.errorConnectingModalSecondaryButton
)}
onActionButtonClick={closeModal}
onClose={onClose}
closeModal={closeModal}
{...props}
>
<ModalContent>
<RoundIconWrapper>
<Icon type={IconType.ThinFail} width={3.2} />
</RoundIconWrapper>
<Text
displayStyle={TextDisplayStyle.Headline4}
color="primary"
message={messages.errorConnectingModalHeaderSubtitle}
/>
<Text
displayStyle={TextDisplayStyle.Paragraph3}
color="info"
message={messages.errorConnectingModalTitle}
/>
<Text
displayStyle={TextDisplayStyle.Paragraph3}
color="info"
message={{
...messages.errorConnectingDescription,
values: {
link: (...chunks: ReactNode[]) => (
<ModalLink onClick={openHelpWindow}>{chunks}</ModalLink>
),
},
}}
/>
</ModalContent>
</ModalDialog>
)
}
export default ErrorConnectingModal

View File

@@ -1,12 +0,0 @@
/**
* Copyright (c) Mudita sp. z o.o. All rights reserved.
* For licensing, see https://github.com/mudita/mudita-center/blob/master/LICENSE.md
*/
import { updateSettings } from "Core/settings/requests"
const registerFirstPhoneConnection = (): void => {
void updateSettings({ key: "neverConnected", value: false })
}
export default registerFirstPhoneConnection

View File

@@ -7,7 +7,7 @@ import React, { ComponentProps } from "react"
import { Provider } from "react-redux"
import store from "Core/__deprecated__/renderer/store"
import { renderWithThemeAndIntl } from "Core/__deprecated__/renderer/utils/render-with-theme-and-intl"
import ModalsManager from "Core/modals-manager/components/modals-manager.container"
import ModalsManager from "Core/modals-manager/components/modals-manager.component"
import ContactSupportFlow from "Core/contact-support/containers/contact-support-flow.container"
import { ContactSupportFlowTestIds } from "Core/contact-support/components/contact-support-flow-test-ids.component"

View File

@@ -274,6 +274,13 @@ const Contacts: FunctionComponent<ContactsProps> = ({
if (payload || message) {
const newError: FormError[] = []
if (message === "Edit Contact request failed") {
void modalService.openModal(<ErrorDataModal />, true)
reject()
return
}
if (
message === "phone-number-duplicated" &&
payload?.primaryPhoneNumberIsDuplicated

View File

@@ -10,7 +10,7 @@ import { useDeviceConnectedEffect } from "Core/core/hooks/use-device-connected-e
import { useApplicationUpdateEffects } from "Core/core/hooks/use-application-update-effects"
import { CrashDump } from "Core/crash-dump"
import NetworkStatusChecker from "Core/__deprecated__/renderer/components/core/network-status-checker/network-status-checker.container"
import ModalsManager from "Core/modals-manager/components/modals-manager.container"
import ModalsManager from "Core/modals-manager/components/modals-manager.component"
import { useWatchOutboxEntriesEffect } from "Core/core/hooks/use-watch-outbox-entries-effect"
import { useWatchUnlockStatus } from "Core/core/hooks/use-watch-unlock-status-effect"
import { useDeviceLockedEffect } from "Core/core/hooks/use-device-locked-effect"

View File

@@ -198,14 +198,13 @@ test("Don't display any modal when `loadingState` flag is equal to `State.Loadin
).not.toBeInTheDocument()
})
test("display `Error` modal if data.files list isn't empty and `loadingState` flag is equal to `State.Failed`", () => {
test("Don't display any modal when `loadingState` flag is equal to `State.Failed`", () => {
render({
...initialStateMock,
crashDump: {
...initialStateMock.crashDump,
data: {
...initialStateMock.crashDump.data,
files: ["/pure/logs/crash-dumps/file.hex"],
},
loadingState: State.Failed,
},
@@ -214,7 +213,9 @@ test("display `Error` modal if data.files list isn't empty and `loadingState` fl
expect(
screen.queryByTestId(CrashDumpModalTestingIds.Content)
).not.toBeInTheDocument()
expect(screen.queryByTestId(CrashDumpTestingIds.Failed)).toBeInTheDocument()
expect(
screen.queryByTestId(CrashDumpTestingIds.Failed)
).not.toBeInTheDocument()
expect(
screen.queryByTestId(CrashDumpTestingIds.Success)
).not.toBeInTheDocument()

View File

@@ -157,7 +157,6 @@ const mapStateToProps = (state: ReduxRootState) => ({
state.crashDump.downloadingState === State.Loaded &&
state.crashDump.sendingState === State.Loaded,
failed:
state.crashDump.loadingState === State.Failed ||
state.crashDump.downloadingState === State.Failed ||
state.crashDump.sendingState === State.Failed,
deviceType: state.device.deviceType,

View File

@@ -11,49 +11,42 @@ enum SerialPortGroup {
uucp = "uucp",
}
const POTENTIAL_GROUPS = [SerialPortGroup.dialout, SerialPortGroup.uucp];
export class DesktopService {
public async isLinux(): Promise<boolean> {
return process.platform === "linux"
}
public async hasUserSerialPortAccess(): Promise<boolean> {
const userGroups = await this.getUserGroups();
return POTENTIAL_GROUPS.some(group => userGroups.includes(group));
const userGroups = await this.getUserGroups()
return userGroups.includes(SerialPortGroup.dialout)
}
public async addUserToSerialPortGroup(): Promise<void> {
const userGroups = await this.getUserGroups();
const groupName = POTENTIAL_GROUPS.find(group => !userGroups.includes(group));
if (groupName) {
const command = `usermod -aG ${groupName} $USER`;
const command = `usermod -aG ${SerialPortGroup.dialout} $USER & usermod -aG ${SerialPortGroup.uucp} $USER`
// Set simpler process.title, otherwise, there is an error from sudoPrompt.exec - 'process.title cannot be used as a valid name.'
process.title = "Mudita Center: assign serial port access";
process.title = "Mudita Center: assign serial port access"
return new Promise<void>((resolve, reject) => {
sudoPrompt.exec(command, { name: 'User Serial Port Access' }, (error) => {
sudoPrompt.exec(command, { name: "User Serial Port Access" }, (error) => {
if (error === null) {
resolve();
resolve()
} else {
reject("Could not add user to serial port group");
}
});
});
reject("Could not add user to serial port group")
}
})
})
}
private async getUserGroups(): Promise<string[]> {
return new Promise<string[]>((resolve, reject) => {
exec("groups", (error, stdout, stderr) => {
if (error || stderr) {
reject(`${error?.name} - ${error?.message} - ${stderr}`);
reject(`${error?.name} - ${error?.message} - ${stderr}`)
} else {
const groups = stdout.trim().split(/\s+/);
resolve(groups);
const groups = stdout.trim().split(/\s+/)
resolve(groups)
}
});
});
})
})
}
}

View File

@@ -55,11 +55,9 @@ export const initializeMuditaPure = createAsyncThunk<
return DeviceInitializationStatus.Aborted
}
const unlockStatus = await dispatch(getUnlockStatus())
if (!unlockStatus.payload) {
if (unlockStatus.payload === "LOCKED") {
return DeviceInitializationStatus.Initializing
}
if (!isActiveDeviceAttachedSelector(getState())) {
} else if (unlockStatus.payload === "ABORTED") {
dispatch(
setDeviceInitializationStatus(DeviceInitializationStatus.Aborted)
)

View File

@@ -86,7 +86,7 @@ export const PasscodeInputs: FunctionComponent<Props> = ({
const onKeyDownHandler =
(number: number) =>
(e: { key: string; code: string; preventDefault: () => void }) => {
if (/[0-9]/.test(e.key)) {
if (/^[0-9]$/.test(e.key)) {
const backspaceEdgeCase = activeInput === 0 && e.key === ""
if (
activeInput !== undefined &&

View File

@@ -46,6 +46,13 @@ const letterKeyEvent = {
charCode: 0,
} as KeyboardEvent
const F1KeyEvent = {
key: "F1",
code: "F1",
keyCode: 112,
charCode: 0,
} as KeyboardEvent
const backspaceKeyEvent = {
key: "Backspace",
code: "Backspace",
@@ -88,7 +95,7 @@ test("Passcode inputs are disabled when filled", () => {
expect(inputsList()[0]).toHaveStyleRule("background-color", "#f4f5f6")
})
test("Show typing error message", async () => {
test("Show typing error message when a latter is typed", async () => {
const { inputsList, errorMessage } = renderer()
fireEvent.keyDown(inputsList()[0] as Element, letterKeyEvent)
await waitFor(() =>
@@ -98,6 +105,16 @@ test("Show typing error message", async () => {
)
})
test("Show typing error message when F1 is typed", async () => {
const { inputsList, errorMessage } = renderer()
fireEvent.keyDown(inputsList()[0] as Element, F1KeyEvent)
await waitFor(() =>
expect(errorMessage()).toHaveTextContent(
"[value] component.passcodeModalErrorTyping"
)
)
})
test("Message is displayed properly when request about phone lock return internal server error", async () => {
const { inputsList, errorMessage } = renderer({
unlockDevice: jest.fn().mockReturnValue({

View File

@@ -44,21 +44,24 @@ const DrawerWrapper = styled("div")`
.EZDrawer .EZDrawer__checkbox:checked ~ .EZDrawer__container {
transition: transform 500ms;
}
* {
box-sizing: border-box;
}
`
const DrawerChildrenContainer = styled("div")`
padding: 1.3rem 1.8rem 1.3rem 1.8rem;
padding: 1.8rem;
overflow: hidden;
display: flex;
flex-direction: column;
gap: 3.2rem;
gap: 3.4rem;
height: 100%;
`
const Header = styled("div")`
display: flex;
justify-content: space-between;
padding-right: 1.8rem;
padding-right: 0.7rem;
`
const DevicesContainer = styled("div")`

View File

@@ -25,10 +25,9 @@ import { intl } from "Core/__deprecated__/renderer/utils/intl"
import { getSerialNumberValue } from "Core/utils/get-serial-number-value"
const Device = styled("div")<{ active: boolean }>`
padding: 1.8rem 2.4rem 1.8rem 1rem;
padding: 1.8rem 2.1rem;
display: flex;
min-width: 27.2rem;
max-width: 27.2rem;
width: 100%;
&:hover {
background: ${backgroundColor("main")};
@@ -55,7 +54,7 @@ const DeviceImageContainer = styled("div")`
justify-content: center;
min-height: 9.6rem;
min-width: 9.1rem;
padding: 0 2.4rem 0 0rem;
padding: 0 2.8rem 0 0;
`
export const DeviceImageStyled = styled(DeviceImage)`
@@ -101,7 +100,7 @@ const ActiveDot = styled("span")`
background-color: ${textColor("primary")};
border-radius: 50%;
display: inline-block;
margin: 0rem 0.5rem 0.8rem 0.5rem;
margin: 0 0.5rem 0.8rem 0.5rem;
`
const DeviceName = styled(Text)`

View File

@@ -4,19 +4,22 @@
*/
import { createAsyncThunk } from "@reduxjs/toolkit"
import { DeviceEvent } from "Core/device/constants"
import { DeviceCommunicationError, DeviceEvent } from "Core/device/constants"
import { unlockDeviceStatusRequest } from "Core/device/requests"
import { ReduxRootState } from "Core/__deprecated__/renderer/store"
import { setLockTime } from "Core/device/actions/base.action"
import { setLockTime, setUnlockedStatus } from "Core/device/actions/base.action"
import { getLeftTimeSelector } from "Core/device/selectors"
import { handleCommunicationError } from "Core/device/actions/handle-communication-error.action"
export type UnlockStatus = "UNLOCKED" | "LOCKED" | "ABORTED"
export const getUnlockStatus = createAsyncThunk<
boolean,
UnlockStatus,
void,
{ state: ReduxRootState }
>(DeviceEvent.GetUnlockedStatus, async (_, { dispatch, getState }) => {
const { ok, error } = await unlockDeviceStatusRequest()
const result = await unlockDeviceStatusRequest()
const { ok, error } = result
const leftTime = getLeftTimeSelector(getState())
if (ok && leftTime !== undefined) {
@@ -27,5 +30,15 @@ export const getUnlockStatus = createAsyncThunk<
await dispatch(handleCommunicationError(error))
}
return ok
if (error?.type === DeviceCommunicationError.DeviceLocked) {
dispatch(setUnlockedStatus(false))
return "LOCKED"
}
if (ok) {
dispatch(setUnlockedStatus(true))
return "UNLOCKED"
} else {
return "ABORTED"
}
})

View File

@@ -5,7 +5,6 @@
import { createReducer } from "@reduxjs/toolkit"
import {
getUnlockStatus,
loadDeviceData,
loadStorageInfoAction,
setCriticalBatteryLevelStatus,
@@ -110,15 +109,6 @@ export const deviceReducer = createReducer<DeviceState>(
error: action.payload as AppError,
}
})
.addCase(getUnlockStatus.fulfilled, (state, action) => {
return {
...state,
status: {
...state.status,
unlocked: action.payload,
},
}
})
.addCase(setLockTime, (state, action) => {
return {
...state,

View File

@@ -25,8 +25,7 @@ import Icon, {
IconSize,
} from "Core/__deprecated__/renderer/components/core/icon/icon.component"
import { NormalizedHelpEntry } from "Core/__deprecated__/renderer/utils/contentful/normalize-help-data"
import ModalsManager from "Core/modals-manager/components/modals-manager.container"
import { fontWeight } from "Core/core/styles/theming/theme-getters"
import ModalsManager from "Core/modals-manager/components/modals-manager.component"
import { IconButtonWithSecondaryTooltip } from "Core/__deprecated__/renderer/components/core/icon-button-with-tooltip/icon-button-with-secondary-tooltip.component"
import { defineMessages } from "react-intl"
import { IconType } from "Core/__deprecated__/renderer/components/core/icon/icon-type"
@@ -91,10 +90,6 @@ const ArrowIcon = styled(Icon)`
transform: rotate(270deg);
`
const NormalHeading = styled(Text)`
font-weight: ${fontWeight("default")};
`
const Help: FunctionComponent<Props> = ({
list: { collection = [], items },
searchQuestion,

View File

@@ -19,9 +19,9 @@ export const checkAppRequiresSerialPortGroup = createAsyncThunk<
ModalsManagerEvent.ShowAppRequiresSerialPortGroup,
async (_, { dispatch }) => {
const runningOnLinux = await isLinux()
const runningOnCI = process.env.CI === "true"
const runningOnE2ECI = process.env.E2ECI === "true"
if (!runningOnLinux || runningOnCI) {
if (!runningOnLinux || runningOnE2ECI) {
dispatch(setUserHasSerialPortAccess(true))
return
}

View File

@@ -4,34 +4,28 @@
*/
import React from "react"
import { useSelector } from "react-redux"
import { FunctionComponent } from "Core/core/types/function-component.interface"
import ContactSupportFlow from "Core/contact-support/containers/contact-support-flow.container"
import { UpdateOsInterruptedFlowContainer } from "Core/update/components/update-os-interrupted-flow"
import ErrorConnectingModal from "Core/connecting/components/error-connecting-modal"
import ConnectingLoaderModal from "Core/modals-manager/components/connecting-loader-modal.component"
import DetachedDuringUploadErrorModal
from "Core/files-manager/components/dettached-during-upload-error-modal/dettached-during-upload-error-modal.component"
import DetachedDuringUploadErrorModal from "Core/files-manager/components/dettached-during-upload-error-modal/dettached-during-upload-error-modal.component"
import { AppUpdateFlow } from "Core/settings/components"
import { shouldAppUpdateVisibleSelector } from "Core/modals-manager/selectors/should-app-update-visible.selector"
import { ReduxRootState } from "Core/__deprecated__/renderer/store"
type Props = {
contactSupportFlowShow: boolean
deviceInitializationFailedModalShowEnabled: boolean
hideModals: () => void
}
const ModalsManager: FunctionComponent<Props> = ({
contactSupportFlowShow,
deviceInitializationFailedModalShowEnabled,
hideModals,
}) => {
const ModalsManager: FunctionComponent = () => {
const appUpdateVisible = useSelector(shouldAppUpdateVisibleSelector)
const contactSupportFlowShow = useSelector(
(state: ReduxRootState) => state.modalsManager.contactSupportFlowShow
)
return (
<>
{deviceInitializationFailedModalShowEnabled && (
<ErrorConnectingModal open closeModal={hideModals} />
)}
{contactSupportFlowShow && <ContactSupportFlow />}
<UpdateOsInterruptedFlowContainer />
<ConnectingLoaderModal />
<DetachedDuringUploadErrorModal />
{appUpdateVisible && <AppUpdateFlow />}
</>
)
}

View File

@@ -1,22 +0,0 @@
/**
* Copyright (c) Mudita sp. z o.o. All rights reserved.
* For licensing, see https://github.com/mudita/mudita-center/blob/master/LICENSE.md
*/
import { connect } from "react-redux"
import { ReduxRootState, RootState } from "Core/__deprecated__/renderer/store"
import ModalsManager from "Core/modals-manager/components/modals-manager.component"
import { hideModals } from "Core/modals-manager"
const mapStateToProps = (state: RootState & ReduxRootState) => {
return {
...state.modalsManager,
deviceInitializationFailedModalShowEnabled: false,
}
}
const mapDispatchToProps = {
hideModals,
}
export default connect(mapStateToProps, mapDispatchToProps)(ModalsManager)

View File

@@ -4,14 +4,7 @@
*/
export enum ModalsManagerEvent {
CheckAppForcedUpdateFlowToShow = "CHECK_APP_FORCED_UPDATE_FLOW_TO_SHOW",
CheckAppUpdateFlowToShow = "CHECK_APP_UPDATE_FLOW_TO_SHOW",
CheckCollectingDataModalToShow = "CHECK_COLLECTING_DATA_MODAL_TO_SHOW",
HideModals = "HIDE_MODALS",
HideCollectingDataModal = "HIDE_COLLECTING_DATA_MODAL",
ShowModal = "SHOW_MODAL",
ShowCollectingDataModal = "SHOW_COLLECTING_DATA_MODAL",
ShowAppForcedUpdateFlow = "SHOW_APP_FORCED_UPDATE_FLOW",
ShowAppRequiresSerialPortGroup = "SHOW_APP_REQUIRES_SERIAL_PORT_GROUP",
AddUSBAccess = "ADD_USB_ACCESS",
}

View File

@@ -10,7 +10,6 @@ export enum ModalLayers {
ContactSupport,
Passcode,
EULA,
ErrorConnecting,
LinuxSerialPortGroup,
UpdateApp,
PrivacyPolicy,

View File

@@ -0,0 +1,20 @@
/**
* Copyright (c) Mudita sp. z o.o. All rights reserved.
* For licensing, see https://github.com/mudita/mudita-center/blob/master/LICENSE.md
*/
import { createSelector } from "@reduxjs/toolkit"
import { getAppInitializationStatus } from "Core/app-initialization/selectors/get-app-initialization-status.selector"
import { modalsManagerStateSelector } from "Core/modals-manager"
import { AppInitializationStatus } from "Core/app-initialization/reducers/app-initialization.interface"
export const shouldAppUpdateVisibleSelector = createSelector(
modalsManagerStateSelector,
getAppInitializationStatus,
({ appUpdateFlowShow }, appInitializationStatus): boolean => {
return (
appUpdateFlowShow &&
appInitializationStatus === AppInitializationStatus.Initialized
)
}
)

View File

@@ -41,7 +41,7 @@ export interface Props {
onTroubleshooting?: VoidFunction
}
const deviceNames = ["Harmony 1", "Harmony 2", "Pure", "Kompakt"]
const deviceNames = ["Harmony 1", "Harmony 2", "Pure"]
const OnboardingUI: FunctionComponent<Props> = ({
onCancel = noop,

View File

@@ -17,14 +17,9 @@ const lastBackupDate = new Date("2020-01-15T07:35:01.562Z")
type Props = ComponentProps<typeof Backup>
const defaultProps: Props = {
autostart: false,
collectingData: undefined,
tethering: false,
tray: false,
diagnosticSentTimestamp: 0,
language: "en-US",
onBackupCreate: noop,
neverConnected: false,
osBackupLocation: "",
osDownloadLocation: "",
backupActionDisabled: false,

View File

@@ -15,6 +15,8 @@ export const setSettings = createAction<
| "updateAvailable"
| "latestVersion"
| "updateAvailableSkipped"
| "checkingForUpdate"
| "checkingForUpdateFailed"
>
>(SettingsEvent.SetSettings)

View File

@@ -1,17 +0,0 @@
/**
* Copyright (c) Mudita sp. z o.o. All rights reserved.
* For licensing, see https://github.com/mudita/mudita-center/blob/master/LICENSE.md
*/
import { createAsyncThunk } from "@reduxjs/toolkit"
import { SettingsEvent } from "Core/settings/constants"
import { updateSettings } from "Core/settings/requests"
export const deleteCollectingData = createAsyncThunk(
SettingsEvent.DeleteCollectingData,
async () => {
await updateSettings({ key: "collectingData", value: undefined })
return
}
)

View File

@@ -6,9 +6,5 @@
export * from "./base.action"
export * from "./check-update-available.action"
export * from "./load-settings.action"
export * from "./send-diagnostic-data.action"
export * from "./set-diagnostic-timestamp.action"
export * from "./set-os-backup-location.action"
export * from "./toggle-tethering.action"
export * from "./toggle-collection-data.action"
export * from "./toggle-privacy-policy-accepted.action"

View File

@@ -18,7 +18,7 @@ jest.mock("../../../../apps/mudita-center/package.json", () => ({
}))
jest.mock("Core/settings/requests", () => ({
getSettings: jest.fn().mockReturnValue({ collectingData: false }),
getSettings: jest.fn().mockReturnValue({}),
getConfiguration: jest.fn().mockReturnValue({
centerVersion: "1.0.0",
productVersions: {
@@ -63,9 +63,6 @@ test("`loadSettings` action dispatch SettingsEvent.LoadSettings event and calls
{
type: SettingsEvent.SetSettings,
payload: {
checkingForUpdate: false,
checkingForUpdateFailed: false,
collectingData: false,
currentVersion: `${packageInfo.version}`,
lowestSupportedVersions: {
lowestSupportedCenterVersion: "1.0.0",

View File

@@ -37,15 +37,11 @@ export const loadSettings = createAsyncThunk<
)
}
settings.collectingData ? logger.enableRollbar() : logger.disableRollbar()
dispatch(
setSettings({
...settings,
updateRequired,
currentVersion: packageInfo.version,
checkingForUpdate: false,
checkingForUpdateFailed: false,
lowestSupportedVersions: {
lowestSupportedCenterVersion: configuration.centerVersion,
lowestSupportedProductVersion: configuration.productVersions,

View File

@@ -1,158 +0,0 @@
/**
* Copyright (c) Mudita sp. z o.o. All rights reserved.
* For licensing, see https://github.com/mudita/mudita-center/blob/master/LICENSE.md
*/
import { AnyAction } from "@reduxjs/toolkit"
import thunk from "redux-thunk"
import createMockStore from "redux-mock-store"
import MockDate from "mockdate"
import { sendDiagnosticData } from "./send-diagnostic-data.action"
import logger from "Core/__deprecated__/main/utils/logger"
const dayBeforeDateMock = "2021-07-05T11:50:35.157Z"
const todayDateMock = "2021-08-05T11:50:35.157Z"
MockDate.set(new Date(todayDateMock))
const setDiagnosticTimestampMock = jest.fn()
jest.mock("Core/__deprecated__/main/utils/logger", () => ({
error: jest.fn(),
info: jest.fn(),
}))
jest.mock("Core/settings/actions/set-diagnostic-timestamp.action", () => ({
setDiagnosticTimestamp: () => setDiagnosticTimestampMock,
}))
afterAll(() => {
MockDate.reset()
})
afterEach(() => {
jest.resetAllMocks()
})
describe("When `serialNumber` is equal to `undefined`", () => {
test("calls `logger.error` with error message and skip `setDiagnosticTimestamp`", async () => {
const mockStore = createMockStore([thunk])({
settings: {
collectingData: undefined,
diagnosticSentTimestamp: undefined,
},
device: {
data: {
serialNumber: undefined,
},
},
})
const {
meta: { requestId },
// AUTO DISABLED - fix me if you like :)
// eslint-disable-next-line @typescript-eslint/await-thenable
} = await mockStore.dispatch(sendDiagnosticData() as unknown as AnyAction)
expect(mockStore.getActions()).toEqual([
sendDiagnosticData.pending(requestId),
sendDiagnosticData.fulfilled(undefined, requestId, undefined),
])
expect(logger.error).toHaveBeenCalledWith(
"Send Diagnostic Data: device logs fail. SerialNumber is undefined."
)
expect(setDiagnosticTimestampMock).not.toHaveBeenCalled()
})
})
describe("When user disallow sending diagnostic data", () => {
test("calls `logger.info` with info message and skip `setDiagnosticTimestamp`", async () => {
const mockStore = createMockStore([thunk])({
settings: {
collectingData: false,
diagnosticSentTimestamp: undefined,
},
device: {
data: {
serialNumber: "0000000000",
},
},
})
const {
meta: { requestId },
// AUTO DISABLED - fix me if you like :)
// eslint-disable-next-line @typescript-eslint/await-thenable
} = await mockStore.dispatch(sendDiagnosticData() as unknown as AnyAction)
expect(mockStore.getActions()).toEqual([
sendDiagnosticData.pending(requestId),
sendDiagnosticData.fulfilled(undefined, requestId, undefined),
])
expect(logger.info).toHaveBeenCalledWith(
"Send Diagnostic Data: user no allowed sent data"
)
expect(setDiagnosticTimestampMock).not.toHaveBeenCalled()
})
})
describe("When diagnostic data has been sended today", () => {
test("calls `logger.info` with info message and skip `setDiagnosticTimestamp`", async () => {
const mockStore = createMockStore([thunk])({
settings: {
collectingData: true,
diagnosticSentTimestamp: todayDateMock,
},
device: {
data: {
serialNumber: "0000000000",
},
},
})
const {
meta: { requestId },
// AUTO DISABLED - fix me if you like :)
// eslint-disable-next-line @typescript-eslint/await-thenable
} = await mockStore.dispatch(sendDiagnosticData() as unknown as AnyAction)
expect(mockStore.getActions()).toEqual([
sendDiagnosticData.pending(requestId),
sendDiagnosticData.fulfilled(undefined, requestId, undefined),
])
expect(logger.info).toHaveBeenCalledWith(
`Send Diagnostic Data: data was sent at ${todayDateMock}`
)
expect(setDiagnosticTimestampMock).not.toHaveBeenCalled()
})
})
describe("When diagnostic data has been sended day before", () => {
test("calls `logger.info` with info message and set `diagnosticSentTimestamp` via `setDiagnosticTimestamp`", async () => {
const mockStore = createMockStore([thunk])({
settings: {
collectingData: true,
diagnosticSentTimestamp: dayBeforeDateMock,
},
device: {
data: {
serialNumber: "0000000000",
},
},
})
const {
meta: { requestId },
// AUTO DISABLED - fix me if you like :)
// eslint-disable-next-line @typescript-eslint/await-thenable
} = await mockStore.dispatch(sendDiagnosticData() as unknown as AnyAction)
expect(mockStore.getActions()).toEqual([
sendDiagnosticData.pending(requestId),
sendDiagnosticData.fulfilled(undefined, requestId, undefined),
])
expect(logger.info).toHaveBeenCalledWith(
`Send Diagnostic Data: skipped until the diagnostic data and storage system will be refined`
)
expect(setDiagnosticTimestampMock).toHaveBeenCalled()
})
})

View File

@@ -1,51 +0,0 @@
/**
* Copyright (c) Mudita sp. z o.o. All rights reserved.
* For licensing, see https://github.com/mudita/mudita-center/blob/master/LICENSE.md
*/
import { createAsyncThunk } from "@reduxjs/toolkit"
import { ReduxRootState } from "Core/__deprecated__/renderer/store"
import logger from "Core/__deprecated__/main/utils/logger"
import { isToday } from "Core/__deprecated__/renderer/utils/is-today"
import { SettingsEvent } from "Core/settings/constants"
import { setDiagnosticTimestamp } from "Core/settings/actions/set-diagnostic-timestamp.action"
export const sendDiagnosticData = createAsyncThunk<void, void>(
SettingsEvent.SendDiagnosticData,
// AUTO DISABLED - fix me if you like :)
// eslint-disable-next-line @typescript-eslint/require-await
async (_, { dispatch, getState }) => {
const state = getState() as ReduxRootState
const { collectingData, diagnosticSentTimestamp } = state.settings
const serialNumber = state.device.data?.serialNumber
if (serialNumber === undefined) {
logger.error(
`Send Diagnostic Data: device logs fail. SerialNumber is undefined.`
)
return
}
if (!collectingData) {
logger.info("Send Diagnostic Data: user no allowed sent data")
return
}
if (isToday(new Date(diagnosticSentTimestamp))) {
logger.info(
`Send Diagnostic Data: data was sent at ${diagnosticSentTimestamp}`
)
return
}
logger.info(
`Send Diagnostic Data: skipped until the diagnostic data and storage system will be refined`
)
const nowTimestamp = Date.now()
void dispatch(setDiagnosticTimestamp(nowTimestamp))
return
}
)

View File

@@ -1,43 +0,0 @@
/**
* Copyright (c) Mudita sp. z o.o. All rights reserved.
* For licensing, see https://github.com/mudita/mudita-center/blob/master/LICENSE.md
*/
import { AnyAction } from "@reduxjs/toolkit"
import thunk from "redux-thunk"
import createMockStore from "redux-mock-store"
import { setDiagnosticTimestamp } from "./set-diagnostic-timestamp.action"
import { updateSettings } from "Core/settings/requests"
jest.mock("Core/settings/requests", () => ({
updateSettings: jest.fn(),
}))
const dateMock = new Date("2021-08-05T11:50:35.157Z").getDate()
const mockStore = createMockStore([thunk])()
afterEach(() => {
jest.clearAllMocks()
})
test("calls `setDiagnosticTimestamp` and `updateSettings` request with timestamp", async () => {
expect(updateSettings).not.toHaveBeenCalled()
const {
meta: { requestId },
// AUTO DISABLED - fix me if you like :)
// eslint-disable-next-line @typescript-eslint/await-thenable
} = await mockStore.dispatch(
setDiagnosticTimestamp(dateMock) as unknown as AnyAction
)
expect(mockStore.getActions()).toEqual([
setDiagnosticTimestamp.pending(requestId, dateMock),
setDiagnosticTimestamp.fulfilled(dateMock, requestId, dateMock),
])
expect(updateSettings).toHaveBeenCalledWith({
key: "diagnosticSentTimestamp",
value: dateMock,
})
})

View File

@@ -1,17 +0,0 @@
/**
* Copyright (c) Mudita sp. z o.o. All rights reserved.
* For licensing, see https://github.com/mudita/mudita-center/blob/master/LICENSE.md
*/
import { createAsyncThunk } from "@reduxjs/toolkit"
import { SettingsEvent } from "Core/settings/constants"
import { updateSettings } from "Core/settings/requests"
export const setDiagnosticTimestamp = createAsyncThunk<number, number>(
SettingsEvent.SetDiagnosticTimestamp,
async (payload) => {
await updateSettings({ key: "diagnosticSentTimestamp", value: payload })
return payload
}
)

View File

@@ -1,17 +0,0 @@
/**
* Copyright (c) Mudita sp. z o.o. All rights reserved.
* For licensing, see https://github.com/mudita/mudita-center/blob/master/LICENSE.md
*/
import { createAsyncThunk } from "@reduxjs/toolkit"
import { SettingsEvent } from "Core/settings/constants"
import { updateSettings } from "Core/settings/requests"
export const toggleCollectionData = createAsyncThunk<boolean, boolean>(
SettingsEvent.ToggleCollectionData,
async (payload) => {
await updateSettings({ key: "collectingData", value: payload })
return payload
}
)

View File

@@ -1,36 +0,0 @@
/**
* Copyright (c) Mudita sp. z o.o. All rights reserved.
* For licensing, see https://github.com/mudita/mudita-center/blob/master/LICENSE.md
*/
import { AnyAction } from "@reduxjs/toolkit"
import thunk from "redux-thunk"
import createMockStore from "redux-mock-store"
import { toggleTethering } from "./toggle-tethering.action"
import { updateSettings } from "Core/settings/requests"
jest.mock("Core/settings/requests", () => ({
updateSettings: jest.fn(),
}))
const mockStore = createMockStore([thunk])()
afterEach(() => {
jest.clearAllMocks()
})
test("calls `toggleTethering` and `updateSettings` request with boolean", async () => {
expect(updateSettings).not.toHaveBeenCalled()
const {
meta: { requestId },
// AUTO DISABLED - fix me if you like :)
// eslint-disable-next-line @typescript-eslint/await-thenable
} = await mockStore.dispatch(toggleTethering(true) as unknown as AnyAction)
expect(mockStore.getActions()).toEqual([
toggleTethering.pending(requestId, true),
toggleTethering.fulfilled(true, requestId, true),
])
expect(updateSettings).toHaveBeenCalledWith({ key: "tethering", value: true })
})

View File

@@ -1,17 +0,0 @@
/**
* Copyright (c) Mudita sp. z o.o. All rights reserved.
* For licensing, see https://github.com/mudita/mudita-center/blob/master/LICENSE.md
*/
import { createAsyncThunk } from "@reduxjs/toolkit"
import { SettingsEvent } from "Core/settings/constants"
import { updateSettings } from "Core/settings/requests"
export const toggleTethering = createAsyncThunk<boolean, boolean>(
SettingsEvent.ToggleTethering,
async (payload) => {
await updateSettings({ key: "tethering", value: payload })
return payload
}
)

View File

@@ -12,6 +12,7 @@ import { ModalLayers } from "Core/modals-manager/constants/modal-layers.enum"
import { settingsStateSelector } from "Core/settings/selectors"
import { Dispatch } from "Core/__deprecated__/renderer/store"
import { skipAvailableUpdate } from "Core/settings/actions/base.action"
import { hideModals } from "Core/modals-manager"
export const AppUpdateFlow: FunctionComponent = () => {
const dispatch = useDispatch<Dispatch>()
@@ -21,6 +22,7 @@ export const AppUpdateFlow: FunctionComponent = () => {
const handleCloseModal = () => {
dispatch(skipAvailableUpdate())
dispatch(hideModals())
}
return (

View File

@@ -3,6 +3,11 @@
* For licensing, see https://github.com/mudita/mudita-center/blob/master/LICENSE.md
*/
import React from "react"
import { defineMessages } from "react-intl"
import { useDispatch } from "react-redux"
import { ipcRenderer } from "electron-better-ipc"
import styled from "styled-components"
import {
ModalContent,
ModalDialog,
@@ -10,24 +15,17 @@ import {
} from "Core/ui/components/modal-dialog"
import { ModalSize } from "Core/__deprecated__/renderer/components/core/modal/modal.interface"
import Icon from "Core/__deprecated__/renderer/components/core/icon/icon.component"
import React, { useEffect } from "react"
import { IconType } from "Core/__deprecated__/renderer/components/core/icon/icon-type"
import Text from "Core/__deprecated__/renderer/components/core/text/text.component"
import { TextDisplayStyle } from "Core/__deprecated__/renderer/components/core/text/text.component"
import styled from "styled-components"
import {
fontWeight,
textColor,
} from "Core/core/styles/theming/theme-getters"
import { defineMessages } from "react-intl"
import { intl } from "Core/__deprecated__/renderer/utils/intl"
import { ipcRenderer } from "electron-better-ipc"
import { AboutActions } from "Core/__deprecated__/common/enums/about-actions.enum"
import { useDispatch, useSelector } from "react-redux"
import { togglePrivacyPolicyAccepted } from "Core/settings/actions"
import { Dispatch, ReduxRootState } from "Core/__deprecated__/renderer/store"
import { deleteCollectingData } from "Core/settings/actions/delete-collecting-data.action"
import { Dispatch } from "Core/__deprecated__/renderer/store"
import { FunctionComponent } from "Core/core/types/function-component.interface"
import { ModalLayers } from "Core/modals-manager/constants/modal-layers.enum"
@@ -55,30 +53,15 @@ export const DescriptionText = styled(Text)`
`
const PrivacyPolicyModal: FunctionComponent = () => {
const { collectingData } = useSelector(
(state: ReduxRootState) => state.settings
)
const dispatch = useDispatch<Dispatch>()
const openPrivacyPolicyWindow = () =>
ipcRenderer.callMain(AboutActions.PolicyOpenBrowser)
const handleAgreeButtonClick = (): void => {
void dispatch(deleteCollectingData())
void dispatch(togglePrivacyPolicyAccepted(true))
}
useEffect(() => {
if (collectingData === undefined) {
void dispatch(togglePrivacyPolicyAccepted(true))
}
}, [collectingData, dispatch])
if (collectingData === undefined) {
return null
}
return (
<ModalDialog
open

View File

@@ -8,15 +8,11 @@ export enum SettingsEvent {
SetLatestVersion = "settings_set-latest-version",
SetSettings = "settings_set-settings",
SetSetting = "settings_set-setting",
SetDiagnosticTimestamp = "settings_set-diagnostic-timestamp",
SetOsBackupLocation = "settings_set-os-backup-location",
ToggleTethering = "settings_toggle-tethering",
ToggleUpdateAvailable = "settings_toggle-update-available",
ToggleCollectionData = "settings_toggle-collection-data",
TogglePrivacyPolicyAccepted = "settings_toggle-privacy-policy-accepted",
CheckUpdateAvailable = "settings_check-update-available",
SendDiagnosticData = "settings_send-diagnostic-data",
DeleteCollectingData = "settings_delete-collecting-data",
SetCheckingForUpdate = "settings_set-checking-for-update",
SetCheckingForUpdateFailed = "settings_set-checking-for-update-failed",
SkipAvailableUpdate = "settings_skip-available-update",

View File

@@ -9,14 +9,9 @@ import { SettingsService } from "Core/settings/services"
export const fakeSettings: Settings = {
applicationId: "app-Nr8uiSV7KmWxX3WOFqZPF7uB",
autostart: false,
tethering: false,
tray: true,
osBackupLocation: `fake/path/pure/phone/backups/`,
osDownloadLocation: `fake/path/pure/os/downloads/`,
language: "en-US",
neverConnected: true,
collectingData: false,
privacyPolicyAccepted: false,
diagnosticSentTimestamp: 0,
ignoredCrashDumps: [],

View File

@@ -4,17 +4,13 @@
*/
export interface Settings {
settingsSchemaVersion?: number
applicationId: string | null
osBackupLocation: string
osDownloadLocation: string
language: string
ignoredCrashDumps: string[]
diagnosticSentTimestamp: number
collectingData: boolean | undefined
privacyPolicyAccepted: boolean | undefined
neverConnected: boolean
tray: boolean
autostart: boolean
tethering: boolean
usbAccessRestartRequired: boolean
}

View File

@@ -16,14 +16,9 @@ import { SettingsState } from "Core/settings/reducers"
const settings: SettingsState = {
applicationId: "app-Nr8uiSV7KmWxX3WOFqZPF7uB",
autostart: false,
tethering: false,
tray: true,
osBackupLocation: `fake/path/pure/phone/backups/`,
osDownloadLocation: `fake/path/pure/os/downloads/`,
language: "en-US",
neverConnected: true,
collectingData: false,
privacyPolicyAccepted: false,
diagnosticSentTimestamp: 0,
ignoredCrashDumps: [],
@@ -81,74 +76,6 @@ describe("Functionality: loading settings", () => {
})
})
describe("Functionality: toggle tethering", () => {
test("Event: ToggleTethering/fulfilled set `tethering` value", () => {
expect(
settingsReducer(undefined, {
type: fulfilledAction(SettingsEvent.ToggleTethering),
payload: true,
})
).toEqual({
...initialState,
tethering: true,
})
expect(
settingsReducer(undefined, {
type: fulfilledAction(SettingsEvent.ToggleTethering),
payload: false,
})
).toEqual({
...initialState,
tethering: false,
})
})
})
describe("Functionality: toggle data collection", () => {
test("Event: ToggleCollectionData/fulfilled set `collectingData` value", () => {
expect(
settingsReducer(undefined, {
type: fulfilledAction(SettingsEvent.ToggleCollectionData),
payload: true,
})
).toEqual({
...initialState,
collectingData: true,
})
expect(
settingsReducer(undefined, {
type: fulfilledAction(SettingsEvent.ToggleCollectionData),
payload: false,
})
).toEqual({
...initialState,
collectingData: false,
})
})
})
describe("Functionality: diagnostic data timestamp", () => {
test("Event: SetDiagnosticTimestamp/fulfilled set `diagnosticSentTimestamp` value", () => {
const timestamp = new Date().getDate()
expect(
settingsReducer(
{
...initialState,
diagnosticSentTimestamp: 0,
},
{
type: fulfilledAction(SettingsEvent.SetDiagnosticTimestamp),
payload: timestamp,
}
)
).toEqual({
...initialState,
diagnosticSentTimestamp: timestamp,
})
})
})
describe("Functionality: os backup location", () => {
test("Event: SetOsBackupLocation/fulfilled set `osBackupLocation` value", () => {
expect(

View File

@@ -10,15 +10,11 @@ import {
setLatestVersion,
setOsBackupLocation,
setSettings,
setDiagnosticTimestamp,
toggleTethering,
toggleApplicationUpdateAvailable,
toggleCollectionData,
togglePrivacyPolicyAccepted,
setCheckingForUpdate,
setUserHasSerialPortAccess,
} from "Core/settings/actions"
import { deleteCollectingData } from "Core/settings/actions/delete-collecting-data.action"
import {
setCheckingForUpdateFailed,
skipAvailableUpdate,
@@ -35,12 +31,7 @@ export const initialState: SettingsState = {
language: "",
ignoredCrashDumps: [],
diagnosticSentTimestamp: 0,
collectingData: undefined,
privacyPolicyAccepted: undefined,
neverConnected: false,
tray: false,
autostart: false,
tethering: false,
updateRequired: false,
updateAvailable: undefined,
updateAvailableSkipped: undefined,
@@ -73,27 +64,13 @@ export const settingsReducer = createReducer<SettingsState>(
state.latestVersion = action.payload
})
.addCase(toggleTethering.fulfilled, (state, action) => {
state.tethering = action.payload
})
.addCase(toggleApplicationUpdateAvailable, (state, action) => {
state.updateAvailable = action.payload
})
.addCase(toggleCollectionData.fulfilled, (state, action) => {
state.collectingData = action.payload
})
.addCase(togglePrivacyPolicyAccepted.fulfilled, (state, action) => {
state.privacyPolicyAccepted = action.payload
})
.addCase(deleteCollectingData.fulfilled, (state, _) => {
state.collectingData = undefined
})
.addCase(setDiagnosticTimestamp.fulfilled, (state, action) => {
state.diagnosticSentTimestamp = action.payload
})
.addCase(setOsBackupLocation.fulfilled, (state, action) => {
state.osBackupLocation = action.payload

View File

@@ -17,14 +17,9 @@ jest.mock("Core/settings/store/schemas", () => ({
export const fakeSettings: Settings = {
applicationId: "app-Nr8uiSV7KmWxX3WOFqZPF7uB",
autostart: false,
tethering: false,
tray: true,
osBackupLocation: `fake/path/pure/phone/backups/`,
osDownloadLocation: `fake/path/pure/os/downloads/`,
language: "en-US",
neverConnected: true,
collectingData: false,
privacyPolicyAccepted: false,
diagnosticSentTimestamp: 0,
ignoredCrashDumps: [],

View File

@@ -6,7 +6,7 @@
// AUTO DISABLED - fix me if you like :)
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any
export const privacyPolicyAcceptedMigration = (store: any) => {
// AUTO DISABLED - fix me if you like :)
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call
if (store.get("settingsSchemaVersion") === undefined) {
store.set("privacyPolicyAccepted", false)
}
}

View File

@@ -0,0 +1,14 @@
/**
* Copyright (c) Mudita sp. z o.o. All rights reserved.
* For licensing, see https://github.com/mudita/mudita-center/blob/master/LICENSE.md
*/
// AUTO DISABLED - fix me if you like :)
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any
export const removeDeprecatedFieldsMigration = (store: any) => {
store.set("settingsSchemaVersion", 4)
store.delete("collectingData")
store.delete("autostart")
store.delete("tray")
store.delete("neverConnected")
}

View File

@@ -11,22 +11,13 @@ import translationConfig from "App/translations.config.json"
import { generateApplicationId } from "Core/settings/store/schemas/generate-application-id"
export const settingsSchema: Schema<Settings> = {
settingsSchemaVersion: {
type: "number",
},
applicationId: {
type: ["string", "null"],
default: generateApplicationId(),
},
autostart: {
type: "boolean",
default: false,
},
tethering: {
type: "boolean",
default: false,
},
tray: {
type: "boolean",
default: false,
},
osBackupLocation: {
type: "string",
default: path.join(getAppPath(), "pure", "phone", "backups"),
@@ -39,17 +30,9 @@ export const settingsSchema: Schema<Settings> = {
type: "string",
default: translationConfig.defaultLanguage,
},
neverConnected: {
type: "boolean",
default: true,
},
collectingData: {
type: "boolean",
default: undefined,
},
privacyPolicyAccepted: {
type: "boolean",
default: false,
default: true,
},
diagnosticSentTimestamp: {
type: "number",

View File

@@ -13,6 +13,7 @@ import {
removeUnusedFields,
osDownloadLocationMigration,
} from "Core/settings/store/migrations"
import { removeDeprecatedFieldsMigration } from "Core/settings/store/migrations/004-remove-deprecated-fields.migration"
export const settingsStore = new Store({
name: "settings",
@@ -27,5 +28,6 @@ export const settingsStore = new Store({
">=1.4.1": removeUnusedFields,
">=2.0.2": privacyPolicyAcceptedMigration,
">=2.1.0": osDownloadLocationMigration,
">=2.3.0": removeDeprecatedFieldsMigration,
},
})

6
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "mudita-center",
"version": "2.2.8",
"version": "2.3.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "mudita-center",
"version": "2.2.8",
"version": "2.3.0",
"license": "GPL-3.0",
"workspaces": [
"apps/mudita-center",
@@ -247,7 +247,7 @@
}
},
"apps/mudita-center": {
"version": "2.2.8",
"version": "2.3.0",
"license": "GPL-3.0",
"dependencies": {
"serialport": "10.1.0"

View File

@@ -2,7 +2,7 @@
"name": "mudita-center",
"description": "Mudita Center",
"productName": "Mudita Center",
"version": "2.2.8",
"version": "2.3.0",
"private": true,
"workspaces": {
"packages": [