Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 2 additions & 0 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,8 @@ packages/kbn-config-mocks @elastic/kibana-core
packages/kbn-config-schema @elastic/kibana-core
src/plugins/console @elastic/kibana-management
packages/content-management/content_editor @elastic/appex-sharedux
packages/content-management/content_insights/content_insights_public @elastic/appex-sharedux
packages/content-management/content_insights/content_insights_server @elastic/appex-sharedux
examples/content_management_examples @elastic/appex-sharedux
packages/content-management/favorites/favorites_public @elastic/appex-sharedux
packages/content-management/favorites/favorites_server @elastic/appex-sharedux
Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,8 @@
"@kbn/config-schema": "link:packages/kbn-config-schema",
"@kbn/console-plugin": "link:src/plugins/console",
"@kbn/content-management-content-editor": "link:packages/content-management/content_editor",
"@kbn/content-management-content-insights-public": "link:packages/content-management/content_insights/content_insights_public",
"@kbn/content-management-content-insights-server": "link:packages/content-management/content_insights/content_insights_server",
"@kbn/content-management-examples-plugin": "link:examples/content_management_examples",
"@kbn/content-management-favorites-public": "link:packages/content-management/favorites/favorites_public",
"@kbn/content-management-favorites-server": "link:packages/content-management/favorites/favorites_server",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ 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 @@ -56,7 +55,7 @@ export interface Props {
}) => Promise<void>;
customValidators?: CustomValidators;
onCancel: () => void;
showActivityView?: boolean;
appendRows?: React.ReactNode;
}

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

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

export type Props = CommonProps;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,32 +6,30 @@
* Side Public License, v 1.
*/

import React, { useState, useCallback, useEffect } from 'react';
import { EuiFlyoutHeader, EuiFlyoutBody, EuiFlyoutFooter } from '@elastic/eui';
import React from 'react';
import { EuiFlyoutBody, EuiFlyoutFooter, EuiFlyoutHeader } from '@elastic/eui';
import type { Props } from './editor_flyout_content_container';

