Skip to content

Commit

Permalink
Merge branch 'main' into rk/text-editor-ui-design
Browse files Browse the repository at this point in the history
  • Loading branch information
ryankeairns authored Jun 18, 2024
2 parents 491245e + e2a98cf commit 6fe6382
Show file tree
Hide file tree
Showing 52 changed files with 787 additions and 206 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ spec:
REPORT_FAILED_TESTS_TO_GITHUB: 'true'
ELASTIC_SLACK_NOTIFICATIONS_ENABLED: 'true'
allow_rebuilds: true
branch_configuration: main 7.17 8.13 8.14
branch_configuration: main 7.17 8.14
default_branch: main
repository: elastic/kibana
pipeline_file: .buildkite/pipelines/on_merge.yml
Expand Down
1 change: 1 addition & 0 deletions .github/CODEOWNERS
Validating CODEOWNERS rules …
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ packages/content-management/tabbed_table_list_view @elastic/appex-sharedux
packages/content-management/table_list_view @elastic/appex-sharedux
packages/content-management/table_list_view_common @elastic/appex-sharedux
packages/content-management/table_list_view_table @elastic/appex-sharedux
packages/content-management/user_profiles @elastic/appex-sharedux
packages/kbn-content-management-utils @elastic/kibana-data-discovery
examples/controls_example @elastic/kibana-presentation
src/plugins/controls @elastic/kibana-presentation
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,7 @@
"@kbn/content-management-table-list-view": "link:packages/content-management/table_list_view",
"@kbn/content-management-table-list-view-common": "link:packages/content-management/table_list_view_common",
"@kbn/content-management-table-list-view-table": "link:packages/content-management/table_list_view_table",
"@kbn/content-management-user-profiles": "link:packages/content-management/user_profiles",
"@kbn/content-management-utils": "link:packages/kbn-content-management-utils",
"@kbn/controls-example-plugin": "link:examples/controls_example",
"@kbn/controls-plugin": "link:src/plugins/controls",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

import React from 'react';
import { render, screen, waitFor } from '@testing-library/react';
import { UserProfilesProvider } from '@kbn/content-management-user-profiles';
import { I18nProvider } from '@kbn/i18n-react';

import { ActivityView as ActivityViewComponent, ActivityViewProps } from './activity_view';

const mockGetUserProfile = jest.fn(async (uid: string) => ({
uid,
enabled: true,
data: {},
user: { username: uid, full_name: uid.toLocaleUpperCase() },
}));

const ActivityView = (props: ActivityViewProps) => {
return (
<I18nProvider>
<UserProfilesProvider bulkGetUserProfiles={jest.fn()} getUserProfile={mockGetUserProfile}>
<ActivityViewComponent {...props} />
</UserProfilesProvider>
</I18nProvider>
);
};

test('should render activity view', () => {
render(<ActivityView item={{}} />);

expect(screen.getByTestId('activityView')).toBeVisible();

expect(screen.getByTestId('createdByCard')).toHaveTextContent(/Unknown/);
expect(() => screen.getByTestId('updateByCard')).toThrow();
});

test('should render creator card', async () => {
render(<ActivityView item={{ createdBy: 'john', createdAt: '2024-06-13T12:55:46.825Z' }} />);

await waitFor(() => {
const createdByCard = screen.getByTestId('createdByCard');
expect(createdByCard).toHaveTextContent(/JOHN/);
expect(createdByCard).toHaveTextContent(/June 13/);
});
});

test('should not render updater card when updatedAt matches createdAt', async () => {
render(
<ActivityView
item={{
createdBy: 'john',
updatedBy: 'john',
createdAt: '2024-06-13T12:55:46.825Z',
updatedAt: '2024-06-13T12:55:46.825Z',
}}
/>
);

expect(screen.getByTestId('createdByCard')).toBeVisible();
expect(() => screen.getByTestId('updateByCard')).toThrow();
});

