Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

feat(content-sidebar): disable version modifications for archive file #3637

Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
a6e56d9
feat(content-sidebar): add unchangeable versions feature to VersionsItem
michalkowalczyk-box Sep 5, 2024
9fbdfc7
feat(content-sidebar): create tests for unchangeableVersionsFeature
michalkowalczyk-box Sep 6, 2024
ea10fee
feat(content-sidebar): add more exhaustive test cases for permissions
michalkowalczyk-box Sep 6, 2024
08ab0b1
feat(content-sidebar): block restore on unchangeableVersions enabled
michalkowalczyk-box Sep 6, 2024
c2c76f3
feat(content-sidebar): fix VersionsItem test names
michalkowalczyk-box Sep 6, 2024
07843c6
feat(content-sidebar): revert changes to VersionsItem
michalkowalczyk-box Oct 2, 2024
fc18385
feat(content-sidebar): update VersionsItem to use archive metadata
michalkowalczyk-box Oct 2, 2024
1dcc7c6
feat(content-sidebar): implement archive fetch for versions
michalkowalczyk-box Oct 2, 2024
37e767c
feat(content-sidebar): fix flow error
michalkowalczyk-box Oct 2, 2024
c0d4e86
feat(content-sidebar): udpate VersionsItem tests to RTL
michalkowalczyk-box Oct 8, 2024
2ac32fb
Merge branch 'master' into disable-version-modifications-for-content-…
michalkowalczyk-box Oct 9, 2024
56896b1
feat(content-sidebar): updated prop name and order
michalkowalczyk-box Oct 9, 2024
6aa0f8f
feat(content-sidebar): reverted changes to en-US.properties
michalkowalczyk-box Oct 9, 2024
df0645e
feat(content-sidebar): moved tests to new file
michalkowalczyk-box Oct 10, 2024
4e21d1c
feat(content-sidebar): updated snapshot
michalkowalczyk-box Oct 10, 2024
da95adf
Merge branch 'master' into disable-version-modifications-for-content-…
michalkowalczyk-box Oct 10, 2024
140a7e1
Merge branch 'master' into disable-version-modifications-for-content-…
michalkowalczyk-box Oct 11, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions src/elements/content-sidebar/versions/VersionsItem.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import './VersionsItem.scss';

