From 77078df6063bda1f405516941f9dfdb8a24cd962 Mon Sep 17 00:00:00 2001 From: Kerry Archibald Date: Wed, 26 Jan 2022 17:23:26 +0100 Subject: [PATCH 01/11] use export settings and hide fields Signed-off-by: Kerry Archibald --- res/css/views/dialogs/_ExportDialog.scss | 4 + src/components/views/dialogs/ExportDialog.tsx | 231 ++++++++++++------ src/settings/Settings.tsx | 4 + src/settings/UIFeature.ts | 3 +- src/utils/exportUtils/Exporter.ts | 2 +- .../__snapshots__/ExportDialog-test.tsx.snap | 128 +++++----- 6 files changed, 232 insertions(+), 140 deletions(-) diff --git a/res/css/views/dialogs/_ExportDialog.scss b/res/css/views/dialogs/_ExportDialog.scss index 294daba2e87..71f4b32a039 100644 --- a/res/css/views/dialogs/_ExportDialog.scss +++ b/res/css/views/dialogs/_ExportDialog.scss @@ -89,3 +89,7 @@ limitations under the License. padding: 9px 10px; } } + +.mx_ExportDialog_attachments-checkbox { + margin-top: $spacing-16; +} diff --git a/src/components/views/dialogs/ExportDialog.tsx b/src/components/views/dialogs/ExportDialog.tsx index cfbd6af1bf6..a2815758ae6 100644 --- a/src/components/views/dialogs/ExportDialog.tsx +++ b/src/components/views/dialogs/ExportDialog.tsx @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React, { useRef, useState } from "react"; +import React, { useRef, useState, Dispatch, SetStateAction } from "react"; import { Room } from "matrix-js-sdk/src"; import { logger } from "matrix-js-sdk/src/logger"; @@ -39,18 +39,98 @@ import { useStateCallback } from "../../../hooks/useStateCallback"; import Exporter from "../../../utils/exportUtils/Exporter"; import Spinner from "../elements/Spinner"; import InfoDialog from "./InfoDialog"; +import SettingsStore from "../../../settings/SettingsStore"; +import { UIFeature } from "../../../settings/UIFeature"; interface IProps extends IDialogProps { room: Room; } +const isExportFormat = (config?: string): config is ExportFormat => + config && Object.values(ExportFormat).includes(config as ExportFormat); + +const isExportType = (config?: string): config is ExportType => + config && Object.values(ExportType).includes(config as ExportType); + +const validateNumberInRange = (min: number, max: number) => (value?: string | number) => { + const parsedSize = parseInt(value as string, 10); + return !(isNaN(parsedSize) || min > parsedSize || parsedSize > max); +}; + +// Sanitize setting values, exclude invalid or missing values +export const getSafeForceRoomExportSettings = (): { + format?: ExportFormat; range?: ExportType; numberOfMessages?: number; includeAttachments?: boolean; sizeMb?: number; +} => { + const config = SettingsStore.getValue(UIFeature.ForceRoomExportSettings); + if (!config || typeof config !== "object") return {}; + + const { format, range, numberOfMessages, includeAttachments, sizeMb } = config; + + return { + ...(isExportFormat(format) && { format }), + ...(isExportType(range) && { range }), + ...(validateNumberInRange(1, 10 ** 8)(numberOfMessages) && { numberOfMessages }), + // 100GB limit + ...(validateNumberInRange(1, 1024000)(sizeMb) && { sizeMb }), + ...(typeof includeAttachments === 'boolean' && { includeAttachments }), + }; +}; + +interface ExportConfig { + exportFormat: ExportFormat; + exportType: ExportType; + numberOfMessages: number; + sizeLimit: number; + includeAttachments: boolean; + setExportFormat?: Dispatch>; + setExportType?: Dispatch>; + setAttachments?: Dispatch>; + setNumberOfMessages?: Dispatch>; + setSizeLimit?: Dispatch>; +} +/** + * Set up form state using UIFeature.ForceRoomExportSettings or defaults + * Form fields configured in ForceRoomExportSettings are not allowed to be edited + * Only return change handlers for editable values + */ +const useExportFormState = (): ExportConfig => { + const config = getSafeForceRoomExportSettings(); + + const [exportFormat, setExportFormat] = useState(config.format || ExportFormat.Html); + const [exportType, setExportType] = useState(config.range || ExportType.Timeline); + const [includeAttachments, setAttachments] = + useState(config.includeAttachments !== undefined && config.includeAttachments); + const [numberOfMessages, setNumberOfMessages] = useState(config.numberOfMessages || 100); + const [sizeLimit, setSizeLimit] = useState(config.sizeMb || 8); + + return { + exportFormat, + exportType, + includeAttachments, + numberOfMessages, + sizeLimit, + setExportFormat: !config.format ? setExportFormat : undefined, + setExportType: !config.range ? setExportType : undefined, + setNumberOfMessages: !config.numberOfMessages ? setNumberOfMessages : undefined, + setSizeLimit: !config.sizeMb ? setSizeLimit : undefined, + setAttachments: config.includeAttachments === undefined ? setAttachments : undefined, + }; +}; const ExportDialog: React.FC = ({ room, onFinished }) => { - const [exportFormat, setExportFormat] = useState(ExportFormat.Html); - const [exportType, setExportType] = useState(ExportType.Timeline); - const [includeAttachments, setAttachments] = useState(false); + const { + exportFormat, + exportType, + includeAttachments, + numberOfMessages, + sizeLimit, + setExportFormat, + setExportType, + setNumberOfMessages, + setSizeLimit, + setAttachments, + } = useExportFormState(); + const [isExporting, setExporting] = useState(false); - const [numberOfMessages, setNumberOfMessages] = useState(100); - const [sizeLimit, setSizeLimit] = useState(8); const sizeLimitRef = useRef(); const messageCountRef = useRef(); const [exportProgressText, setExportProgressText] = useState(_t("Processing...")); @@ -110,9 +190,10 @@ const ExportDialog: React.FC = ({ room, onFinished }) => { }; const onExportClick = async () => { - const isValidSize = await sizeLimitRef.current.validate({ + const isValidSize = !setSizeLimit || (await sizeLimitRef.current.validate({ focused: false, - }); + })); + if (!isValidSize) { sizeLimitRef.current.validate({ focused: true }); return; @@ -146,12 +227,7 @@ const ExportDialog: React.FC = ({ room, onFinished }) => { }, }, { key: "number", - test: ({ value }) => { - const parsedSize = parseFloat(value); - const min = 1; - const max = 2000; - return !(isNaN(parsedSize) || min > parsedSize || parsedSize > max); - }, + test: ({ value }) => validateNumberInRange(1, 2000)(value), invalid: () => { const min = 1; const max = 2000; @@ -186,13 +262,7 @@ const ExportDialog: React.FC = ({ room, onFinished }) => { }, }, { key: "number", - test: ({ value }) => { - const parsedSize = parseFloat(value); - const min = 1; - const max = 10 ** 8; - if (isNaN(parsedSize)) return false; - return !(min > parsedSize || parsedSize > max); - }, + test: ({ value }) => validateNumberInRange(1, 10 ** 8)(value), invalid: () => { const min = 1; const max = 10 ** 8; @@ -236,7 +306,7 @@ const ExportDialog: React.FC = ({ room, onFinished }) => { }); let messageCount = null; - if (exportType === ExportType.LastNMessages) { + if (exportType === ExportType.LastNMessages && setNumberOfMessages) { messageCount = ( = ({ room, onFinished }) => { ) }