test('should render updater card', async () => {
render(
<ActivityView
item={{
createdBy: 'john',
updatedBy: 'pete',
createdAt: '2024-06-13T12:55:46.825Z',
updatedAt: '2024-06-14T12:55:46.825Z',
}}
/>
);

await waitFor(() => {
const createdByCard = screen.getByTestId('createdByCard');
expect(createdByCard).toHaveTextContent(/JOHN/);
expect(createdByCard).toHaveTextContent(/June 13/);
});

await waitFor(() => {
const updatedByCard = screen.getByTestId('updatedByCard');
expect(updatedByCard).toHaveTextContent(/PETE/);
expect(updatedByCard).toHaveTextContent(/June 14/);
});
});

test('should handle managed objects', async () => {
render(
<ActivityView
item={{
managed: true,
createdAt: '2024-06-13T12:55:46.825Z',
updatedAt: '2024-06-14T12:55:46.825Z',
}}
/>
);

await waitFor(() => {
const createdByCard = screen.getByTestId('createdByCard');
expect(createdByCard).toHaveTextContent(/System/);
expect(createdByCard).toHaveTextContent(/June 13/);
});

const updatedByCard = screen.getByTestId('updatedByCard');
expect(updatedByCard).toHaveTextContent(/System/);
expect(updatedByCard).toHaveTextContent(/June 14/);
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

import {
EuiFlexGroup,
EuiFlexItem,
EuiFormRow,
EuiIconTip,
EuiPanel,
EuiSpacer,
EuiText,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n-react';
import React from 'react';
import {
UserAvatarTip,
useUserProfile,
NoUpdaterTip,
NoCreatorTip,
ManagedAvatarTip,
} from '@kbn/content-management-user-profiles';
import { getUserDisplayName } from '@kbn/user-profile-components';

import { Item } from '../types';

export interface ActivityViewProps {
item: Pick<Item, 'createdBy' | 'createdAt' | 'updatedBy' | 'updatedAt' | 'managed'>;
}

export const ActivityView = ({ item }: ActivityViewProps) => {
const showLastUpdated = Boolean(item.updatedAt && item.updatedAt !== item.createdAt);

const UnknownUserLabel = (
<FormattedMessage
id="contentManagement.contentEditor.activity.unkownUserLabel"
defaultMessage="Unknown"
/>
);

const ManagedUserLabel = (
<>
<ManagedAvatarTip />{' '}
<FormattedMessage
id="contentManagement.contentEditor.activity.managedUserLabel"
defaultMessage="System"
/>
</>
);

return (
<EuiFormRow
label={
<>
<FormattedMessage
id="contentManagement.contentEditor.metadataForm.activityLabel"
defaultMessage="Activity"
/>{' '}
<EuiIconTip
type={'iInCircle'}
iconProps={{ style: { verticalAlign: 'bottom' } }}
content={
<FormattedMessage
id="contentManagement.contentEditor.activity.activityLabelHelpText"
defaultMessage="Activity data is auto-generated and cannot be updated."
/>
}
/>
</>
}
fullWidth
data-test-subj={'activityView'}
>
<>
<EuiFlexGroup gutterSize={'s'}>
<EuiFlexItem grow={1} css={{ flexBasis: '50%', minWidth: 0 }}>
<ActivityCard
what={i18n.translate('contentManagement.contentEditor.activity.createdByLabelText', {
defaultMessage: 'Created by',
})}
who={
item.createdBy ? (
<UserLabel uid={item.createdBy} />
) : item.managed ? (
<>{ManagedUserLabel}</>
) : (
<>
{UnknownUserLabel}
<NoCreatorTip />
</>
)
}
when={item.createdAt}
data-test-subj={'createdByCard'}
/>
</EuiFlexItem>
<EuiFlexItem grow={1} css={{ flexBasis: '50%', minWidth: 0 }}>
{showLastUpdated && (
<ActivityCard
what={i18n.translate(
'contentManagement.contentEditor.activity.lastUpdatedByLabelText',
{ defaultMessage: 'Last updated by' }
)}
who={
item.updatedBy ? (
<UserLabel uid={item.updatedBy} />
) : item.managed ? (
<>{ManagedUserLabel}</>
) : (
<>
{UnknownUserLabel}
<NoUpdaterTip />
</>
)
}
when={item.updatedAt}
data-test-subj={'updatedByCard'}
/>
)}
</EuiFlexItem>
</EuiFlexGroup>
</>
</EuiFormRow>
);
};

const dateFormatter = new Intl.DateTimeFormat(i18n.getLocale(), {
dateStyle: 'long',
timeStyle: 'short',
});

const ActivityCard = ({
what,
when,
who,
'data-test-subj': dataTestSubj,
}: {
what: string;
who: React.ReactNode;
when?: string;
'data-test-subj'?: string;
}) => {
return (
<EuiPanel hasBorder paddingSize={'s'} data-test-subj={dataTestSubj}>
<EuiText size={'s'}>
<b>{what}</b>
</EuiText>
<EuiSpacer size={'xs'} />
<EuiText size={'s'} className={'eui-textTruncate'}>
{who}
</EuiText>
{when && (
<>
<EuiSpacer size={'xs'} />
<EuiText title={when} color={'subdued'} size={'s'}>
<FormattedMessage
id="contentManagement.contentEditor.activity.lastUpdatedByDateTime"
defaultMessage="on {dateTime}"
values={{
dateTime: dateFormatter.format(new Date(when)),
}}
/>
</EuiText>
</>
)}
</EuiPanel>
);
};

const UserLabel = ({ uid }: { uid: string }) => {
const userQuery = useUserProfile(uid);

if (!userQuery.data) return null;

return (
<>
<UserAvatarTip uid={uid} /> {getUserDisplayName(userQuery.data.user)}
</>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import type { Item } from '../types';
import { MetadataForm } from './metadata_form';
import { useMetadataForm } from './use_metadata_form';
import type { CustomValidators } from './use_metadata_form';
import { ActivityView } from './activity_view';

const getI18nTexts = ({ entityName }: { entityName: string }) => ({
saveButtonLabel: i18n.translate('contentManagement.contentEditor.saveButtonLabel', {
Expand Down Expand Up @@ -55,6 +56,7 @@ export interface Props {
}) => Promise<void>;
customValidators?: CustomValidators;
onCancel: () => void;
showActivityView?: boolean;
}

const capitalize = (str: string) => `${str.charAt(0).toLocaleUpperCase()}${str.substring(1)}`;
Expand All @@ -68,6 +70,7 @@ export const ContentEditorFlyoutContent: FC<Props> = ({
onSave,
onCancel,
customValidators,
showActivityView,
}) => {
const { euiTheme } = useEuiTheme();
const [isSubmitting, setIsSubmitting] = useState(false);
Expand Down Expand Up @@ -147,7 +150,9 @@ export const ContentEditorFlyoutContent: FC<Props> = ({
tagsReferences={item.tags}
TagList={TagList}
TagSelector={TagSelector}
/>
>
{showActivityView && <ActivityView item={item} />}
</MetadataForm>
</EuiFlyoutBody>

<EuiFlyoutFooter>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ type CommonProps = Pick<
| 'onCancel'
| 'entityName'
| 'customValidators'
| 'showActivityView'
>;

export type Props = CommonProps;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -262,5 +262,22 @@ describe('<ContentEditorFlyoutContent />', () => {
tags: ['id-3', 'id-4'], // New selection
});
});

test('should render activity view', async () => {
await act(async () => {
testBed = await setup({ showActivityView: true });
});
const { find, component } = testBed!;

expect(find('activityView').exists()).toBe(true);
expect(find('activityView.createdByCard').exists()).toBe(true);
expect(find('activityView.updatedByCard').exists()).toBe(false);

testBed.setProps({
item: { ...savedObjectItem, updatedAt: '2021-01-01T00:00:00Z' },
});
component.update();
expect(find('activityView.updatedByCard').exists()).toBe(true);
});
});
});
Loading

0 comments on commit 6fe6382

Please sign in to comment.