type Props = {
fileId: string,
isArchiveFile?: boolean,
isCurrent?: boolean,
isSelected?: boolean,
isWatermarked?: boolean,
Expand All @@ -51,6 +52,7 @@ const FIVE_MINUTES_MS = 5 * 60 * 1000;

const VersionsItem = ({
fileId,
isArchiveFile = false,
isCurrent = false,
isSelected = false,
isWatermarked = false,
Expand Down Expand Up @@ -104,10 +106,10 @@ const VersionsItem = ({

// Version action helpers
const canPreview = can_preview && !isDeleted && !isLimited && !isRestricted;
const showDelete = can_delete && !isDeleted && !isCurrent;
const showDelete = can_delete && !isDeleted && !isArchiveFile && !isCurrent;
const showDownload = can_download && !isDeleted && isDownloadable;
const showPromote = can_upload && !isDeleted && !isCurrent;
const showRestore = can_delete && isDeleted;
const showPromote = can_upload && !isDeleted && !isArchiveFile && !isCurrent;
const showRestore = can_delete && isDeleted && !isArchiveFile;
const showPreview = canPreview && !isSelected;
const hasActions = showDelete || showDownload || showPreview || showPromote || showRestore;

Expand Down
21 changes: 18 additions & 3 deletions src/elements/content-sidebar/versions/VersionsSidebarAPI.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* @author Box
*/
import API from '../../../api';
import { FILE_VERSION_FIELDS_TO_FETCH } from '../../../utils/fields';
import { FILE_VERSION_FIELDS_TO_FETCH, FILE_VERSION_FIELDS_TO_FETCH_ARCHIVE } from '../../../utils/fields';
import type { BoxItem, FileVersions, BoxItemVersion } from '../../../common/types/core';

export type fetchPayload = [BoxItem, FileVersions];
Expand All @@ -14,9 +14,20 @@ export default class VersionsSidebarAPI {

fileId: string;

constructor({ api, fileId }: { api: API, fileId: string }) {
isArchiveFeatureEnabled: boolean;

constructor({
api,
fileId,
isArchiveFeatureEnabled,
}: {
api: API,
fileId: string,
isArchiveFeatureEnabled: boolean,
}) {
this.api = api;
this.fileId = fileId;
this.isArchiveFeatureEnabled = isArchiveFeatureEnabled;
}

fetchData = (): Promise<fetchPayload> => {
Expand All @@ -34,9 +45,13 @@ export default class VersionsSidebarAPI {
};

fetchFile = (): Promise<BoxItem> => {
const fields = this.isArchiveFeatureEnabled
? FILE_VERSION_FIELDS_TO_FETCH_ARCHIVE
: FILE_VERSION_FIELDS_TO_FETCH;

return new Promise((resolve, reject) =>
this.api.getFileAPI().getFile(this.fileId, resolve, reject, {
fields: FILE_VERSION_FIELDS_TO_FETCH,
fields,
forceFetch: true,
}),
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,22 @@ import noop from 'lodash/noop';
import { generatePath, withRouter } from 'react-router-dom';
import type { Match, RouterHistory } from 'react-router-dom';
import type { MessageDescriptor } from 'react-intl';
import { withFeatureConsumer, isFeatureEnabled } from '../../common/feature-checking';
import API from '../../../api';
import { FIELD_METADATA_ARCHIVE } from '../../../constants';
import messages from './messages';
import openUrlInsideIframe from '../../../utils/iframe';
import StaticVersionsSidebar from './StaticVersionSidebar';
import VersionsSidebar from './VersionsSidebar';
import VersionsSidebarAPI from './VersionsSidebarAPI';
import { withAPIContext } from '../../common/api-context';
import type { FeatureConfig } from '../../common/feature-checking';
import type { VersionActionCallback, VersionChangeCallback, SidebarLoadCallback } from './flowTypes';
import type { BoxItemVersion, BoxItem, FileVersions } from '../../../common/types/core';

type Props = {
api: API,
features: FeatureConfig,
fileId: string,
hasSidebarInitialized?: boolean,
history: RouterHistory,
Expand All @@ -43,6 +47,7 @@ type Props = {
type State = {
error?: MessageDescriptor,
isLoading: boolean,
isArchiveFile: boolean,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor, but we should alphabetize props and object properties when possible.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated

isWatermarked: boolean,
versionCount: number,
versionLimit: number,
Expand All @@ -66,6 +71,7 @@ class VersionsSidebarContainer extends React.Component<Props, State> {
props: Props;

state: State = {
isArchiveFile: false,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor, but consider isArchived to stay consistent with isWatermarked.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated

isLoading: true,
isWatermarked: false,
versionCount: Infinity,
Expand Down Expand Up @@ -168,16 +174,18 @@ class VersionsSidebarContainer extends React.Component<Props, State> {
handleFetchError = (): void => {
this.setState({
error: messages.versionFetchError,
isArchiveFile: false,
isLoading: false,
isWatermarked: false,
versionCount: 0,
versions: [],
});
};

handleFetchSuccess = ([fileResponse, versionsResponse]): [BoxItem, FileVersions] => {
handleFetchSuccess = ([fileResponse, versionsResponse]: [BoxItem, FileVersions]): [BoxItem, FileVersions] => {
const { api } = this.props;
const { version_limit } = fileResponse;
const isArchiveFile = !!getProp(fileResponse, FIELD_METADATA_ARCHIVE);
const isWatermarked = getProp(fileResponse, 'watermark_info.is_watermarked', false);
const versionLimit = version_limit !== null && version_limit !== undefined ? version_limit : Infinity;
const versionsWithPermissions = api.getVersionsAPI(false).addPermissions(versionsResponse, fileResponse) || {};
Expand All @@ -186,6 +194,7 @@ class VersionsSidebarContainer extends React.Component<Props, State> {
this.setState(
{
error: undefined,
isArchiveFile,
isLoading: false,
isWatermarked,
versionCount,
Expand All @@ -207,14 +216,14 @@ class VersionsSidebarContainer extends React.Component<Props, State> {
};

initialize = (): void => {
this.api = new VersionsSidebarAPI(this.props);
const { api, features, fileId }: Props = this.props;
const isArchiveFeatureEnabled = isFeatureEnabled(features, 'contentSidebar.archive.enabled');

this.api = new VersionsSidebarAPI({ api, fileId, isArchiveFeatureEnabled });
};

fetchData = (): Promise<?[BoxItem, FileVersions]> => {
return this.api
.fetchData()
.then(this.handleFetchSuccess)
.catch(this.handleFetchError);
return this.api.fetchData().then(this.handleFetchSuccess).catch(this.handleFetchError);
};

findVersion = (versionId: ?string): ?BoxItemVersion => {
Expand Down Expand Up @@ -299,4 +308,5 @@ class VersionsSidebarContainer extends React.Component<Props, State> {
}

export type VersionsSidebarProps = Props;
export default flow([withRouter, withAPIContext])(VersionsSidebarContainer);
export { VersionsSidebarContainer as VersionsSidebarContainerComponent };
export default flow([withRouter, withAPIContext, withFeatureConsumer])(VersionsSidebarContainer);
106 changes: 106 additions & 0 deletions src/elements/content-sidebar/versions/__tests__/VersionsItem.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -238,5 +238,111 @@ describe('elements/content-sidebar/versions/VersionsItem', () => {
expect(wrapper.exists(VersionsItemRetention)).toBe(true);
expect(wrapper.find(VersionsItemActions).prop('isRetained')).toBe(true);
});

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you please rewrite those test to use React Testing Library. Enzyme is a huge problem in EUA and we need to slowly convert existing tests instead of creating new ones.

Copy link
Contributor Author

@michalkowalczyk-box michalkowalczyk-box Oct 9, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated whole file to RTL

test.each`
permissions
${{ can_delete: true, can_download: true, can_preview: true, can_upload: true }}
${{ can_delete: true, can_download: true, can_preview: true, can_upload: false }}
${{ can_delete: true, can_download: true, can_preview: false, can_upload: true }}
${{ can_delete: true, can_download: false, can_preview: true, can_upload: true }}
${{ can_delete: false, can_download: true, can_preview: true, can_upload: true }}
${{ can_delete: true, can_download: true, can_preview: false, can_upload: false }}
${{ can_delete: true, can_download: false, can_preview: true, can_upload: false }}
${{ can_delete: false, can_download: true, can_preview: true, can_upload: false }}
${{ can_delete: false, can_download: true, can_preview: false, can_upload: true }}
${{ can_delete: false, can_download: false, can_preview: true, can_upload: true }}
${{ can_delete: false, can_download: true, can_preview: false, can_upload: false }}
${{ can_delete: false, can_download: false, can_preview: true, can_upload: false }}
`('should disable the correct menu items based on permissions when is archive file', ({ permissions }) => {
const wrapper = getWrapper({
version: getVersion({ permissions }),
isArchiveFile: true,
});
const actions = wrapper.find(VersionsItemActions);
const button = wrapper.find(VersionsItemButton);

expect(button.prop('isDisabled')).toBe(!permissions.can_preview);
expect(actions.prop('showDelete')).toBe(false);
expect(actions.prop('showDownload')).toBe(permissions.can_download);
expect(actions.prop('showPromote')).toBe(false);
expect(actions.prop('showPreview')).toBe(permissions.can_preview);
expect(wrapper.find(ReadableTime)).toBeTruthy();
});

test.each`
permissions
${{ can_delete: true, can_download: false, can_preview: false, can_upload: true }}
${{ can_delete: true, can_download: false, can_preview: false, can_upload: false }}
${{ can_delete: false, can_download: false, can_preview: false, can_upload: true }}
${{ can_delete: false, can_download: false, can_preview: false, can_upload: false }}
`('should not render actions based on permissions when is archive file', ({ permissions }) => {
const wrapper = getWrapper({
version: getVersion({ permissions }),
isArchiveFile: true,
});
const actions = wrapper.exists(VersionsItemActions);
const button = wrapper.find(VersionsItemButton);

expect(button.prop('isDisabled')).toBe(!permissions.can_preview);
expect(actions).toBeFalsy();
expect(wrapper.find(ReadableTime)).toBeTruthy();
});

test.each`
action | showDelete | showDownload | showPreview | showPromote | showRestore
${'restore'} | ${false} | ${true} | ${true} | ${false} | ${false}
${'upload'} | ${false} | ${true} | ${true} | ${false} | ${false}
`(
"should show actions correctly when the version's action is $action and is archive file",
({ action, showDelete, showDownload, showPreview, showPromote, showRestore }) => {
selectors.getVersionAction.mockReturnValueOnce(action);

const wrapper = getWrapper({
version: getVersion({
permissions: {
can_delete: true,
can_download: true,
can_preview: true,
can_upload: true,
},
}),
isArchiveFile: true,
});
const actions = wrapper.find(VersionsItemActions);
const button = wrapper.find(VersionsItemButton);

expect(button.prop('isDisabled')).toBe(!showPreview);
expect(actions.prop('showDelete')).toBe(showDelete);
expect(actions.prop('showDownload')).toBe(showDownload);
expect(actions.prop('showPromote')).toBe(showPromote);
expect(actions.prop('showPreview')).toBe(showPreview);
expect(actions.prop('showRestore')).toBe(showRestore);
expect(wrapper.find(ReadableTime)).toBeTruthy();
expect(wrapper).toMatchSnapshot();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We generally want to avoid adding new snapshot tests, especially for larger components. Let's rely on explicit assertions, instead.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've moved all the tests for this component to react-testing-library, so I removed snapshots completely there

},
);

test("should not show actions when the version's action is delete and unchangeableVersions feature is enabled", () => {
selectors.getVersionAction.mockReturnValueOnce('delete');

const wrapper = getWrapper({
version: getVersion({
permissions: {
can_delete: true,
can_download: true,
can_preview: true,
can_upload: true,
},
}),
isArchiveFile: true,
});
const actions = wrapper.exists(VersionsItemActions);
const button = wrapper.find(VersionsItemButton);

expect(button.prop('isDisabled')).toBe(true);
expect(actions).toBeFalsy();
expect(wrapper.find(ReadableTime)).toBeTruthy();
expect(wrapper).toMatchSnapshot();
});
});
});
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import VersionsSidebarAPI from '../VersionsSidebarAPI';
import { FILE_VERSION_FIELDS_TO_FETCH } from '../../../../utils/fields';
import { FILE_VERSION_FIELDS_TO_FETCH, FILE_VERSION_FIELDS_TO_FETCH_ARCHIVE } from '../../../../utils/fields';

describe('VersionsSidebarAPI', () => {
const defaultFileId = '123';
Expand All @@ -24,7 +24,8 @@ describe('VersionsSidebarAPI', () => {
};
const mockPermissons = { can_delete: true, can_download: true, can_upload: true };
const mockVersion = { id: defaultVersionId, permissions: mockPermissons };
const getInstance = (options = {}) => new VersionsSidebarAPI({ api: mockAPI, fileId: defaultFileId, ...options });
const getInstance = (options = {}) =>
new VersionsSidebarAPI({ api: mockAPI, fileId: defaultFileId, isArchiveFeatureEnabled: false, ...options });

describe('deleteVersion', () => {
test('should call deleteVersion', () => {
Expand Down Expand Up @@ -68,7 +69,7 @@ describe('VersionsSidebarAPI', () => {
});

describe('fetchFile', () => {
test('should call getFile', () => {
test('should call getFile without archive field when feature is disabled', () => {
const instance = getInstance();

expect(instance.fetchFile()).toBeInstanceOf(Promise);
Expand All @@ -77,6 +78,16 @@ describe('VersionsSidebarAPI', () => {
forceFetch: true,
});
});

test('should call getFile with archive field when feature is enabled', () => {
const instance = getInstance({ isArchiveFeatureEnabled: true });

expect(instance.fetchFile()).toBeInstanceOf(Promise);
expect(fileAPI.getFile).toBeCalledWith(defaultFileId, expect.any(Function), expect.any(Function), {
fields: FILE_VERSION_FIELDS_TO_FETCH_ARCHIVE,
forceFetch: true,
});
});
});

describe('fetchVersions', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import * as React from 'react';
import { shallow } from 'enzyme/build';
import messages from '../messages';
import openUrlInsideIframe from '../../../../utils/iframe';
import VersionsSidebar from '../VersionsSidebarContainer';
import { VersionsSidebarContainerComponent } from '../VersionsSidebarContainer';

jest.mock('react-router-dom', () => ({
...jest.requireActual('react-router-dom'),
Expand Down Expand Up @@ -32,7 +32,8 @@ describe('elements/content-sidebar/versions/VersionsSidebarContainer', () => {
const api = {
getVersionsAPI: () => versionsAPI,
};
const getWrapper = ({ ...props } = {}) => shallow(<VersionsSidebar api={api} fileId="12345" {...props} />);
const getWrapper = ({ ...props } = {}) =>
shallow(<VersionsSidebarContainerComponent api={api} fileId="12345" {...props} />);

describe('componentDidUpdate', () => {
let onVersionChange;
Expand Down Expand Up @@ -245,6 +246,7 @@ describe('elements/content-sidebar/versions/VersionsSidebarContainer', () => {
expect(instance.sortVersions).toBeCalledWith(versionsWithCurrent.entries);
expect(wrapper.state()).toMatchObject({
error: undefined,
isArchiveFile: false,
isLoading: false,
isWatermarked: false,
versionCount: 2,
Expand All @@ -263,6 +265,17 @@ describe('elements/content-sidebar/versions/VersionsSidebarContainer', () => {

expect(wrapper.state('isWatermarked')).toBe(true);
});

test('should set state with isArchiveFile if file is archive file', () => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As above.

Copy link
Contributor Author

@michalkowalczyk-box michalkowalczyk-box Oct 9, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel this file is too complicated to switch whole of it from enzyme to RTL in scope of this PR. I have RTL implementation for new tests (added to tests this PR code changes), but I'm working on putting it alongside enzyme in this file. I'm having trouble, because some module mocks needed for RTL are breaking enzyme tests and vice versa

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved this test case to new file using RTL

const wrapper = getWrapper();
const instance = wrapper.instance();
const testFile = { ...file, metadata: { global: { archivedItemTemplate: { archiveDate: '1726832355' } } } };

instance.verifyVersion = jest.fn();
instance.handleFetchSuccess([testFile, versionsWithCurrent]);

expect(wrapper.state('isArchiveFile')).toBe(true);
});
});

describe('findVersion', () => {
Expand Down
Loading
Loading