: null } - - { _t("Format") } - -
- setExportFormat(ExportFormat[key])} - definitions={exportFormatOptions} - /> + { !!setExportFormat && <> + + { _t("Format") } + - - { _t("Messages") } - - - { - setExportType(ExportType[e.target.value]); - }} - > - { exportTypeOptions } - - { messageCount } - - - { _t("Size Limit") } - - - setSizeLimit(parseInt(e.target.value))} - /> + setExportFormat(ExportFormat[key])} + definitions={exportFormatOptions} + /> + } + + { + !!setExportType && <> + + + { _t("Messages") } + + + { + setExportType(ExportType[e.target.value]); + }} + > + { exportTypeOptions } + + { messageCount } + + } + + { setSizeLimit && <> + + { _t("Size Limit") } + + + setSizeLimit(parseInt(e.target.value))} + /> + } + + { setAttachments && <> + + setAttachments( + (e.target as HTMLInputElement).checked, + ) + } + > + { _t("Include Attachments") } + + } - - setAttachments( - (e.target as HTMLInputElement).checked, - ) - } - > - { _t("Include Attachments") } -
{ isExporting ? (
diff --git a/src/settings/Settings.tsx b/src/settings/Settings.tsx index cf4e7d2b43f..432e3aa4747 100644 --- a/src/settings/Settings.tsx +++ b/src/settings/Settings.tsx @@ -956,4 +956,8 @@ export const SETTINGS: {[setting: string]: ISetting} = { supportedLevels: LEVELS_UI_FEATURE, default: true, }, + [UIFeature.ForceRoomExportSettings]: { + supportedLevels: LEVELS_UI_FEATURE, + default: {}, + }, }; diff --git a/src/settings/UIFeature.ts b/src/settings/UIFeature.ts index 225f785614f..41581d517b9 100644 --- a/src/settings/UIFeature.ts +++ b/src/settings/UIFeature.ts @@ -32,7 +32,8 @@ export enum UIFeature { Communities = "UIFeature.communities", AdvancedSettings = "UIFeature.advancedSettings", RoomHistorySettings = "UIFeature.roomHistorySettings", - TimelineEnableRelativeDates = "UIFeature.timelineEnableRelativeDates" + TimelineEnableRelativeDates = "UIFeature.timelineEnableRelativeDates", + ForceRoomExportSettings = "UIFeature.forceRoomExportSettings" } export enum UIComponent { diff --git a/src/utils/exportUtils/Exporter.ts b/src/utils/exportUtils/Exporter.ts index 3025be64928..70826cbfcef 100644 --- a/src/utils/exportUtils/Exporter.ts +++ b/src/utils/exportUtils/Exporter.ts @@ -48,7 +48,7 @@ export default abstract class Exporter { protected setProgressText: React.Dispatch>, ) { if (exportOptions.maxSize < 1 * 1024 * 1024|| // Less than 1 MB - exportOptions.maxSize > 2000 * 1024 * 1024|| // More than ~ 2 GB + exportOptions.maxSize > 1024000 * 1024 * 1024 || // More than 1000 GB exportOptions.numberOfMessages > 10**8 ) { throw new Error("Invalid export options"); diff --git a/test/components/views/dialogs/__snapshots__/ExportDialog-test.tsx.snap b/test/components/views/dialogs/__snapshots__/ExportDialog-test.tsx.snap index 68081248d72..15f65b84976 100644 --- a/test/components/views/dialogs/__snapshots__/ExportDialog-test.tsx.snap +++ b/test/components/views/dialogs/__snapshots__/ExportDialog-test.tsx.snap @@ -105,14 +105,14 @@ Array [

Select from the options below to export chats from your timeline

- - Format -
+ + Format +
Select from the options below to export chats from your timeline

- - Format -
+ + Format +
Select from the options below to export chats from your timeline

- - Format -
+ + Format + Select from the options below to export chats from your timeline

- - Format -
+ + Format +
Select from the options below to export chats from your timeline

- - Format -
+ + Format +
Select from the options below to export chats from your timeline

- - Format -
+ + Format + Select from the options below to export chats from your timeline

- - Format -
+ + Format +
Select from the options below to export chats from your timeline

- - Format -
+ + Format +
Select from the options below to export chats from your timeline

- - Format -
+ + Format + Select from the options below to export chats from your timeline

- - Format -
+ + Format + Date: Wed, 26 Jan 2022 18:17:17 +0100 Subject: [PATCH 02/11] fix exporter tests Signed-off-by: Kerry Archibald --- test/utils/export-test.tsx | 47 ++++++++++++++++++-------------------- 1 file changed, 22 insertions(+), 25 deletions(-) diff --git a/test/utils/export-test.tsx b/test/utils/export-test.tsx index 1ce463fdbd2..74ae8a51c3a 100644 --- a/test/utils/export-test.tsx +++ b/test/utils/export-test.tsx @@ -50,24 +50,6 @@ describe('export', function() { attachmentsIncluded: false, }; - const invalidExportOptions: IExportOptions[] = [ - { - numberOfMessages: 10**9, - maxSize: 1024 * 1024 * 1024, - attachmentsIncluded: false, - }, - { - numberOfMessages: -1, - maxSize: 4096 * 1024 * 1024, - attachmentsIncluded: false, - }, - { - numberOfMessages: 0, - maxSize: 0, - attachmentsIncluded: false, - }, - ]; - function createRoom() { const room = new Room(generateRoomId(), null, client.getUserId()); return room; @@ -212,13 +194,28 @@ describe('export', function() { ).toBeTruthy(); }); - it('checks if the export options are valid', function() { - for (const exportOption of invalidExportOptions) { - expect( - () => - new PlainTextExporter(mockRoom, ExportType.Beginning, exportOption, null), - ).toThrowError("Invalid export options"); - } + const testCases: [string, IExportOptions][] = [ + ['numberOfMessages exceeds max', { + numberOfMessages: 10 ** 9, + maxSize: 1024 * 1024 * 1024, + attachmentsIncluded: false, + }], + ['maxSize exceeds 1000GB', { + numberOfMessages: -1, + maxSize: 1024001 * 1024 * 1024, + attachmentsIncluded: false, + }], + ['maxSize is less than 1mb', { + numberOfMessages: 0, + maxSize: 0, + attachmentsIncluded: false, + }], + ]; + it.each(testCases)('%s', (_d, options) => { + expect( + () => + new PlainTextExporter(mockRoom, ExportType.Beginning, options, null), + ).toThrowError("Invalid export options"); }); it('tests the file extension splitter', function() { From 3146317a2011e1c9ebb1652b4a56148a5af5e3d7 Mon Sep 17 00:00:00 2001 From: Kerry Archibald Date: Wed, 26 Jan 2022 18:18:19 +0100 Subject: [PATCH 03/11] test ExportDialog with settings Signed-off-by: Kerry Archibald --- package.json | 1 + src/components/views/dialogs/ExportDialog.tsx | 8 +- .../views/dialogs/ExportDialog-test.tsx | 153 ++++++++++++++++-- test/utils/export-test.tsx | 4 +- yarn.lock | 104 +++++++++--- 5 files changed, 233 insertions(+), 37 deletions(-) diff --git a/package.json b/package.json index 1ac0c7b28d1..f1e81966d17 100644 --- a/package.json +++ b/package.json @@ -191,6 +191,7 @@ "stylelint": "^13.9.0", "stylelint-config-standard": "^20.0.0", "stylelint-scss": "^3.18.0", + "ts-jest": "^27.1.3", "typescript": "4.5.3", "walk": "^2.3.14" }, diff --git a/src/components/views/dialogs/ExportDialog.tsx b/src/components/views/dialogs/ExportDialog.tsx index a2815758ae6..dde138a7048 100644 --- a/src/components/views/dialogs/ExportDialog.tsx +++ b/src/components/views/dialogs/ExportDialog.tsx @@ -57,10 +57,11 @@ const validateNumberInRange = (min: number, max: number) => (value?: string | nu }; // Sanitize setting values, exclude invalid or missing values -export const getSafeForceRoomExportSettings = (): { +export type ForceRoomExportSettings = { format?: ExportFormat; range?: ExportType; numberOfMessages?: number; includeAttachments?: boolean; sizeMb?: number; -} => { - const config = SettingsStore.getValue(UIFeature.ForceRoomExportSettings); +}; +export const getSafeForceRoomExportSettings = (): ForceRoomExportSettings => { + const config = SettingsStore.getValue(UIFeature.ForceRoomExportSettings); if (!config || typeof config !== "object") return {}; const { format, range, numberOfMessages, includeAttachments, sizeMb } = config; @@ -87,6 +88,7 @@ interface ExportConfig { setNumberOfMessages?: Dispatch>; setSizeLimit?: Dispatch>; } + /** * Set up form state using UIFeature.ForceRoomExportSettings or defaults * Form fields configured in ForceRoomExportSettings are not allowed to be edited diff --git a/test/components/views/dialogs/ExportDialog-test.tsx b/test/components/views/dialogs/ExportDialog-test.tsx index 8d6d36ad08c..c8561d4799b 100644 --- a/test/components/views/dialogs/ExportDialog-test.tsx +++ b/test/components/views/dialogs/ExportDialog-test.tsx @@ -16,22 +16,40 @@ limitations under the License. import React from 'react'; import { mount } from 'enzyme'; +import { mocked } from 'ts-jest/utils'; import '../../../skinned-sdk'; import { act } from "react-dom/test-utils"; import { Room } from 'matrix-js-sdk'; -import ExportDialog from '../../../../src/components/views/dialogs/ExportDialog'; +import ExportDialog, +{ getSafeForceRoomExportSettings, ForceRoomExportSettings } + from '../../../../src/components/views/dialogs/ExportDialog'; import { ExportType, ExportFormat } from '../../../../src/utils/exportUtils/exportUtils'; import { createTestClient, mkStubRoom } from '../../../test-utils'; import { MatrixClientPeg } from '../../../../src/MatrixClientPeg'; import HTMLExporter from "../../../../src/utils/exportUtils/HtmlExport"; +import SettingsStore from '../../../../src/settings/SettingsStore'; +import PlainTextExporter from '../../../../src/utils/exportUtils/PlainTextExport'; jest.useFakeTimers(); -const mockHtmlExporter = ({ +const htmlExporterInstance = ({ + export: jest.fn().mockResolvedValue({}), +}); +const plainTextExporterInstance = ({ export: jest.fn().mockResolvedValue({}), }); jest.mock("../../../../src/utils/exportUtils/HtmlExport", () => jest.fn()); +jest.mock("../../../../src/utils/exportUtils/PlainTextExport", () => jest.fn()); + +jest.mock('../../../../src/settings/SettingsStore', () => ({ + monitorSetting: jest.fn(), + getValue: jest.fn(), +})); + +const SettingsStoreMock = mocked(SettingsStore); +const HTMLExporterMock = mocked(HTMLExporter); +const PlainTextExporterMock = mocked(PlainTextExporter); describe('', () => { const mockClient = createTestClient(); @@ -81,8 +99,13 @@ describe('', () => { }); beforeEach(() => { - (HTMLExporter as jest.Mock).mockImplementation(jest.fn().mockReturnValue(mockHtmlExporter)); - mockHtmlExporter.export.mockClear(); + HTMLExporterMock.mockClear().mockImplementation(jest.fn().mockReturnValue(htmlExporterInstance)); + PlainTextExporterMock.mockClear().mockImplementation(jest.fn().mockReturnValue(plainTextExporterInstance)); + htmlExporterInstance.export.mockClear(); + plainTextExporterInstance.export.mockClear(); + + // default setting value + SettingsStoreMock.getValue.mockClear().mockReturnValue({}); }); it('renders export dialog', () => { @@ -104,7 +127,7 @@ describe('', () => { await submitForm(component); // 4th arg is an component function - const exportConstructorProps = (HTMLExporter as jest.Mock).mock.calls[0].slice(0, 3); + const exportConstructorProps = HTMLExporterMock.mock.calls[0].slice(0, 3); expect(exportConstructorProps).toEqual([ defaultProps.room, ExportType.Timeline, @@ -114,7 +137,32 @@ describe('', () => { numberOfMessages: 100, }, ]); - expect(mockHtmlExporter.export).toHaveBeenCalled(); + expect(htmlExporterInstance.export).toHaveBeenCalled(); + }); + + it('exports room using values set from ForceRoomExportSettings', async () => { + SettingsStoreMock.getValue.mockReturnValue({ + format: ExportFormat.PlainText, + range: ExportType.Beginning, + sizeMb: 15000, + numberOfMessages: 30, + attachmentsIncluded: true, + }); + const component = getComponent(); + await submitForm(component); + + // 4th arg is an component function + const exportConstructorProps = PlainTextExporterMock.mock.calls[0].slice(0, 3); + expect(exportConstructorProps).toEqual([ + defaultProps.room, + ExportType.Beginning, + { + attachmentsIncluded: false, + maxSize: 15000 * 1024 * 1024, + numberOfMessages: 30, + }, + ]); + expect(plainTextExporterInstance.export).toHaveBeenCalled(); }); it('renders success screen when export is finished', async () => { @@ -139,6 +187,19 @@ describe('', () => { expect(getExportFormatInput(component, ExportFormat.PlainText).props().checked).toBeTruthy(); expect(getExportFormatInput(component, ExportFormat.Html).props().checked).toBeFalsy(); }); + + it('hides export format input when format is valid in ForceRoomExportSettings', () => { + const component = getComponent(); + expect(getExportFormatInput(component, ExportFormat.Html).props().checked).toBeTruthy(); + }); + + it('does not render export format when set in ForceRoomExportSettings', () => { + SettingsStoreMock.getValue.mockReturnValue({ + format: ExportFormat.PlainText, + }); + const component = getComponent(); + expect(getExportFormatInput(component, ExportFormat.Html).length).toBeFalsy(); + }); }); describe('export type', () => { @@ -153,6 +214,14 @@ describe('', () => { expect(getExportTypeInput(component).props().value).toEqual(ExportType.Beginning); }); + it('does not render export type when set in ForceRoomExportSettings', () => { + SettingsStoreMock.getValue.mockReturnValue({ + range: ExportType.Beginning, + }); + const component = getComponent(); + expect(getExportTypeInput(component).length).toBeFalsy(); + }); + it('does not render message count input', async () => { const component = getComponent(); expect(getMessageCountInput(component).length).toBeFalsy(); @@ -177,7 +246,7 @@ describe('', () => { await setMessageCount(component, 0); await submitForm(component); - expect(mockHtmlExporter.export).not.toHaveBeenCalled(); + expect(htmlExporterInstance.export).not.toHaveBeenCalled(); }); it('does not export when export type is lastNMessages and message count is more than max', async () => { @@ -186,7 +255,7 @@ describe('', () => { await setMessageCount(component, 99999999999); await submitForm(component); - expect(mockHtmlExporter.export).not.toHaveBeenCalled(); + expect(htmlExporterInstance.export).not.toHaveBeenCalled(); }); it('exports when export type is NOT lastNMessages and message count is falsy', async () => { @@ -196,7 +265,7 @@ describe('', () => { await selectExportType(component, ExportType.Timeline); await submitForm(component); - expect(mockHtmlExporter.export).toHaveBeenCalled(); + expect(htmlExporterInstance.export).toHaveBeenCalled(); }); }); @@ -217,7 +286,7 @@ describe('', () => { await setSizeLimit(component, 0); await submitForm(component); - expect(mockHtmlExporter.export).not.toHaveBeenCalled(); + expect(htmlExporterInstance.export).not.toHaveBeenCalled(); }); it('does not export when size limit is larger than max', async () => { @@ -225,7 +294,7 @@ describe('', () => { await setSizeLimit(component, 2001); await submitForm(component); - expect(mockHtmlExporter.export).not.toHaveBeenCalled(); + expect(htmlExporterInstance.export).not.toHaveBeenCalled(); }); it('exports when size limit is max', async () => { @@ -233,11 +302,32 @@ describe('', () => { await setSizeLimit(component, 2000); await submitForm(component); - expect(mockHtmlExporter.export).toHaveBeenCalled(); + expect(htmlExporterInstance.export).toHaveBeenCalled(); + }); + + it('does not render size limit input when set in ForceRoomExportSettings', () => { + SettingsStoreMock.getValue.mockReturnValue({ + sizeMb: 10000, + }); + const component = getComponent(); + expect(getSizeInput(component).length).toBeFalsy(); + }); + + /** + * 2000mb size limit does not apply when higher limit is configured in config + */ + it('exports when size limit set in ForceRoomExportSettings is larger than 2000', async () => { + SettingsStoreMock.getValue.mockReturnValue({ + sizeMb: 10000, + }); + const component = getComponent(); + await submitForm(component); + + expect(htmlExporterInstance.export).toHaveBeenCalled(); }); }); - describe('include attachements', () => { + describe('include attachments', () => { it('renders input with default value of false', () => { const component = getComponent(); expect(getAttachmentsCheckbox(component).props().checked).toEqual(false); @@ -248,6 +338,43 @@ describe('', () => { await setIncludeAttachments(component, true); expect(getAttachmentsCheckbox(component).props().checked).toEqual(true); }); + + it('does not render input when set in ForceRoomExportSettings', () => { + SettingsStoreMock.getValue.mockReturnValue({ + includeAttachments: false, + }); + const component = getComponent(); + expect(getAttachmentsCheckbox(component).length).toBeFalsy(); + }); + }); + + describe('getSafeForceRoomExportSettings()', () => { + const testCases: [string, ForceRoomExportSettings, ForceRoomExportSettings][] = [ + ['setting is falsy', undefined, {}], + ['setting is configured to string', 'test' as unknown, {}], + ['setting is empty', {}, {}], + ['format is not a valid ExportFormat', { format: 'mp3' }, {}], + ['format is a valid ExportFormat', { format: ExportFormat.Html }, { format: ExportFormat.Html }], + ['range is not a valid ExportType', { range: 'yesterday' }, {}], + ['range is a valid ExportType', { range: ExportType.LastNMessages }, { range: ExportType.LastNMessages }], + ['numberOfMessages is not a number', { numberOfMessages: 'test' }, {}], + ['numberOfMessages is less than 1', { numberOfMessages: -1 }, {}], + ['numberOfMessages is more than 100000000', { numberOfMessages: 9999999999 }, {}], + ['numberOfMessages is valid', { numberOfMessages: 2000 }, { numberOfMessages: 2000 }], + ['sizeMb is not a number', { sizeMb: 'test' }, {}], + ['sizeMb is less than 1', { sizeMb: -1 }, {}], + ['sizeMb is more than 1024000', { sizeMb: Number.MAX_SAFE_INTEGER }, {}], + ['sizeMb is valid', { sizeMb: 50000 }, { sizeMb: 50000 }], + ['includeAttachments is not a boolean', { includeAttachments: 'yes' }, {}], + ['includeAttachments is true', { includeAttachments: true }, { includeAttachments: true }], + ['includeAttachments is false', { includeAttachments: false }, { includeAttachments: false }], + ]; + + it.each(testCases)('sanitizes correctly when %s', (_d, setting, expected) => { + SettingsStoreMock.getValue.mockReturnValue(setting); + + expect(getSafeForceRoomExportSettings()).toEqual(expected); + }); }); }); diff --git a/test/utils/export-test.tsx b/test/utils/export-test.tsx index 74ae8a51c3a..749f062b46e 100644 --- a/test/utils/export-test.tsx +++ b/test/utils/export-test.tsx @@ -194,7 +194,7 @@ describe('export', function() { ).toBeTruthy(); }); - const testCases: [string, IExportOptions][] = [ + const invalidExportOptions: [string, IExportOptions][] = [ ['numberOfMessages exceeds max', { numberOfMessages: 10 ** 9, maxSize: 1024 * 1024 * 1024, @@ -211,7 +211,7 @@ describe('export', function() { attachmentsIncluded: false, }], ]; - it.each(testCases)('%s', (_d, options) => { + it.each(invalidExportOptions)('%s', (_d, options) => { expect( () => new PlainTextExporter(mockRoom, ExportType.Beginning, options, null), diff --git a/yarn.lock b/yarn.lock index 1e91b21ada5..5d3b5f2ab56 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1334,6 +1334,17 @@ "@types/yargs" "^15.0.0" chalk "^4.0.0" +"@jest/types@^27.4.2": + version "27.4.2" + resolved "https://registry.yarnpkg.com/@jest/types/-/types-27.4.2.tgz#96536ebd34da6392c2b7c7737d693885b5dd44a5" + integrity sha512-j35yw0PMTPpZsUoOBiuHzr1zTYoad1cVIE0ajEjcrJONxxrko/IRGKkXx3os0Nsi4Hu3+5VmDbVfq5WhG/pWAg== + dependencies: + "@types/istanbul-lib-coverage" "^2.0.0" + "@types/istanbul-reports" "^3.0.0" + "@types/node" "*" + "@types/yargs" "^16.0.0" + chalk "^4.0.0" + "@mapbox/geojson-rewind@^0.5.0": version "0.5.1" resolved "https://registry.yarnpkg.com/@mapbox/geojson-rewind/-/geojson-rewind-0.5.1.tgz#adbe16dc683eb40e90934c51a5e28c7bbf44f4e1" @@ -1983,6 +1994,13 @@ dependencies: "@types/yargs-parser" "*" +"@types/yargs@^16.0.0": + version "16.0.4" + resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-16.0.4.tgz#26aad98dd2c2a38e421086ea9ad42b9e51642977" + integrity sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw== + dependencies: + "@types/yargs-parser" "*" + "@types/zxcvbn@^4.4.0": version "4.4.1" resolved "https://registry.yarnpkg.com/@types/zxcvbn/-/zxcvbn-4.4.1.tgz#46e42cbdcee681b22181478feaf4af2bc4c1abd2" @@ -2634,6 +2652,13 @@ browserslist@^4.12.0, browserslist@^4.17.5, browserslist@^4.19.1: node-releases "^2.0.1" picocolors "^1.0.0" +bs-logger@0.x: + version "0.2.6" + resolved "https://registry.yarnpkg.com/bs-logger/-/bs-logger-0.2.6.tgz#eb7d365307a72cf974cc6cda76b68354ad336bd8" + integrity sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog== + dependencies: + fast-json-stable-stringify "2.x" + bs58@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/bs58/-/bs58-4.0.1.tgz#be161e76c354f6f788ae4071f63f34e8c4f0a42a" @@ -2832,6 +2857,11 @@ ci-info@^2.0.0: resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-2.0.0.tgz#67a9e964be31a51e15e5010d58e6f12834002f46" integrity sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ== +ci-info@^3.2.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.3.0.tgz#b4ed1fb6818dea4803a55c623041f9165d2066b2" + integrity sha512-riT/3vI5YpVH6/qomlDnJow6TBee2PBKSEpx3O32EGPYbWGIRsIlGRms3Sm74wYE1JMo8RnO04Hb12+v1J5ICw== + cjs-module-lexer@^0.6.0: version "0.6.0" resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-0.6.0.tgz#4186fcca0eae175970aee870b9fe2d6cf8d5655f" @@ -4079,7 +4109,7 @@ fast-glob@^3.2.5, fast-glob@^3.2.9: merge2 "^1.3.0" micromatch "^4.0.4" -fast-json-stable-stringify@^2.0.0: +fast-json-stable-stringify@2.x, fast-json-stable-stringify@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== @@ -5697,6 +5727,18 @@ jest-util@^26.6.2: is-ci "^2.0.0" micromatch "^4.0.2" +jest-util@^27.0.0: + version "27.4.2" + resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-27.4.2.tgz#ed95b05b1adfd761e2cda47e0144c6a58e05a621" + integrity sha512-YuxxpXU6nlMan9qyLuxHaMMOzXAl5aGZWCSzben5DhLHemYQxCc4YK+4L3ZrCutT8GPQ+ui9k5D8rUJoDioMnA== + dependencies: + "@jest/types" "^27.4.2" + "@types/node" "*" + chalk "^4.0.0" + ci-info "^3.2.0" + graceful-fs "^4.2.4" + picomatch "^2.2.3" + jest-validate@^26.6.2: version "26.6.2" resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-26.6.2.tgz#23d380971587150467342911c3d7b4ac57ab20ec" @@ -5843,6 +5885,13 @@ json-stringify-safe@~5.0.1: resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" integrity sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus= +json5@2.x, json5@^2.1.2: + version "2.2.0" + resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.0.tgz#2dfefe720c6ba525d9ebd909950f0515316c89a3" + integrity sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA== + dependencies: + minimist "^1.2.5" + json5@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.1.tgz#779fb0018604fa854eacbf6252180d83543e3dbe" @@ -5850,13 +5899,6 @@ json5@^1.0.1: dependencies: minimist "^1.2.0" -json5@^2.1.2: - version "2.2.0" - resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.0.tgz#2dfefe720c6ba525d9ebd909950f0515316c89a3" - integrity sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA== - dependencies: - minimist "^1.2.5" - jsprim@^1.2.2: version "1.4.2" resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.2.tgz#712c65533a15c878ba59e9ed5f0e26d5b77c5feb" @@ -6028,6 +6070,11 @@ lodash.isequal@^4.5.0: resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0" integrity sha1-QVxEePK8wwEgwizhDtMib30+GOA= +lodash.memoize@4.x: + version "4.1.2" + resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" + integrity sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4= + lodash.truncate@^4.4.2: version "4.4.2" resolved "https://registry.yarnpkg.com/lodash.truncate/-/lodash.truncate-4.4.2.tgz#5a350da0b1113b837ecfffd5812cbe58d6eae193" @@ -6099,6 +6146,11 @@ make-dir@^3.0.0: dependencies: semver "^6.0.0" +make-error@1.x: + version "1.3.6" + resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" + integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== + makeerror@1.0.12: version "1.0.12" resolved "https://registry.yarnpkg.com/makeerror/-/makeerror-1.0.12.tgz#3e5dd2079a82e812e983cc6610c4a2cb0eaa801a" @@ -7787,18 +7839,18 @@ semver@7.0.0: resolved "https://registry.yarnpkg.com/semver/-/semver-7.0.0.tgz#5f3ca35761e47e05b206c6daff2cf814f0316b8e" integrity sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A== -semver@^6.0.0, semver@^6.1.1, semver@^6.1.2, semver@^6.3.0: - version "6.3.0" - resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" - integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== - -semver@^7.2.1, semver@^7.3.2, semver@^7.3.4, semver@^7.3.5: +semver@7.x, semver@^7.2.1, semver@^7.3.2, semver@^7.3.4, semver@^7.3.5: version "7.3.5" resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.5.tgz#0b621c879348d8998e4b0e4be94b3f12e6018ef7" integrity sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ== dependencies: lru-cache "^6.0.0" +semver@^6.0.0, semver@^6.1.1, semver@^6.1.2, semver@^6.3.0: + version "6.3.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" + integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== + set-blocking@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" @@ -8484,6 +8536,20 @@ trough@^1.0.0: resolved "https://registry.yarnpkg.com/trough/-/trough-1.0.5.tgz#b8b639cefad7d0bb2abd37d433ff8293efa5f406" integrity sha512-rvuRbTarPXmMb79SmzEp8aqXNKcK+y0XaB298IXueQ8I2PsrATcPBCSPyK/dDNa2iWOhKlfNnOjdAOTBU/nkFA== +ts-jest@^27.1.3: + version "27.1.3" + resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-27.1.3.tgz#1f723e7e74027c4da92c0ffbd73287e8af2b2957" + integrity sha512-6Nlura7s6uM9BVUAoqLH7JHyMXjz8gluryjpPXxr3IxZdAXnU6FhjvVLHFtfd1vsE1p8zD1OJfskkc0jhTSnkA== + dependencies: + bs-logger "0.x" + fast-json-stable-stringify "2.x" + jest-util "^27.0.0" + json5 "2.x" + lodash.memoize "4.x" + make-error "1.x" + semver "7.x" + yargs-parser "20.x" + tsconfig-paths@^3.12.0: version "3.12.0" resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.12.0.tgz#19769aca6ee8f6a1a341e38c8fa45dd9fb18899b" @@ -9057,6 +9123,11 @@ yaml@^1.10.0: resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b" integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg== +yargs-parser@20.x, yargs-parser@^20.2.3: + version "20.2.9" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee" + integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w== + yargs-parser@^13.1.2: version "13.1.2" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-13.1.2.tgz#130f09702ebaeef2650d54ce6e3e5706f7a4fb38" @@ -9073,11 +9144,6 @@ yargs-parser@^18.1.2: camelcase "^5.0.0" decamelize "^1.2.0" -yargs-parser@^20.2.3: - version "20.2.9" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee" - integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w== - yargs-parser@^21.0.0: version "21.0.0" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.0.0.tgz#a485d3966be4317426dd56bdb6a30131b281dc55" From 63f3559640dbc4b111acadc4a00c3df37631ba3c Mon Sep 17 00:00:00 2001 From: Kerry Archibald Date: Thu, 27 Jan 2022 10:02:40 +0100 Subject: [PATCH 04/11] tidy debugs, rename setting to Parameters Signed-off-by: Kerry Archibald --- src/components/views/dialogs/ExportDialog.tsx | 12 +++++----- src/settings/Settings.tsx | 2 +- src/settings/UIFeature.ts | 2 +- .../views/dialogs/ExportDialog-test.tsx | 22 +++++++++---------- 4 files changed, 19 insertions(+), 19 deletions(-) diff --git a/src/components/views/dialogs/ExportDialog.tsx b/src/components/views/dialogs/ExportDialog.tsx index dde138a7048..44cc9b79154 100644 --- a/src/components/views/dialogs/ExportDialog.tsx +++ b/src/components/views/dialogs/ExportDialog.tsx @@ -57,11 +57,11 @@ const validateNumberInRange = (min: number, max: number) => (value?: string | nu }; // Sanitize setting values, exclude invalid or missing values -export type ForceRoomExportSettings = { +export type ForceRoomExportParameters = { format?: ExportFormat; range?: ExportType; numberOfMessages?: number; includeAttachments?: boolean; sizeMb?: number; }; -export const getSafeForceRoomExportSettings = (): ForceRoomExportSettings => { - const config = SettingsStore.getValue(UIFeature.ForceRoomExportSettings); +export const getSafeForceRoomExportParameters = (): ForceRoomExportParameters => { + const config = SettingsStore.getValue(UIFeature.ForceRoomExportParameters); if (!config || typeof config !== "object") return {}; const { format, range, numberOfMessages, includeAttachments, sizeMb } = config; @@ -90,12 +90,12 @@ interface ExportConfig { } /** - * Set up form state using UIFeature.ForceRoomExportSettings or defaults - * Form fields configured in ForceRoomExportSettings are not allowed to be edited + * Set up form state using UIFeature.ForceRoomExportParameters or defaults + * Form fields configured in ForceRoomExportParameters are not allowed to be edited * Only return change handlers for editable values */ const useExportFormState = (): ExportConfig => { - const config = getSafeForceRoomExportSettings(); + const config = getSafeForceRoomExportParameters(); const [exportFormat, setExportFormat] = useState(config.format || ExportFormat.Html); const [exportType, setExportType] = useState(config.range || ExportType.Timeline); diff --git a/src/settings/Settings.tsx b/src/settings/Settings.tsx index 432e3aa4747..5edad814cf3 100644 --- a/src/settings/Settings.tsx +++ b/src/settings/Settings.tsx @@ -956,7 +956,7 @@ export const SETTINGS: {[setting: string]: ISetting} = { supportedLevels: LEVELS_UI_FEATURE, default: true, }, - [UIFeature.ForceRoomExportSettings]: { + [UIFeature.ForceRoomExportParameters]: { supportedLevels: LEVELS_UI_FEATURE, default: {}, }, diff --git a/src/settings/UIFeature.ts b/src/settings/UIFeature.ts index 41581d517b9..4e32c0413c4 100644 --- a/src/settings/UIFeature.ts +++ b/src/settings/UIFeature.ts @@ -33,7 +33,7 @@ export enum UIFeature { AdvancedSettings = "UIFeature.advancedSettings", RoomHistorySettings = "UIFeature.roomHistorySettings", TimelineEnableRelativeDates = "UIFeature.timelineEnableRelativeDates", - ForceRoomExportSettings = "UIFeature.forceRoomExportSettings" + ForceRoomExportParameters = "UIFeature.ForceRoomExportParameters" } export enum UIComponent { diff --git a/test/components/views/dialogs/ExportDialog-test.tsx b/test/components/views/dialogs/ExportDialog-test.tsx index c8561d4799b..3db56a7eebc 100644 --- a/test/components/views/dialogs/ExportDialog-test.tsx +++ b/test/components/views/dialogs/ExportDialog-test.tsx @@ -22,7 +22,7 @@ import { act } from "react-dom/test-utils"; import { Room } from 'matrix-js-sdk'; import ExportDialog, -{ getSafeForceRoomExportSettings, ForceRoomExportSettings } +{ getSafeForceRoomExportParameters, ForceRoomExportParameters } from '../../../../src/components/views/dialogs/ExportDialog'; import { ExportType, ExportFormat } from '../../../../src/utils/exportUtils/exportUtils'; import { createTestClient, mkStubRoom } from '../../../test-utils'; @@ -140,7 +140,7 @@ describe('', () => { expect(htmlExporterInstance.export).toHaveBeenCalled(); }); - it('exports room using values set from ForceRoomExportSettings', async () => { + it('exports room using values set from ForceRoomExportParameters', async () => { SettingsStoreMock.getValue.mockReturnValue({ format: ExportFormat.PlainText, range: ExportType.Beginning, @@ -188,12 +188,12 @@ describe('', () => { expect(getExportFormatInput(component, ExportFormat.Html).props().checked).toBeFalsy(); }); - it('hides export format input when format is valid in ForceRoomExportSettings', () => { + it('hides export format input when format is valid in ForceRoomExportParameters', () => { const component = getComponent(); expect(getExportFormatInput(component, ExportFormat.Html).props().checked).toBeTruthy(); }); - it('does not render export format when set in ForceRoomExportSettings', () => { + it('does not render export format when set in ForceRoomExportParameters', () => { SettingsStoreMock.getValue.mockReturnValue({ format: ExportFormat.PlainText, }); @@ -214,7 +214,7 @@ describe('', () => { expect(getExportTypeInput(component).props().value).toEqual(ExportType.Beginning); }); - it('does not render export type when set in ForceRoomExportSettings', () => { + it('does not render export type when set in ForceRoomExportParameters', () => { SettingsStoreMock.getValue.mockReturnValue({ range: ExportType.Beginning, }); @@ -305,7 +305,7 @@ describe('', () => { expect(htmlExporterInstance.export).toHaveBeenCalled(); }); - it('does not render size limit input when set in ForceRoomExportSettings', () => { + it('does not render size limit input when set in ForceRoomExportParameters', () => { SettingsStoreMock.getValue.mockReturnValue({ sizeMb: 10000, }); @@ -316,7 +316,7 @@ describe('', () => { /** * 2000mb size limit does not apply when higher limit is configured in config */ - it('exports when size limit set in ForceRoomExportSettings is larger than 2000', async () => { + it('exports when size limit set in ForceRoomExportParameters is larger than 2000', async () => { SettingsStoreMock.getValue.mockReturnValue({ sizeMb: 10000, }); @@ -339,7 +339,7 @@ describe('', () => { expect(getAttachmentsCheckbox(component).props().checked).toEqual(true); }); - it('does not render input when set in ForceRoomExportSettings', () => { + it('does not render input when set in ForceRoomExportParameters', () => { SettingsStoreMock.getValue.mockReturnValue({ includeAttachments: false, }); @@ -348,8 +348,8 @@ describe('', () => { }); }); - describe('getSafeForceRoomExportSettings()', () => { - const testCases: [string, ForceRoomExportSettings, ForceRoomExportSettings][] = [ + describe('getSafeForceRoomExportParameters()', () => { + const testCases: [string, ForceRoomExportParameters, ForceRoomExportParameters][] = [ ['setting is falsy', undefined, {}], ['setting is configured to string', 'test' as unknown, {}], ['setting is empty', {}, {}], @@ -373,7 +373,7 @@ describe('', () => { it.each(testCases)('sanitizes correctly when %s', (_d, setting, expected) => { SettingsStoreMock.getValue.mockReturnValue(setting); - expect(getSafeForceRoomExportSettings()).toEqual(expected); + expect(getSafeForceRoomExportParameters()).toEqual(expected); }); }); }); From 74ccf3d47ae8936bd45b3774ad6b1cf48f7d63fb Mon Sep 17 00:00:00 2001 From: Kerry Archibald Date: Thu, 27 Jan 2022 10:09:57 +0100 Subject: [PATCH 05/11] use reasonable 100gb limit Signed-off-by: Kerry Archibald --- src/components/views/dialogs/ExportDialog.tsx | 4 ++-- src/utils/exportUtils/Exporter.ts | 2 +- test/components/views/dialogs/ExportDialog-test.tsx | 2 +- test/utils/export-test.tsx | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/components/views/dialogs/ExportDialog.tsx b/src/components/views/dialogs/ExportDialog.tsx index 44cc9b79154..a287df486d3 100644 --- a/src/components/views/dialogs/ExportDialog.tsx +++ b/src/components/views/dialogs/ExportDialog.tsx @@ -70,8 +70,8 @@ export const getSafeForceRoomExportParameters = (): ForceRoomExportParameters => ...(isExportFormat(format) && { format }), ...(isExportType(range) && { range }), ...(validateNumberInRange(1, 10 ** 8)(numberOfMessages) && { numberOfMessages }), - // 100GB limit - ...(validateNumberInRange(1, 1024000)(sizeMb) && { sizeMb }), + // ~100GB limit + ...(validateNumberInRange(1, 100000)(sizeMb) && { sizeMb }), ...(typeof includeAttachments === 'boolean' && { includeAttachments }), }; }; diff --git a/src/utils/exportUtils/Exporter.ts b/src/utils/exportUtils/Exporter.ts index 70826cbfcef..b55cb6d1bad 100644 --- a/src/utils/exportUtils/Exporter.ts +++ b/src/utils/exportUtils/Exporter.ts @@ -48,7 +48,7 @@ export default abstract class Exporter { protected setProgressText: React.Dispatch>, ) { if (exportOptions.maxSize < 1 * 1024 * 1024|| // Less than 1 MB - exportOptions.maxSize > 1024000 * 1024 * 1024 || // More than 1000 GB + exportOptions.maxSize > 100000 * 1024 * 1024 || // More than 100 GB exportOptions.numberOfMessages > 10**8 ) { throw new Error("Invalid export options"); diff --git a/test/components/views/dialogs/ExportDialog-test.tsx b/test/components/views/dialogs/ExportDialog-test.tsx index 3db56a7eebc..a69190f5047 100644 --- a/test/components/views/dialogs/ExportDialog-test.tsx +++ b/test/components/views/dialogs/ExportDialog-test.tsx @@ -363,7 +363,7 @@ describe('', () => { ['numberOfMessages is valid', { numberOfMessages: 2000 }, { numberOfMessages: 2000 }], ['sizeMb is not a number', { sizeMb: 'test' }, {}], ['sizeMb is less than 1', { sizeMb: -1 }, {}], - ['sizeMb is more than 1024000', { sizeMb: Number.MAX_SAFE_INTEGER }, {}], + ['sizeMb is more than 100000', { sizeMb: Number.MAX_SAFE_INTEGER }, {}], ['sizeMb is valid', { sizeMb: 50000 }, { sizeMb: 50000 }], ['includeAttachments is not a boolean', { includeAttachments: 'yes' }, {}], ['includeAttachments is true', { includeAttachments: true }, { includeAttachments: true }], diff --git a/test/utils/export-test.tsx b/test/utils/export-test.tsx index 749f062b46e..e0232c775d4 100644 --- a/test/utils/export-test.tsx +++ b/test/utils/export-test.tsx @@ -200,9 +200,9 @@ describe('export', function() { maxSize: 1024 * 1024 * 1024, attachmentsIncluded: false, }], - ['maxSize exceeds 1000GB', { + ['maxSize exceeds 100GB', { numberOfMessages: -1, - maxSize: 1024001 * 1024 * 1024, + maxSize: 100001 * 1024 * 1024, attachmentsIncluded: false, }], ['maxSize is less than 1mb', { From 699830211c45ba16779c28a5a88f689096d629d7 Mon Sep 17 00:00:00 2001 From: Kerry Archibald Date: Thu, 27 Jan 2022 14:35:33 +0100 Subject: [PATCH 06/11] use normal setting instead of UIFeature Signed-off-by: Kerry Archibald --- src/components/views/dialogs/ExportDialog.tsx | 5 ++--- src/settings/Settings.tsx | 8 ++++---- src/settings/UIFeature.ts | 1 - 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/src/components/views/dialogs/ExportDialog.tsx b/src/components/views/dialogs/ExportDialog.tsx index a287df486d3..3ce314fb07b 100644 --- a/src/components/views/dialogs/ExportDialog.tsx +++ b/src/components/views/dialogs/ExportDialog.tsx @@ -40,7 +40,6 @@ import Exporter from "../../../utils/exportUtils/Exporter"; import Spinner from "../elements/Spinner"; import InfoDialog from "./InfoDialog"; import SettingsStore from "../../../settings/SettingsStore"; -import { UIFeature } from "../../../settings/UIFeature"; interface IProps extends IDialogProps { room: Room; @@ -61,7 +60,7 @@ export type ForceRoomExportParameters = { format?: ExportFormat; range?: ExportType; numberOfMessages?: number; includeAttachments?: boolean; sizeMb?: number; }; export const getSafeForceRoomExportParameters = (): ForceRoomExportParameters => { - const config = SettingsStore.getValue(UIFeature.ForceRoomExportParameters); + const config = SettingsStore.getValue("forceRoomExportParameters"); if (!config || typeof config !== "object") return {}; const { format, range, numberOfMessages, includeAttachments, sizeMb } = config; @@ -90,7 +89,7 @@ interface ExportConfig { } /** - * Set up form state using UIFeature.ForceRoomExportParameters or defaults + * Set up form state using "forceRoomExportParameters" or defaults * Form fields configured in ForceRoomExportParameters are not allowed to be edited * Only return change handlers for editable values */ diff --git a/src/settings/Settings.tsx b/src/settings/Settings.tsx index 5edad814cf3..56499568df0 100644 --- a/src/settings/Settings.tsx +++ b/src/settings/Settings.tsx @@ -883,6 +883,10 @@ export const SETTINGS: {[setting: string]: ISetting} = { default: false, controller: new ReloadOnChangeController(), }, + "forceRoomExportParameters": { + supportedLevels: LEVELS_UI_FEATURE, + default: {}, + }, [UIFeature.RoomHistorySettings]: { supportedLevels: LEVELS_UI_FEATURE, default: true, @@ -956,8 +960,4 @@ export const SETTINGS: {[setting: string]: ISetting} = { supportedLevels: LEVELS_UI_FEATURE, default: true, }, - [UIFeature.ForceRoomExportParameters]: { - supportedLevels: LEVELS_UI_FEATURE, - default: {}, - }, }; diff --git a/src/settings/UIFeature.ts b/src/settings/UIFeature.ts index 4e32c0413c4..f0339942143 100644 --- a/src/settings/UIFeature.ts +++ b/src/settings/UIFeature.ts @@ -33,7 +33,6 @@ export enum UIFeature { AdvancedSettings = "UIFeature.advancedSettings", RoomHistorySettings = "UIFeature.roomHistorySettings", TimelineEnableRelativeDates = "UIFeature.timelineEnableRelativeDates", - ForceRoomExportParameters = "UIFeature.ForceRoomExportParameters" } export enum UIComponent { From 63d97853f6f4b9ec9fb1c4cec593ccd8d664297d Mon Sep 17 00:00:00 2001 From: Kerry Archibald Date: Thu, 27 Jan 2022 17:52:12 +0100 Subject: [PATCH 07/11] use a customisation Signed-off-by: Kerry Archibald --- src/components/views/dialogs/ExportDialog.tsx | 29 +-------- src/customisations/ChatExport.ts | 49 +++++++++++++++ src/settings/Settings.tsx | 4 -- .../views/dialogs/ExportDialog-test.tsx | 60 +++++-------------- 4 files changed, 65 insertions(+), 77 deletions(-) create mode 100644 src/customisations/ChatExport.ts diff --git a/src/components/views/dialogs/ExportDialog.tsx b/src/components/views/dialogs/ExportDialog.tsx index 3ce314fb07b..83a871e8dad 100644 --- a/src/components/views/dialogs/ExportDialog.tsx +++ b/src/components/views/dialogs/ExportDialog.tsx @@ -39,42 +39,17 @@ import { useStateCallback } from "../../../hooks/useStateCallback"; import Exporter from "../../../utils/exportUtils/Exporter"; import Spinner from "../elements/Spinner"; import InfoDialog from "./InfoDialog"; -import SettingsStore from "../../../settings/SettingsStore"; +import ChatExport from "../../../customisations/ChatExport"; interface IProps extends IDialogProps { room: Room; } -const isExportFormat = (config?: string): config is ExportFormat => - config && Object.values(ExportFormat).includes(config as ExportFormat); - -const isExportType = (config?: string): config is ExportType => - config && Object.values(ExportType).includes(config as ExportType); const validateNumberInRange = (min: number, max: number) => (value?: string | number) => { const parsedSize = parseInt(value as string, 10); return !(isNaN(parsedSize) || min > parsedSize || parsedSize > max); }; -// Sanitize setting values, exclude invalid or missing values -export type ForceRoomExportParameters = { - format?: ExportFormat; range?: ExportType; numberOfMessages?: number; includeAttachments?: boolean; sizeMb?: number; -}; -export const getSafeForceRoomExportParameters = (): ForceRoomExportParameters => { - const config = SettingsStore.getValue("forceRoomExportParameters"); - if (!config || typeof config !== "object") return {}; - - const { format, range, numberOfMessages, includeAttachments, sizeMb } = config; - - return { - ...(isExportFormat(format) && { format }), - ...(isExportType(range) && { range }), - ...(validateNumberInRange(1, 10 ** 8)(numberOfMessages) && { numberOfMessages }), - // ~100GB limit - ...(validateNumberInRange(1, 100000)(sizeMb) && { sizeMb }), - ...(typeof includeAttachments === 'boolean' && { includeAttachments }), - }; -}; - interface ExportConfig { exportFormat: ExportFormat; exportType: ExportType; @@ -94,7 +69,7 @@ interface ExportConfig { * Only return change handlers for editable values */ const useExportFormState = (): ExportConfig => { - const config = getSafeForceRoomExportParameters(); + const config = ChatExport.getForceChatExportParameters(); const [exportFormat, setExportFormat] = useState(config.format || ExportFormat.Html); const [exportType, setExportType] = useState(config.range || ExportType.Timeline); diff --git a/src/customisations/ChatExport.ts b/src/customisations/ChatExport.ts new file mode 100644 index 00000000000..6c69e55154f --- /dev/null +++ b/src/customisations/ChatExport.ts @@ -0,0 +1,49 @@ +/* +Copyright 2022 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import { ExportFormat, ExportType } from "../utils/exportUtils/exportUtils"; + +export type ForceChatExportParameters = { + format?: ExportFormat; + range?: ExportType; + // must be < 10**8 + numberOfMessages?: number; + includeAttachments?: boolean; + // must be > 0 and < 100000 + sizeMb?: number; +}; + +/** + * Force parameters in room chat export + * fields returned here are forced + * and not allowed to be edited in the chat export form + */ +const getForceChatExportParameters = (): ForceChatExportParameters => { + return {}; +}; + +// This interface summarises all available customisation points and also marks +// them all as optional. This allows customisers to only define and export the +// customisations they need while still maintaining type safety. +export interface IChatExportCustomisations { + getForceChatExportParameters?: typeof getForceChatExportParameters; +} + +// A real customisation module will define and export one or more of the +// customisation points that make up `IChatExportCustomisations`. +export default { + getForceChatExportParameters, +} as IChatExportCustomisations; diff --git a/src/settings/Settings.tsx b/src/settings/Settings.tsx index 56499568df0..cf4e7d2b43f 100644 --- a/src/settings/Settings.tsx +++ b/src/settings/Settings.tsx @@ -883,10 +883,6 @@ export const SETTINGS: {[setting: string]: ISetting} = { default: false, controller: new ReloadOnChangeController(), }, - "forceRoomExportParameters": { - supportedLevels: LEVELS_UI_FEATURE, - default: {}, - }, [UIFeature.RoomHistorySettings]: { supportedLevels: LEVELS_UI_FEATURE, default: true, diff --git a/test/components/views/dialogs/ExportDialog-test.tsx b/test/components/views/dialogs/ExportDialog-test.tsx index a69190f5047..db87a1c1c67 100644 --- a/test/components/views/dialogs/ExportDialog-test.tsx +++ b/test/components/views/dialogs/ExportDialog-test.tsx @@ -21,14 +21,12 @@ import '../../../skinned-sdk'; import { act } from "react-dom/test-utils"; import { Room } from 'matrix-js-sdk'; -import ExportDialog, -{ getSafeForceRoomExportParameters, ForceRoomExportParameters } - from '../../../../src/components/views/dialogs/ExportDialog'; +import ExportDialog from '../../../../src/components/views/dialogs/ExportDialog'; import { ExportType, ExportFormat } from '../../../../src/utils/exportUtils/exportUtils'; import { createTestClient, mkStubRoom } from '../../../test-utils'; import { MatrixClientPeg } from '../../../../src/MatrixClientPeg'; import HTMLExporter from "../../../../src/utils/exportUtils/HtmlExport"; -import SettingsStore from '../../../../src/settings/SettingsStore'; +import ChatExport from '../../../../src/customisations/ChatExport'; import PlainTextExporter from '../../../../src/utils/exportUtils/PlainTextExport'; jest.useFakeTimers(); @@ -42,12 +40,11 @@ const plainTextExporterInstance = ({ jest.mock("../../../../src/utils/exportUtils/HtmlExport", () => jest.fn()); jest.mock("../../../../src/utils/exportUtils/PlainTextExport", () => jest.fn()); -jest.mock('../../../../src/settings/SettingsStore', () => ({ - monitorSetting: jest.fn(), - getValue: jest.fn(), +jest.mock('../../../../src/customisations/ChatExport', () => ({ + getForceChatExportParameters: jest.fn().mockReturnValue({}), })); -const SettingsStoreMock = mocked(SettingsStore); +const ChatExportMock = mocked(ChatExport); const HTMLExporterMock = mocked(HTMLExporter); const PlainTextExporterMock = mocked(PlainTextExporter); @@ -105,7 +102,7 @@ describe('', () => { plainTextExporterInstance.export.mockClear(); // default setting value - SettingsStoreMock.getValue.mockClear().mockReturnValue({}); + ChatExportMock.getForceChatExportParameters.mockClear().mockReturnValue({}); }); it('renders export dialog', () => { @@ -141,12 +138,12 @@ describe('', () => { }); it('exports room using values set from ForceRoomExportParameters', async () => { - SettingsStoreMock.getValue.mockReturnValue({ + ChatExportMock.getForceChatExportParameters.mockReturnValue({ format: ExportFormat.PlainText, range: ExportType.Beginning, sizeMb: 15000, numberOfMessages: 30, - attachmentsIncluded: true, + includeAttachments: true, }); const component = getComponent(); await submitForm(component); @@ -157,7 +154,7 @@ describe('', () => { defaultProps.room, ExportType.Beginning, { - attachmentsIncluded: false, + attachmentsIncluded: true, maxSize: 15000 * 1024 * 1024, numberOfMessages: 30, }, @@ -194,7 +191,7 @@ describe('', () => { }); it('does not render export format when set in ForceRoomExportParameters', () => { - SettingsStoreMock.getValue.mockReturnValue({ + ChatExportMock.getForceChatExportParameters.mockReturnValue({ format: ExportFormat.PlainText, }); const component = getComponent(); @@ -215,7 +212,7 @@ describe('', () => { }); it('does not render export type when set in ForceRoomExportParameters', () => { - SettingsStoreMock.getValue.mockReturnValue({ + ChatExportMock.getForceChatExportParameters.mockReturnValue({ range: ExportType.Beginning, }); const component = getComponent(); @@ -306,7 +303,7 @@ describe('', () => { }); it('does not render size limit input when set in ForceRoomExportParameters', () => { - SettingsStoreMock.getValue.mockReturnValue({ + ChatExportMock.getForceChatExportParameters.mockReturnValue({ sizeMb: 10000, }); const component = getComponent(); @@ -317,7 +314,7 @@ describe('', () => { * 2000mb size limit does not apply when higher limit is configured in config */ it('exports when size limit set in ForceRoomExportParameters is larger than 2000', async () => { - SettingsStoreMock.getValue.mockReturnValue({ + ChatExportMock.getForceChatExportParameters.mockReturnValue({ sizeMb: 10000, }); const component = getComponent(); @@ -340,41 +337,12 @@ describe('', () => { }); it('does not render input when set in ForceRoomExportParameters', () => { - SettingsStoreMock.getValue.mockReturnValue({ + ChatExportMock.getForceChatExportParameters.mockReturnValue({ includeAttachments: false, }); const component = getComponent(); expect(getAttachmentsCheckbox(component).length).toBeFalsy(); }); }); - - describe('getSafeForceRoomExportParameters()', () => { - const testCases: [string, ForceRoomExportParameters, ForceRoomExportParameters][] = [ - ['setting is falsy', undefined, {}], - ['setting is configured to string', 'test' as unknown, {}], - ['setting is empty', {}, {}], - ['format is not a valid ExportFormat', { format: 'mp3' }, {}], - ['format is a valid ExportFormat', { format: ExportFormat.Html }, { format: ExportFormat.Html }], - ['range is not a valid ExportType', { range: 'yesterday' }, {}], - ['range is a valid ExportType', { range: ExportType.LastNMessages }, { range: ExportType.LastNMessages }], - ['numberOfMessages is not a number', { numberOfMessages: 'test' }, {}], - ['numberOfMessages is less than 1', { numberOfMessages: -1 }, {}], - ['numberOfMessages is more than 100000000', { numberOfMessages: 9999999999 }, {}], - ['numberOfMessages is valid', { numberOfMessages: 2000 }, { numberOfMessages: 2000 }], - ['sizeMb is not a number', { sizeMb: 'test' }, {}], - ['sizeMb is less than 1', { sizeMb: -1 }, {}], - ['sizeMb is more than 100000', { sizeMb: Number.MAX_SAFE_INTEGER }, {}], - ['sizeMb is valid', { sizeMb: 50000 }, { sizeMb: 50000 }], - ['includeAttachments is not a boolean', { includeAttachments: 'yes' }, {}], - ['includeAttachments is true', { includeAttachments: true }, { includeAttachments: true }], - ['includeAttachments is false', { includeAttachments: false }, { includeAttachments: false }], - ]; - - it.each(testCases)('sanitizes correctly when %s', (_d, setting, expected) => { - SettingsStoreMock.getValue.mockReturnValue(setting); - - expect(getSafeForceRoomExportParameters()).toEqual(expected); - }); - }); }); From 02dbc601efbd9bb3ee60812f6ab8ebd1990ab07c Mon Sep 17 00:00:00 2001 From: Kerry Archibald Date: Fri, 28 Jan 2022 14:32:07 +0100 Subject: [PATCH 08/11] move validateNumberInRange to utils Signed-off-by: Kerry Archibald --- src/components/views/dialogs/ExportDialog.tsx | 16 +++++++----- src/utils/validate/index.ts | 1 + src/utils/validate/numberInRange.ts | 9 +++++++ test/utils/validate/numberInRange-test.ts | 26 +++++++++++++++++++ 4 files changed, 45 insertions(+), 7 deletions(-) create mode 100644 src/utils/validate/index.ts create mode 100644 src/utils/validate/numberInRange.ts create mode 100644 test/utils/validate/numberInRange-test.ts diff --git a/src/components/views/dialogs/ExportDialog.tsx b/src/components/views/dialogs/ExportDialog.tsx index 83a871e8dad..04f183c904d 100644 --- a/src/components/views/dialogs/ExportDialog.tsx +++ b/src/components/views/dialogs/ExportDialog.tsx @@ -40,16 +40,12 @@ import Exporter from "../../../utils/exportUtils/Exporter"; import Spinner from "../elements/Spinner"; import InfoDialog from "./InfoDialog"; import ChatExport from "../../../customisations/ChatExport"; +import { validateNumberInRange } from "../../../utils/validate"; interface IProps extends IDialogProps { room: Room; } -const validateNumberInRange = (min: number, max: number) => (value?: string | number) => { - const parsedSize = parseInt(value as string, 10); - return !(isNaN(parsedSize) || min > parsedSize || parsedSize > max); -}; - interface ExportConfig { exportFormat: ExportFormat; exportType: ExportType; @@ -203,7 +199,10 @@ const ExportDialog: React.FC = ({ room, onFinished }) => { }, }, { key: "number", - test: ({ value }) => validateNumberInRange(1, 2000)(value), + test: ({ value }) => { + const parsedSize = parseInt(value as string, 10); + return validateNumberInRange(1, 2000)(parsedSize); + }, invalid: () => { const min = 1; const max = 2000; @@ -238,7 +237,10 @@ const ExportDialog: React.FC = ({ room, onFinished }) => { }, }, { key: "number", - test: ({ value }) => validateNumberInRange(1, 10 ** 8)(value), + test: ({ value }) => { + const parsedSize = parseInt(value as string, 10); + return validateNumberInRange(1, 10 ** 8)(parsedSize); + }, invalid: () => { const min = 1; const max = 10 ** 8; diff --git a/src/utils/validate/index.ts b/src/utils/validate/index.ts new file mode 100644 index 00000000000..f4357cbc17f --- /dev/null +++ b/src/utils/validate/index.ts @@ -0,0 +1 @@ +export * from "./numberInRange"; diff --git a/src/utils/validate/numberInRange.ts b/src/utils/validate/numberInRange.ts new file mode 100644 index 00000000000..dda5af8f07c --- /dev/null +++ b/src/utils/validate/numberInRange.ts @@ -0,0 +1,9 @@ + +/** + * Validates that a value is + * - a number + * - in a provided range (inclusive) + */ +export const validateNumberInRange = (min: number, max: number) => (value?: number) => { + return typeof value === 'number' && !(isNaN(value) || min > value || value > max); +}; diff --git a/test/utils/validate/numberInRange-test.ts b/test/utils/validate/numberInRange-test.ts new file mode 100644 index 00000000000..f7ad2e8c1c2 --- /dev/null +++ b/test/utils/validate/numberInRange-test.ts @@ -0,0 +1,26 @@ +import { validateNumberInRange } from '../../../src/utils/validate'; + +describe('validateNumberInRange', () => { + const min = 1; const max = 10; + it('returns false when value is a not a number', () => { + expect(validateNumberInRange(min, max)('test' as unknown as number)).toEqual(false); + }); + it('returns false when value is undefined', () => { + expect(validateNumberInRange(min, max)(undefined)).toEqual(false); + }); + it('returns false when value is NaN', () => { + expect(validateNumberInRange(min, max)(NaN)).toEqual(false); + }); + it('returns true when value is equal to min', () => { + expect(validateNumberInRange(min, max)(min)).toEqual(true); + }); + it('returns true when value is equal to max', () => { + expect(validateNumberInRange(min, max)(max)).toEqual(true); + }); + it('returns true when value is an int in range', () => { + expect(validateNumberInRange(min, max)(2)).toEqual(true); + }); + it('returns true when value is a float in range', () => { + expect(validateNumberInRange(min, max)(2.2)).toEqual(true); + }); +}); From 4118e3e3d8295ede29e2e95da972768abc95f595 Mon Sep 17 00:00:00 2001 From: Kerry Archibald Date: Fri, 28 Jan 2022 14:34:56 +0100 Subject: [PATCH 09/11] use nullish coalesce Signed-off-by: Kerry Archibald --- src/components/views/dialogs/ExportDialog.tsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/components/views/dialogs/ExportDialog.tsx b/src/components/views/dialogs/ExportDialog.tsx index 04f183c904d..918bc733c12 100644 --- a/src/components/views/dialogs/ExportDialog.tsx +++ b/src/components/views/dialogs/ExportDialog.tsx @@ -67,12 +67,12 @@ interface ExportConfig { const useExportFormState = (): ExportConfig => { const config = ChatExport.getForceChatExportParameters(); - const [exportFormat, setExportFormat] = useState(config.format || ExportFormat.Html); - const [exportType, setExportType] = useState(config.range || ExportType.Timeline); + const [exportFormat, setExportFormat] = useState(config.format ?? ExportFormat.Html); + const [exportType, setExportType] = useState(config.range ?? ExportType.Timeline); const [includeAttachments, setAttachments] = - useState(config.includeAttachments !== undefined && config.includeAttachments); - const [numberOfMessages, setNumberOfMessages] = useState(config.numberOfMessages || 100); - const [sizeLimit, setSizeLimit] = useState(config.sizeMb || 8); + useState(config.includeAttachments ?? false); + const [numberOfMessages, setNumberOfMessages] = useState(config.numberOfMessages ?? 100); + const [sizeLimit, setSizeLimit] = useState(config.sizeMb ?? 8); return { exportFormat, From 00f049506d481f4c3b47bfbf3da5eb2d1ee2995f Mon Sep 17 00:00:00 2001 From: Kerry Archibald Date: Mon, 31 Jan 2022 12:41:58 +0100 Subject: [PATCH 10/11] use 8gb size limit for customisation Signed-off-by: Kerry Archibald --- src/customisations/ChatExport.ts | 3 +++ src/utils/exportUtils/Exporter.ts | 2 +- test/components/views/dialogs/ExportDialog-test.tsx | 4 ++-- test/utils/export-test.tsx | 4 ++-- 4 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/customisations/ChatExport.ts b/src/customisations/ChatExport.ts index 6c69e55154f..938f3a1dc6f 100644 --- a/src/customisations/ChatExport.ts +++ b/src/customisations/ChatExport.ts @@ -20,8 +20,11 @@ export type ForceChatExportParameters = { format?: ExportFormat; range?: ExportType; // must be < 10**8 + // only used when range is 'LastNMessages' + // default is 100 numberOfMessages?: number; includeAttachments?: boolean; + // maximum size of exported archive // must be > 0 and < 100000 sizeMb?: number; }; diff --git a/src/utils/exportUtils/Exporter.ts b/src/utils/exportUtils/Exporter.ts index b55cb6d1bad..7b4a7bf342f 100644 --- a/src/utils/exportUtils/Exporter.ts +++ b/src/utils/exportUtils/Exporter.ts @@ -48,7 +48,7 @@ export default abstract class Exporter { protected setProgressText: React.Dispatch>, ) { if (exportOptions.maxSize < 1 * 1024 * 1024|| // Less than 1 MB - exportOptions.maxSize > 100000 * 1024 * 1024 || // More than 100 GB + exportOptions.maxSize > 8000 * 1024 * 1024 || // More than 8 GB exportOptions.numberOfMessages > 10**8 ) { throw new Error("Invalid export options"); diff --git a/test/components/views/dialogs/ExportDialog-test.tsx b/test/components/views/dialogs/ExportDialog-test.tsx index db87a1c1c67..07d859358a4 100644 --- a/test/components/views/dialogs/ExportDialog-test.tsx +++ b/test/components/views/dialogs/ExportDialog-test.tsx @@ -141,7 +141,7 @@ describe('', () => { ChatExportMock.getForceChatExportParameters.mockReturnValue({ format: ExportFormat.PlainText, range: ExportType.Beginning, - sizeMb: 15000, + sizeMb: 7000, numberOfMessages: 30, includeAttachments: true, }); @@ -155,7 +155,7 @@ describe('', () => { ExportType.Beginning, { attachmentsIncluded: true, - maxSize: 15000 * 1024 * 1024, + maxSize: 7000 * 1024 * 1024, numberOfMessages: 30, }, ]); diff --git a/test/utils/export-test.tsx b/test/utils/export-test.tsx index e0232c775d4..a9d0f00e2b4 100644 --- a/test/utils/export-test.tsx +++ b/test/utils/export-test.tsx @@ -200,9 +200,9 @@ describe('export', function() { maxSize: 1024 * 1024 * 1024, attachmentsIncluded: false, }], - ['maxSize exceeds 100GB', { + ['maxSize exceeds 8GB', { numberOfMessages: -1, - maxSize: 100001 * 1024 * 1024, + maxSize: 8001 * 1024 * 1024, attachmentsIncluded: false, }], ['maxSize is less than 1mb', { From 506900cbb444b02d62d4785ad6a95e5d57910cb5 Mon Sep 17 00:00:00 2001 From: Kerry Archibald Date: Mon, 31 Jan 2022 12:43:35 +0100 Subject: [PATCH 11/11] update comments Signed-off-by: Kerry Archibald --- src/customisations/ChatExport.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/customisations/ChatExport.ts b/src/customisations/ChatExport.ts index 938f3a1dc6f..abb55a07482 100644 --- a/src/customisations/ChatExport.ts +++ b/src/customisations/ChatExport.ts @@ -25,7 +25,7 @@ export type ForceChatExportParameters = { numberOfMessages?: number; includeAttachments?: boolean; // maximum size of exported archive - // must be > 0 and < 100000 + // must be > 0 and < 8000 sizeMb?: number; };