Skip to content

Commit

Permalink
feat(content-sidebar): add archived date to content preview sidebar (#…
Browse files Browse the repository at this point in the history
…3625)

* feat(content-sidebar): add archived date to content preview sidebar

* feat(content-sidebar): add feature flag to details sidebar

* feat(content-sidebar): fix archived field

* feat(content-sidebar): add tests and update snapshots for DetailsSidebar

* feat(content-sidebar): add tests and update snapshots for ItemProperties

* feat(content-sidebar):  update test and snapshots SidebarFileProperties

* feat(content-sidebar): move feature to ItemProperties

* feat(content-sidebar): revert tests for DetailsSidebar

This reverts commit 11d68f5.

* feat(content-sidebar): update snapshots and tests

* feat(content-sidebar): update test and prop types

* feat(content-sidebar): update sidebar to fetch archive date

* feat(content-sidebar): revert tests to master

* feat(content-sidebar): update tests and snapshots

* feat(content-sidebar): remove unused prop

* feat(content-sidebar): remove unused field

* feat(content-sidebar): refactor fields to fetch

* feat(content-sidebar): added comment to fetched fields

* feat(content-sidebar): import reorder and mock fix

* feat(content-sidebar): remove type annotations

* feat(content-sidebar): add type annotation

* feat(content-sidebar): changed archiveDate conversion
  • Loading branch information
michalkowalczyk-box authored Sep 26, 2024
1 parent 4bf65f8 commit 10e68f3
Show file tree
Hide file tree
Showing 15 changed files with 148 additions and 42 deletions.
2 changes: 2 additions & 0 deletions i18n/en-US.properties
Original file line number Diff line number Diff line change
Expand Up @@ -1106,6 +1106,8 @@ boxui.features.VirtualizedTableRenderers.externalFolder = External Folder
boxui.features.VirtualizedTableRenderers.lastModifiedBy = {lastModified} by {user}
# The user is unknown in the database.
boxui.features.VirtualizedTableRenderers.unknownUser = Unknown User
# Label for archivization date under item properties in the sidebar
boxui.itemDetails.archived = Archived
# Warning message in the sidebar that this bookmark will be automatically deleted on a certain date, {expiration} is the date
boxui.itemDetails.bookmarkExpiration = This bookmark will be deleted on {expiration}.
# Label for created date under item properties in the sidebar
Expand Down
3 changes: 3 additions & 0 deletions src/common/types/metadata.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ type MetadataTemplateSchemaResponse = {
};

type MetadataSkillsTemplate = {
archivedItemTemplate?: {
archiveDate: string,
},
boxSkillsCards?: SkillCards,
};

Expand Down
2 changes: 2 additions & 0 deletions src/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ export const HEADER_ACCEPT_LANGUAGE = 'Accept-Language';