export const ContentEditorLoader: React.FC<Props> = (props) => {
const [Editor, setEditor] = useState<React.ComponentType<Props> | null>(null);

const loadEditor = useCallback(async () => {
const { ContentEditorFlyoutContentContainer } = await import(
'./editor_flyout_content_container'
);
setEditor(() => ContentEditorFlyoutContentContainer);
}, []);
const ContentEditorFlyoutContentContainer = React.lazy(() =>
import('./editor_flyout_content_container').then(
({ ContentEditorFlyoutContentContainer: _ContentEditorFlyoutContentContainer }) => ({
default: _ContentEditorFlyoutContentContainer,
})
)
);

useEffect(() => {
// On mount: load the editor asynchronously
loadEditor();
}, [loadEditor]);

return Editor ? (
<Editor {...props} />
) : (
<>
<EuiFlyoutHeader />
<EuiFlyoutBody />
<EuiFlyoutFooter />
</>
export const ContentEditorLoader: React.FC<Props> = (props) => {
return (
<React.Suspense
fallback={
<>
<EuiFlyoutHeader />
<EuiFlyoutBody />
<EuiFlyoutFooter />
</>
}
>
<ContentEditorFlyoutContentContainer {...props} />
</React.Suspense>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -262,22 +262,5 @@ 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);
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export type OpenContentEditorParams = Pick<
| 'readonlyReason'
| 'entityName'
| 'customValidators'
| 'showActivityView'
| 'appendRows'
>;

export function useOpenContentEditor() {
Expand Down
1 change: 0 additions & 1 deletion packages/content-management/content_editor/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@
"@kbn/test-jest-helpers",
"@kbn/react-kibana-mount",
"@kbn/content-management-user-profiles",
"@kbn/user-profile-components"
],
"exclude": [
"target/**/*"
Expand Down
64 changes: 64 additions & 0 deletions packages/content-management/content_insights/README.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
---
id: sharedUX/ContentInsights
slug: /shared-ux/content-insights
title: Content Insights
description: A set of Content Management services and component to provide insights on the content of Kibana.
tags: ['shared-ux', 'component']
date: 2024-08-06
---

## Description

The Content Insights is a set of Content Management services and components to provide insights on the content of Kibana.
Currently, it allows to track the usage of your content and display the stats of it.

- The service can count the following events:
- `viewed`
- It provides the api for registering the routes to increase the count and to get the stats.
- It provides the client to increase the count and to get the stats.
- It provides a flyout and a component to display the stats as a total count and a weekly chart.
- Internally it uses the usage collection plugin to store and search the data.

## API

// server side

```ts
import { registerContentInsights } from '@kbn/content-management-content-insights-server';

if (plugins.usageCollection) {
// Registers routes for tracking and fetching dashboard views
registerContentInsights(
{
usageCollection: plugins.usageCollection,
http: core.http,
getStartServices: () =>
core.getStartServices().then(([_, start]) => ({
usageCollection: start.usageCollection!,
})),
},
{
domainId: 'dashboard',
// makes sure that only users with read/all access to dashboard app can access the routes
routeTags: ['access:dashboardUsageStats'],
}
);
}
```

// client side

```ts
import { ContentInsightsClient } from '@kbn/content-management-content-insights-public';

const contentInsightsClient = new ContentInsightsClient(
{ http: params.coreStart.http },
{ domainId: 'dashboard' }
);

contentInsightsClient.track(dashboardId, 'viewed');

// wrap component in `ContentInsightsProvider` and use the hook to open an insights flyout
const openInsightsFlyout = useOpenInsightsFlyout();
openInsightsFlyout({ item });
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# @kbn/content-management-content-insights-public

Refer to [README](../README.mdx)
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/*
* 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.
*/

export {
ContentInsightsProvider,
type ContentInsightsServices,
useServices as useContentInsightsServices,
} from './src/services';

export {
type ContentInsightsClientPublic,
ContentInsightsClient,
type ContentInsightsEventTypes,
} from './src/client';

export { ActivityView, ViewsStats, type ActivityViewProps } from './src/components';
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/*
* 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.
*/

module.exports = {
preset: '@kbn/test',
rootDir: '../../../..',
roots: ['<rootDir>/packages/content-management/content_insights/content_insights_public'],
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"type": "shared-browser",
"id": "@kbn/content-management-content-insights-public",
"owner": "@elastic/appex-sharedux"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"name": "@kbn/content-management-content-insights-public",
"private": true,
"version": "1.0.0",
"license": "SSPL-1.0 OR Elastic License 2.0"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
* 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 type { HttpStart } from '@kbn/core-http-browser';
import type {
ContentInsightsStats,
ContentInsightsStatsResponse,
} from '@kbn/content-management-content-insights-server';

export type ContentInsightsEventTypes = 'viewed';

/**
* Public interface of the Content Management Insights service.
*/
export interface ContentInsightsClientPublic {
track(id: string, eventType: ContentInsightsEventTypes): void;
getStats(id: string, eventType: ContentInsightsEventTypes): Promise<ContentInsightsStats>;
}

/**
* Client for the Content Management Insights service.
*/
export class ContentInsightsClient implements ContentInsightsClientPublic {
constructor(
private readonly deps: { http: HttpStart },
private readonly config: { domainId: string }
) {}

track(id: string, eventType: ContentInsightsEventTypes) {
this.deps.http
.post(`/internal/content_management/insights/${this.config.domainId}/${id}/${eventType}`)
.catch((e) => {
// eslint-disable-next-line no-console
console.warn(`Could not track ${eventType} event for ${id}`, e);
});
}

async getStats(id: string, eventType: ContentInsightsEventTypes) {
return this.deps.http
.get<ContentInsightsStatsResponse>(
`/internal/content_management/insights/${this.config.domainId}/${id}/${eventType}/stats`
)
.then((response) => response.result);
}
}
Loading