Skip to content

Commit

Permalink
[data view mgmt] add saved object relationships to data view manageme…
Browse files Browse the repository at this point in the history
…nt (#132385)

* add saved object relationships to data view management
  • Loading branch information
mattkime authored May 24, 2022
1 parent d1b4465 commit 606d9c2
Show file tree
Hide file tree
Showing 20 changed files with 402 additions and 17 deletions.
2 changes: 1 addition & 1 deletion src/plugins/data_view_management/kibana.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"version": "kibana",
"server": true,
"ui": true,
"requiredPlugins": ["management", "data", "urlForwarding", "dataViewFieldEditor", "dataViewEditor", "dataViews", "fieldFormats", "unifiedSearch"],
"requiredPlugins": ["management", "data", "urlForwarding", "dataViewFieldEditor", "dataViewEditor", "dataViews", "fieldFormats", "unifiedSearch", "savedObjectsManagement"],
"requiredBundles": ["kibanaReact", "kibanaUtils"],
"optionalPlugins": ["spaces"],
"owner": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@
export const TAB_INDEXED_FIELDS = 'indexedFields';
export const TAB_SCRIPTED_FIELDS = 'scriptedFields';
export const TAB_SOURCE_FILTERS = 'sourceFilters';
export const TAB_RELATIONSHIPS = 'relationships';
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,12 @@ import {
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n-react';
import { DataView, DataViewField } from '@kbn/data-views-plugin/public';
import { DATA_VIEW_SAVED_OBJECT_TYPE } from '@kbn/data-views-plugin/public';
import { useKibana } from '@kbn/kibana-react-plugin/public';
import {
SavedObjectRelation,
SavedObjectManagementTypeInfo,
} from '@kbn/saved-objects-management-plugin/public';
import { IndexPatternManagmentContext } from '../../types';
import { Tabs } from './tabs';
import { IndexHeader } from './index_header';
Expand All @@ -32,6 +37,10 @@ export interface EditIndexPatternProps extends RouteComponentProps {
indexPattern: DataView;
}

export interface SavedObjectRelationWithTitle extends SavedObjectRelation {
title: string;
}

const mappingAPILink = i18n.translate(
'indexPatternManagement.editIndexPattern.timeFilterLabel.mappingAPILink',
{
Expand All @@ -57,14 +66,34 @@ const securitySolution = 'security-solution';

export const EditIndexPattern = withRouter(
({ indexPattern, history, location }: EditIndexPatternProps) => {
const { uiSettings, overlays, chrome, dataViews } =
const { uiSettings, overlays, chrome, dataViews, savedObjectsManagement } =
useKibana<IndexPatternManagmentContext>().services;
const [fields, setFields] = useState<DataViewField[]>(indexPattern.getNonScriptedFields());
const [conflictedFields, setConflictedFields] = useState<DataViewField[]>(
indexPattern.fields.getAll().filter((field) => field.type === 'conflict')
);
const [defaultIndex, setDefaultIndex] = useState<string>(uiSettings.get('defaultIndex'));
const [tags, setTags] = useState<any[]>([]);
const [relationships, setRelationships] = useState<SavedObjectRelationWithTitle[]>([]);
const [allowedTypes, setAllowedTypes] = useState<SavedObjectManagementTypeInfo[]>([]);

useEffect(() => {
savedObjectsManagement.getAllowedTypes().then((resp) => {
setAllowedTypes(resp);
});
}, [savedObjectsManagement]);

useEffect(() => {
if (allowedTypes.length === 0) {
return;
}
const allowedAsString = allowedTypes.map((item) => item.name);
savedObjectsManagement
.getRelationships(DATA_VIEW_SAVED_OBJECT_TYPE, indexPattern.id!, allowedAsString)
.then((resp) => {
setRelationships(resp.relations.map((r) => ({ ...r, title: r.meta.title! })));
});
}, [savedObjectsManagement, indexPattern, allowedTypes]);

useEffect(() => {
setFields(indexPattern.getNonScriptedFields());
Expand Down Expand Up @@ -200,6 +229,8 @@ export const EditIndexPattern = withRouter(
indexPattern={indexPattern}
saveIndexPattern={dataViews.updateSavedObject.bind(dataViews)}
fields={fields}
relationships={relationships}
allowedTypes={allowedTypes}
history={history}
location={location}
refreshFields={() => {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
* 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 { i18n } from '@kbn/i18n';

export const typeFieldName = i18n.translate(
'indexPatternManagement.objectsTable.relationships.columnTypeName',
{
defaultMessage: 'Type',
}
);

export const typeFieldDescription = i18n.translate(
'indexPatternManagement.objectsTable.relationships.columnTypeDescription',
{ defaultMessage: 'Type of the saved object' }
);

export const titleFieldName = i18n.translate(
'indexPatternManagement.objectsTable.relationships.columnTitleName',
{
defaultMessage: 'Title',
}
);

export const titleFieldDescription = i18n.translate(
'indexPatternManagement.objectsTable.relationships.columnTitleDescription',
{ defaultMessage: 'Title of the saved object' }
);

export const filterTitle = i18n.translate(
'indexPatternManagement.objectsTable.relationships.search.filters.type.name',
{ defaultMessage: 'Type' }
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/*
* 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 { RelationshipsTable } from './relationships_table';
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
/*
* 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 {
EuiInMemoryTable,
HorizontalAlignment,
EuiText,
EuiLink,
EuiTableDataType,
} from '@elastic/eui';
import { CoreStart } from '@kbn/core/public';
import { get } from 'lodash';
import { RedirectAppLinks } from '@kbn/shared-ux-link-redirect-app';

import {
SavedObjectRelation,
SavedObjectManagementTypeInfo,
SavedObjectsManagementPluginStart,
} from '@kbn/saved-objects-management-plugin/public';

import { EuiToolTip, EuiIcon, SearchFilterConfig } from '@elastic/eui';
import { IPM_APP_ID } from '../../../plugin';
import {
typeFieldName,
typeFieldDescription,
titleFieldName,
titleFieldDescription,
filterTitle,
} from './i18n';

const canGoInApp = (
savedObject: SavedObjectRelation,
capabilities: CoreStart['application']['capabilities']
) => {
const { inAppUrl } = savedObject.meta;
if (!inAppUrl) return false;
if (!inAppUrl.uiCapabilitiesPath) return true;
return Boolean(get(capabilities, inAppUrl.uiCapabilitiesPath));
};

export const RelationshipsTable = ({
basePath,
capabilities,
id,
navigateToUrl,
getDefaultTitle,
getSavedObjectLabel,
relationships,
allowedTypes,
}: {
basePath: CoreStart['http']['basePath'];
capabilities: CoreStart['application']['capabilities'];
navigateToUrl: CoreStart['application']['navigateToUrl'];
id: string;
getDefaultTitle: SavedObjectsManagementPluginStart['getDefaultTitle'];
getSavedObjectLabel: SavedObjectsManagementPluginStart['getSavedObjectLabel'];
relationships: SavedObjectRelation[];
allowedTypes: SavedObjectManagementTypeInfo[];
}) => {
const columns = [
{
field: 'type',
name: typeFieldName,
width: '50px',
align: 'center' as HorizontalAlignment,
description: typeFieldDescription,
sortable: false,
render: (type: string, object: SavedObjectRelation) => {
const typeLabel = getSavedObjectLabel(type, allowedTypes);
return (
<EuiToolTip position="top" content={typeLabel}>
<EuiIcon
aria-label={typeLabel}
type={object.meta.icon || 'apps'}
size="s"
data-test-subj="relationshipsObjectType"
/>
</EuiToolTip>
);
},
},
{
field: 'title',
name: titleFieldName,
description: titleFieldDescription,
dataType: 'string' as EuiTableDataType,
sortable: false,
render: (title: string, object: SavedObjectRelation) => {
const path = object.meta.inAppUrl?.path || '';
const showUrl = canGoInApp(object, capabilities);
const titleDisplayed = title || getDefaultTitle(object);

return showUrl ? (
<EuiLink href={basePath.prepend(path)} data-test-subj="relationshipsTitle">
{titleDisplayed}
</EuiLink>
) : (
<EuiText size="s" data-test-subj="relationshipsTitle">
{titleDisplayed}
</EuiText>
);
},
},
];

const filterTypesMap = new Map(
relationships.map((relationship) => [
relationship.type,
{
value: relationship.type,
name: relationship.type,
view: relationship.type,
},
])
);

const search = {
// query,
// onChange: handleOnChange,
box: {
incremental: true,
schema: true,
},
filters: [
{
type: 'field_value_selection',
field: 'type',
name: filterTitle,
multiSelect: 'or',
options: [...filterTypesMap.values()],
},
] as SearchFilterConfig[],
};

return (
<RedirectAppLinks currentAppId={IPM_APP_ID} navigateToUrl={navigateToUrl}>
<EuiInMemoryTable<SavedObjectRelation>
items={relationships}
columns={columns}
pagination={true}
search={search}
rowProps={() => ({
'data-test-subj': `relationshipsTableRow`,
})}
/>
</RedirectAppLinks>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,23 @@ import {
DataViewsPublicPluginStart,
META_FIELDS,
} from '@kbn/data-views-plugin/public';
import {
SavedObjectRelation,
SavedObjectManagementTypeInfo,
} from '@kbn/saved-objects-management-plugin/public';
import { useKibana } from '@kbn/kibana-react-plugin/public';
import { IndexPatternManagmentContext } from '../../../types';
import { createEditIndexPatternPageStateContainer } from '../edit_index_pattern_state_container';
import { TAB_INDEXED_FIELDS, TAB_SCRIPTED_FIELDS, TAB_SOURCE_FILTERS } from '../constants';
import {
TAB_INDEXED_FIELDS,
TAB_SCRIPTED_FIELDS,
TAB_SOURCE_FILTERS,
TAB_RELATIONSHIPS,
} from '../constants';
import { SourceFiltersTable } from '../source_filters_table';
import { IndexedFieldsTable } from '../indexed_fields_table';
import { ScriptedFieldsTable } from '../scripted_fields_table';
import { RelationshipsTable } from '../relationships_table';
import { getTabs, getPath, convertToEuiFilterOptions } from './utils';
import { getFieldInfo } from '../../utils';

Expand All @@ -45,6 +55,8 @@ interface TabsProps extends Pick<RouteComponentProps, 'history' | 'location'> {
fields: DataViewField[];
saveIndexPattern: DataViewsPublicPluginStart['updateSavedObject'];
refreshFields: () => void;
relationships: SavedObjectRelation[];
allowedTypes: SavedObjectManagementTypeInfo[];
}

interface FilterItems {
Expand Down Expand Up @@ -131,9 +143,20 @@ export function Tabs({
history,
location,
refreshFields,
relationships,
allowedTypes,
}: TabsProps) {
const { uiSettings, docLinks, dataViewFieldEditor, overlays, theme, dataViews } =
useKibana<IndexPatternManagmentContext>().services;
const {
uiSettings,
docLinks,
dataViewFieldEditor,
overlays,
theme,
dataViews,
http,
application,
savedObjectsManagement,
} = useKibana<IndexPatternManagmentContext>().services;
const [fieldFilter, setFieldFilter] = useState<string>('');
const [syncingStateFunc, setSyncingStateFunc] = useState<any>({
getCurrentTab: () => TAB_INDEXED_FIELDS,
Expand Down Expand Up @@ -492,6 +515,22 @@ export function Tabs({
/>
</Fragment>
);
case TAB_RELATIONSHIPS:
return (
<Fragment>
<EuiSpacer size="m" />
<RelationshipsTable
basePath={http.basePath}
id={indexPattern.id!}
capabilities={application.capabilities}
relationships={relationships}
allowedTypes={allowedTypes}
navigateToUrl={application.navigateToUrl}
getDefaultTitle={savedObjectsManagement.getDefaultTitle}
getSavedObjectLabel={savedObjectsManagement.getSavedObjectLabel}
/>
</Fragment>
);
}
},
[
Expand All @@ -513,18 +552,25 @@ export function Tabs({
overlays,
theme,
dataViews,
http,
application,
savedObjectsManagement,
allowedTypes,
relationships,
]
);

const euiTabs: EuiTabbedContentTab[] = useMemo(
() =>
getTabs(indexPattern, fieldFilter).map((tab: Pick<EuiTabbedContentTab, 'name' | 'id'>) => {
return {
...tab,
content: getContent(tab.id),
};
}),
[fieldFilter, getContent, indexPattern]
getTabs(indexPattern, fieldFilter, relationships.length).map(
(tab: Pick<EuiTabbedContentTab, 'name' | 'id'>) => {
return {
...tab,
content: getContent(tab.id),
};
}
),
[fieldFilter, getContent, indexPattern, relationships]
);

const [selectedTabId, setSelectedTabId] = useState(euiTabs[0].id);
Expand Down
Loading

0 comments on commit 606d9c2

Please sign in to comment.