/* ------------------ Metadata ---------------------- */
export const KEY_CLASSIFICATION_TYPE = 'Box__Security__Classification__Key';
export const METADATA_TEMPLATE_ARCHIVE = 'archivedItemTemplate';
export const METADATA_TEMPLATE_CLASSIFICATION = 'securityClassification-6VMVochwUWo';
export const METADATA_TEMPLATE_SKILLS = 'boxSkillsCards';
export const METADATA_TEMPLATE_PROPERTIES = 'properties';
Expand Down Expand Up @@ -135,6 +136,7 @@ export const FIELD_IS_DOWNLOAD_AVAILABLE = 'is_download_available';
export const FIELD_VERSION_LIMIT = 'version_limit';
export const FIELD_VERSION_NUMBER = 'version_number';
export const FIELD_METADATA = 'metadata';
export const FIELD_METADATA_ARCHIVE = `${FIELD_METADATA}.${METADATA_SCOPE_GLOBAL}.${METADATA_TEMPLATE_ARCHIVE}`;
export const FIELD_METADATA_SKILLS = `${FIELD_METADATA}.${METADATA_SCOPE_GLOBAL}.${METADATA_TEMPLATE_SKILLS}`;
export const FIELD_METADATA_PROPERTIES = `${FIELD_METADATA}.${METADATA_SCOPE_GLOBAL}.${METADATA_TEMPLATE_PROPERTIES}`;
export const FIELD_METADATA_CLASSIFICATION = `${FIELD_METADATA}.${METADATA_SCOPE_ENTERPRISE}.${METADATA_TEMPLATE_CLASSIFICATION}`;
Expand Down
17 changes: 13 additions & 4 deletions src/elements/content-sidebar/ContentSidebar.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,13 @@ import SidebarUtils from './SidebarUtils';
import { DEFAULT_HOSTNAME_API, CLIENT_NAME_CONTENT_SIDEBAR, ORIGIN_CONTENT_SIDEBAR } from '../../constants';
import { EVENT_JS_READY } from '../common/logger/constants';
import { mark } from '../../utils/performance';
import { SIDEBAR_FIELDS_TO_FETCH } from '../../utils/fields';
import { SIDEBAR_FIELDS_TO_FETCH, SIDEBAR_FIELDS_TO_FETCH_ARCHIVE } from '../../utils/fields';
import { withErrorBoundary } from '../common/error-boundary';
import { withFeatureProvider } from '../common/feature-checking';
import {
isFeatureEnabled as isFeatureEnabledInContext,
withFeatureConsumer,
withFeatureProvider,
} from '../common/feature-checking';
import { withLogger } from '../common/logger';

import type { ActivitySidebarProps } from './ActivitySidebar';
Expand Down Expand Up @@ -294,12 +298,16 @@ class ContentSidebar extends React.Component<Props, State> {
* @return {void}
*/
fetchFile(fetchOptions: RequestOptions = {}): void {
const { fileId }: Props = this.props;
const { fileId, features }: Props = this.props;
const archiveEnabled = isFeatureEnabledInContext(features, 'contentSidebar.archive.enabled');
const fields = archiveEnabled ? SIDEBAR_FIELDS_TO_FETCH_ARCHIVE : SIDEBAR_FIELDS_TO_FETCH;

this.setState({ isLoading: true });

if (fileId && SidebarUtils.canHaveSidebar(this.props)) {
this.api.getFileAPI().getFile(fileId, this.fetchFileSuccessCallback, this.errorCallback, {
...fetchOptions,
fields: SIDEBAR_FIELDS_TO_FETCH,
fields,
});
}
}
Expand Down Expand Up @@ -401,6 +409,7 @@ class ContentSidebar extends React.Component<Props, State> {
export type ContentSidebarProps = Props;
export { ContentSidebar as ContentSidebarComponent };
export default flow([
withFeatureConsumer,
withFeatureProvider,
withLogger(ORIGIN_CONTENT_SIDEBAR),
withErrorBoundary(ORIGIN_CONTENT_SIDEBAR),
Expand Down
23 changes: 17 additions & 6 deletions src/elements/content-sidebar/DetailsSidebar.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,10 @@ import SidebarSection from './SidebarSection';
import SidebarVersions from './SidebarVersions';
import { EVENT_JS_READY } from '../common/logger/constants';
import { getBadItemError } from '../../utils/error';
import { isFeatureEnabled, withFeatureConsumer } from '../common/feature-checking';
import { mark } from '../../utils/performance';
import { SECTION_TARGETS } from '../common/interactionTargets';
import { SIDEBAR_FIELDS_TO_FETCH } from '../../utils/fields';
import { SIDEBAR_FIELDS_TO_FETCH, SIDEBAR_FIELDS_TO_FETCH_ARCHIVE } from '../../utils/fields';
import { withAPIContext } from '../common/api-context';
import { withErrorBoundary } from '../common/error-boundary';
import { withLogger } from '../common/logger';
Expand All @@ -39,6 +40,7 @@ import type { ClassificationInfo, ContentInsights, FileAccessStats } from './flo
import type { WithLoggerProps } from '../../common/types/logging';
import type { ElementsErrorCallback, ErrorContextProps, ElementsXhrError } from '../../common/types/api';
import type { BoxItem } from '../../common/types/core';
import type { FeatureConfig } from '../common/feature-checking';
import './DetailsSidebar.scss';

type ExternalProps = {
Expand All @@ -65,6 +67,7 @@ type ExternalProps = {
WithLoggerProps;
type Props = {
api: API,
features: FeatureConfig,
} & ExternalProps &
ErrorContextProps &
WithLoggerProps;
Expand Down Expand Up @@ -161,9 +164,14 @@ class DetailsSidebar extends React.PureComponent<Props, State> {
successCallback: (file: BoxItem) => void = this.fetchFileSuccessCallback,
errorCallback: ElementsErrorCallback = this.fetchFileErrorCallback,
): void {
const { api, fileId }: Props = this.props;
const { api, features, fileId }: Props = this.props;
const archiveEnabled = isFeatureEnabled(features, 'contentSidebar.archive.enabled');

// TODO: replace this with DETAILS_SIDEBAR_FIELDS_TO_FETCH as we do not need all the sidebar fields
const fields = archiveEnabled ? SIDEBAR_FIELDS_TO_FETCH_ARCHIVE : SIDEBAR_FIELDS_TO_FETCH;

api.getFileAPI().getFile(fileId, successCallback, errorCallback, {
fields: SIDEBAR_FIELDS_TO_FETCH, // TODO: replace this with DETAILS_SIDEBAR_FIELDS_TO_FETCH as we do not need all the sidebar fields
fields,
});
}

Expand Down Expand Up @@ -408,6 +416,9 @@ class DetailsSidebar extends React.PureComponent<Props, State> {

export type DetailsSidebarProps = ExternalProps;
export { DetailsSidebar as DetailsSidebarComponent };
export default flow([withLogger(ORIGIN_DETAILS_SIDEBAR), withErrorBoundary(ORIGIN_DETAILS_SIDEBAR), withAPIContext])(
DetailsSidebar,
);
export default flow([
withLogger(ORIGIN_DETAILS_SIDEBAR),
withErrorBoundary(ORIGIN_DETAILS_SIDEBAR),
withAPIContext,
withFeatureConsumer,
])(DetailsSidebar);
65 changes: 35 additions & 30 deletions src/elements/content-sidebar/SidebarFileProperties.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import { INTERACTION_TARGET, DETAILS_TARGETS } from '../common/interactionTarget
import withErrorHandling from './withErrorHandling';
import type { ClassificationInfo } from './flowTypes';
import type { BoxItem } from '../../common/types/core';
import { PLACEHOLDER_USER } from '../../constants';
import { FIELD_METADATA_ARCHIVE, PLACEHOLDER_USER } from '../../constants';

type Props = {
classification?: ClassificationInfo,
Expand All @@ -37,35 +37,40 @@ const SidebarFileProperties = ({
onRetentionPolicyExtendClick,
isLoading,
intl,
}: Props) => (
<LoadingIndicatorWrapper isLoading={isLoading}>
<ItemProperties
createdAt={file.content_created_at}
description={file.description}
descriptionTextareaProps={{
[INTERACTION_TARGET]: DETAILS_TARGETS.DESCRIPTION,
}}
modifiedAt={file.content_modified_at}
onDescriptionChange={getProp(file, 'permissions.can_rename') ? onDescriptionChange : undefined}
owner={getProp(file, 'owned_by.name')}
retentionPolicyProps={
hasRetentionPolicy
? {
...retentionPolicy,
openModal: onRetentionPolicyExtendClick,
}
: {}
}
size={getFileSize(file.size, intl.locale)}
// use uploader_display_name if uploaded anonymously
uploader={
getProp(file, 'created_by.id') === PLACEHOLDER_USER.id
? getProp(file, 'uploader_display_name')
: getProp(file, 'created_by.name')
}
/>
</LoadingIndicatorWrapper>
);
}: Props) => {
const archiveDate = getProp(file, FIELD_METADATA_ARCHIVE)?.archiveDate;

return (
<LoadingIndicatorWrapper isLoading={isLoading}>
<ItemProperties
archivedAt={archiveDate && archiveDate * 1000}
createdAt={file.content_created_at}
description={file.description}
descriptionTextareaProps={{
[INTERACTION_TARGET]: DETAILS_TARGETS.DESCRIPTION,
}}
modifiedAt={file.content_modified_at}
onDescriptionChange={getProp(file, 'permissions.can_rename') ? onDescriptionChange : undefined}
owner={getProp(file, 'owned_by.name')}
retentionPolicyProps={
hasRetentionPolicy
? {
...retentionPolicy,
openModal: onRetentionPolicyExtendClick,
}
: {}
}
size={getFileSize(file.size, intl.locale)}
// use uploader_display_name if uploaded anonymously
uploader={
getProp(file, 'created_by.id') === PLACEHOLDER_USER.id
? getProp(file, 'uploader_display_name')
: getProp(file, 'created_by.name')
}
/>
</LoadingIndicatorWrapper>
);
};

export { SidebarFileProperties as SidebarFilePropertiesComponent };
export default injectIntl(withErrorHandling(SidebarFileProperties));
15 changes: 14 additions & 1 deletion src/elements/content-sidebar/__tests__/ContentSidebar.test.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import React, { act } from 'react';
import noop from 'lodash/noop';
import { mount } from 'enzyme';
import { SIDEBAR_FIELDS_TO_FETCH } from '../../../utils/fields';
import { ContentSidebarComponent as ContentSidebar } from '../ContentSidebar';
import { isFeatureEnabled } from '../../common/feature-checking';
import { SIDEBAR_FIELDS_TO_FETCH, SIDEBAR_FIELDS_TO_FETCH_ARCHIVE } from '../../../utils/fields';
import SidebarUtils from '../SidebarUtils';

jest.mock('../SidebarUtils');
jest.mock('../Sidebar', () => 'sidebar');
jest.mock('../../common/feature-checking');

const file = {
id: 'I_AM_A_FILE',
Expand Down Expand Up @@ -131,6 +133,17 @@ describe('elements/content-sidebar/ContentSidebar', () => {
});
expect(instance.setState).toBeCalled();
});

test('should fetch the file with archive metadata field when feature is enabled', () => {
SidebarUtils.canHaveSidebar = jest.fn().mockReturnValueOnce(true);
isFeatureEnabled.mockReturnValueOnce(true);
instance.fetchFile();
expect(SidebarUtils.canHaveSidebar).toBeCalledWith(instance.props);
expect(fileStub).toBeCalledWith(file.id, fetchFileSuccessCallback, instance.errorCallback, {
fields: SIDEBAR_FIELDS_TO_FETCH_ARCHIVE,
});
expect(instance.setState).toBeCalled();
});
});

describe('fetchMetadataSuccessCallback()', () => {
Expand Down
17 changes: 16 additions & 1 deletion src/elements/content-sidebar/__tests__/DetailsSidebar.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@ import { shallow } from 'enzyme';
import * as React from 'react';
import messages from '../../common/messages';
import { getBadItemError } from '../../../utils/error';
import { SIDEBAR_FIELDS_TO_FETCH } from '../../../utils/fields';
import { SIDEBAR_FIELDS_TO_FETCH, SIDEBAR_FIELDS_TO_FETCH_ARCHIVE } from '../../../utils/fields';
import { isFeatureEnabled } from '../../common/feature-checking';
import { DetailsSidebarComponent as DetailsSidebar } from '../DetailsSidebar';

jest.mock('../SidebarFileProperties', () => 'SidebarFileProperties');
jest.mock('../SidebarAccessStats', () => 'SidebarAccessStats');
jest.mock('../SidebarClassification', () => 'SidebarClassification');
jest.mock('../SidebarContentInsights', () => 'SidebarContentInsights');
jest.mock('../../common/feature-checking');

const file = {
id: 'foo',
Expand Down Expand Up @@ -419,6 +421,19 @@ describe('elements/content-sidebar/DetailsSidebar', () => {
},
);
});

test('should fetch the file info with archive metadata field when feature is enabled', () => {
isFeatureEnabled.mockReturnValueOnce(true);
instance.fetchFile();
expect(getFile).toHaveBeenCalledWith(
file.id,
instance.fetchFileSuccessCallback,
instance.fetchFileErrorCallback,
{
fields: SIDEBAR_FIELDS_TO_FETCH_ARCHIVE,
},
);
});
});

describe('fetchFileSuccessCallback()', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,13 @@ describe('elements/content-sidebar/SidebarFileProperties', () => {
created_by: {
name: 'foo',
},
metadata: {
global: {
archivedItemTemplate: {
archiveDate: '1726832355',
},
},
},
size: '1',
permissions: {
can_rename: true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
exports[`elements/content-sidebar/SidebarFileProperties render() should render ItemProperties 1`] = `
<LoadingIndicatorWrapper>
<ItemProperties
archivedAt={1726832355000}
createdAt="2018-04-18T16:56:05.352Z"
description="foo"
descriptionTextareaProps={
Expand All @@ -23,6 +24,7 @@ exports[`elements/content-sidebar/SidebarFileProperties render() should render I
exports[`elements/content-sidebar/SidebarFileProperties render() should render ItemProperties for anonymous uploaders 1`] = `
<LoadingIndicatorWrapper>
<ItemProperties
archivedAt={1726832355000}
createdAt="2018-04-18T16:56:05.352Z"
description="foo"
descriptionTextareaProps={
Expand Down
11 changes: 11 additions & 0 deletions src/features/item-details/ItemProperties.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ const datetimeOptions = {
};

const ItemProperties = ({
archivedAt,
createdAt,
description,
descriptionTextareaProps = {},
Expand Down Expand Up @@ -96,6 +97,14 @@ const ItemProperties = ({
</dd>
</>
) : null}
{archivedAt ? (
<>
<FormattedMessage tagName="dt" {...messages.archived} />
<dd>
<FormattedDate value={new Date(archivedAt)} {...datetimeOptions} />
</dd>
</>
) : null}
{size ? (
<>
<FormattedMessage tagName="dt" {...messages.size} />
Expand All @@ -116,6 +125,8 @@ const ItemProperties = ({
};

ItemProperties.propTypes = {
/** the datetime this item was archived, accepts any value that can be passed to the Date() constructor */
archivedAt: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
/** the datetime this item was created, accepts any value that can be passed to the Date() constructor */
createdAt: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
/** a description for the item */
Expand Down
1 change: 1 addition & 0 deletions src/features/item-details/__tests__/ItemProperties.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ describe('features/item-details/ItemProperties', () => {

test('should render properties when specified', () => {
const wrapper = getWrapper({
archivedAt: 1726832355000,
createdAt: '2012-12-12T11:04:26-08:00',
description: 'Hi\ntesting this link http://box.com',
enterpriseOwner: 'Test Enterprise Owner',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,21 @@ testing this link http://box.com"
year="numeric"
/>
</dd>
<FormattedMessage
defaultMessage="Archived"
id="boxui.itemDetails.archived"
tagName="dt"
/>
<dd>
<FormattedDate
day="numeric"
hour="numeric"
minute="numeric"
month="short"
value={2024-09-20T11:39:15.000Z}
year="numeric"
/>
</dd>
<FormattedMessage
defaultMessage="Size"
id="boxui.itemDetails.size"
Expand Down
5 changes: 5 additions & 0 deletions src/features/item-details/messages.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@
import { defineMessages } from 'react-intl';

const messages = defineMessages({
archived: {
defaultMessage: 'Archived',
description: 'Label for archivization date under item properties in the sidebar',
id: 'boxui.itemDetails.archived',
},
bookmarkExpiration: {
defaultMessage: 'This bookmark will be deleted on {expiration}.',
description:
Expand Down
Loading

0 comments on commit 10e68f3

Please sign in to comment.