[data view mgmt] add saved object relationships to data view management (#132385)

* add saved object relationships to data view management
This commit is contained in:
Matthew Kime 2022-05-24 05:01:39 -05:00 committed by GitHub
parent d1b4465d0a
commit 606d9c2649
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
20 changed files with 402 additions and 17 deletions

View file

@ -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": {

View file

@ -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';

View file

@ -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';
@ -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',
{
@ -57,7 +66,7 @@ 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[]>(
@ -65,6 +74,26 @@ export const EditIndexPattern = withRouter(
);
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());
@ -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={() => {

View file

@ -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' }
);

View file

@ -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';

View file

@ -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>
);
};

View file

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

View file

@ -9,7 +9,12 @@
import { Dictionary, countBy, defaults, uniq } from 'lodash';
import { i18n } from '@kbn/i18n';
import { DataView, DataViewField } from '@kbn/data-views-plugin/public';
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 { areScriptedFieldsEnabled } from '../../utils';
function filterByName(items: DataViewField[], filter: string) {
@ -68,7 +73,7 @@ function getTitle(type: string, filteredCount: Dictionary<number>, totalCount: D
return title + count;
}
export function getTabs(indexPattern: DataView, fieldFilter: string) {
export function getTabs(indexPattern: DataView, fieldFilter: string, relationshipCount = 0) {
const totalCount = getCounts(indexPattern.fields.getAll(), indexPattern.getSourceFiltering());
const filteredCount = getCounts(
indexPattern.fields.getAll(),
@ -98,6 +103,15 @@ export function getTabs(indexPattern: DataView, fieldFilter: string) {
'data-test-subj': 'tab-sourceFilters',
});
tabs.push({
name: i18n.translate('indexPatternManagement.editIndexPattern.tabs.relationshipsHeader', {
defaultMessage: 'Relationships ({count})',
values: { count: relationshipCount },
}),
id: TAB_RELATIONSHIPS,
'data-test-subj': 'tab-relationships',
});
return tabs;
}

View file

@ -40,7 +40,16 @@ export async function mountManagementSection(
) {
const [
{ application, chrome, uiSettings, notifications, overlays, http, docLinks, theme },
{ data, dataViewFieldEditor, dataViewEditor, dataViews, fieldFormats, unifiedSearch, spaces },
{
data,
dataViewFieldEditor,
dataViewEditor,
dataViews,
fieldFormats,
unifiedSearch,
spaces,
savedObjectsManagement,
},
indexPatternManagementStart,
] = await getStartServices();
const canSave = dataViews.getCanSaveSync();
@ -68,6 +77,7 @@ export async function mountManagementSection(
fieldFormats,
spaces,
theme,
savedObjectsManagement,
};
ReactDOM.render(

View file

@ -16,6 +16,7 @@ import { indexPatternFieldEditorPluginMock } from '@kbn/data-view-field-editor-p
import { indexPatternEditorPluginMock } from '@kbn/data-view-editor-plugin/public/mocks';
import { dataViewPluginMocks } from '@kbn/data-views-plugin/public/mocks';
import { fieldFormatsServiceMock } from '@kbn/field-formats-plugin/public/mocks';
import { savedObjectsManagementPluginMock } from '@kbn/saved-objects-management-plugin/public/mocks';
import {
IndexPatternManagementSetup,
IndexPatternManagementStart,
@ -63,6 +64,7 @@ const createIndexPatternManagmentContext = (): {
const dataViewFieldEditor = indexPatternFieldEditorPluginMock.createStartContract();
const dataViews = dataViewPluginMocks.createStartContract();
const unifiedSearch = unifiedSearchPluginMock.createStartContract();
const savedObjectsManagement = savedObjectsManagementPluginMock.createStartContract();
return {
application,
@ -83,6 +85,7 @@ const createIndexPatternManagmentContext = (): {
indexPatternEditorPluginMock.createStartContract().IndexPatternEditorComponent,
fieldFormats: fieldFormatsServiceMock.createStartContract(),
theme,
savedObjectsManagement,
};
};

View file

@ -18,6 +18,7 @@ import { DataViewEditorStart } from '@kbn/data-view-editor-plugin/public';
import { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public';
import { SpacesPluginStart } from '@kbn/spaces-plugin/public';
import { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/public';
import { SavedObjectsManagementPluginStart } from '@kbn/saved-objects-management-plugin/public';
export interface IndexPatternManagementSetupDependencies {
management: ManagementSetup;
@ -32,6 +33,7 @@ export interface IndexPatternManagementStartDependencies {
fieldFormats: FieldFormatsStart;
spaces?: SpacesPluginStart;
unifiedSearch: UnifiedSearchPublicPluginStart;
savedObjectsManagement: SavedObjectsManagementPluginStart;
}
// eslint-disable-next-line @typescript-eslint/no-empty-interface
@ -44,7 +46,7 @@ const sectionsHeader = i18n.translate('indexPatternManagement.dataView.sectionsH
defaultMessage: 'Data Views',
});
const IPM_APP_ID = 'dataViews';
export const IPM_APP_ID = 'dataViews';
export class IndexPatternManagementPlugin
implements

View file

@ -25,6 +25,7 @@ import { DataViewEditorStart } from '@kbn/data-view-editor-plugin/public';
import { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public';
import { FieldFormatsStart } from '@kbn/field-formats-plugin/public';
import { SpacesPluginStart } from '@kbn/spaces-plugin/public';
import { SavedObjectsManagementPluginStart } from '@kbn/saved-objects-management-plugin/public';
import { IndexPatternManagementStart } from '.';
export interface IndexPatternManagmentContext {
@ -46,6 +47,7 @@ export interface IndexPatternManagmentContext {
fieldFormats: FieldFormatsStart;
spaces?: SpacesPluginStart;
theme: ThemeServiceStart;
savedObjectsManagement: SavedObjectsManagementPluginStart;
}
export type IndexPatternManagmentContextValue =

View file

@ -21,6 +21,7 @@
{ "path": "../data_view_field_editor/tsconfig.json" },
{ "path": "../data_view_editor/tsconfig.json" },
{ "path": "../unified_search/tsconfig.json" },
{ "path": "../saved_objects_management/tsconfig.json" },
{ "path": "../../../x-pack/plugins/spaces/tsconfig.json" }
]
}

View file

@ -24,7 +24,12 @@ export type {
export { SavedObjectsManagementAction } from './services';
export type { ProcessedImportResponse, FailedImport } from './lib';
export { processImportResponse } from './lib';
export type { SavedObjectRelation, SavedObjectWithMetadata, SavedObjectMetadata } from './types';
export type {
SavedObjectRelation,
SavedObjectWithMetadata,
SavedObjectMetadata,
SavedObjectManagementTypeInfo,
} from './types';
export function plugin(initializerContext: PluginInitializerContext) {
return new SavedObjectsManagementPlugin();

View file

@ -22,6 +22,10 @@ const createStartContractMock = (): jest.Mocked<SavedObjectsManagementPluginStar
const mock = {
actions: actionServiceMock.createStart(),
columns: columnServiceMock.createStart(),
getAllowedTypes: jest.fn(),
getRelationships: jest.fn(),
getSavedObjectLabel: jest.fn(),
getDefaultTitle: jest.fn(),
};
return mock;
};

View file

@ -23,6 +23,9 @@ import {
SavedObjectsManagementColumnServiceStart,
} from './services';
import { SavedObjectManagementTypeInfo, SavedObjectGetRelationshipsResponse } from './types';
import { getAllowedTypes, getRelationships, getSavedObjectLabel, getDefaultTitle } from './lib';
export interface SavedObjectsManagementPluginSetup {
actions: SavedObjectsManagementActionServiceSetup;
columns: SavedObjectsManagementColumnServiceSetup;
@ -31,6 +34,14 @@ export interface SavedObjectsManagementPluginSetup {
export interface SavedObjectsManagementPluginStart {
actions: SavedObjectsManagementActionServiceStart;
columns: SavedObjectsManagementColumnServiceStart;
getAllowedTypes: () => Promise<SavedObjectManagementTypeInfo[]>;
getRelationships: (
type: string,
id: string,
savedObjectTypes: string[]
) => Promise<SavedObjectGetRelationshipsResponse>;
getSavedObjectLabel: typeof getSavedObjectLabel;
getDefaultTitle: typeof getDefaultTitle;
}
export interface SetupDependencies {
@ -109,6 +120,11 @@ export class SavedObjectsManagementPlugin
return {
actions: actionStart,
columns: columnStart,
getAllowedTypes: () => getAllowedTypes(_core.http),
getRelationships: (type: string, id: string, savedObjectTypes: string[]) =>
getRelationships(_core.http, type, id, savedObjectTypes),
getSavedObjectLabel,
getDefaultTitle,
};
}
}

View file

@ -13,4 +13,5 @@ export type {
SavedObjectRelation,
SavedObjectInvalidRelation,
SavedObjectGetRelationshipsResponse,
SavedObjectManagementTypeInfo,
} from '../common';

View file

@ -0,0 +1,35 @@
/*
* 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 expect from '@kbn/expect';
import { FtrProviderContext } from '../../ftr_provider_context';
export default function ({ getService, getPageObjects }: FtrProviderContext) {
const kibanaServer = getService('kibanaServer');
const browser = getService('browser');
const PageObjects = getPageObjects(['common', 'home', 'settings', 'discover', 'header']);
describe('data view relationships', function describeIndexTests() {
before(async function () {
await browser.setWindowSize(1200, 800);
await kibanaServer.importExport.load('test/functional/fixtures/kbn_archiver/discover');
});
after(async () => {
await kibanaServer.importExport.unload('test/functional/fixtures/kbn_archiver/discover');
});
it('Render relationships tab and verify count', async function () {
await PageObjects.settings.navigateTo();
await PageObjects.settings.clickKibanaIndexPatterns();
await PageObjects.settings.clickIndexPatternLogstash();
await PageObjects.settings.clickRelationshipsTab();
expect(parseInt(await PageObjects.settings.getRelationshipsTabCount(), 10)).to.be(1);
});
});
}

View file

@ -40,5 +40,6 @@ export default function ({ getService, loadTestFile }: FtrProviderContext) {
loadTestFile(require.resolve('./_test_huge_fields'));
loadTestFile(require.resolve('./_handle_alias'));
loadTestFile(require.resolve('./_handle_version_conflict'));
loadTestFile(require.resolve('./_data_view_relationships'));
});
}

View file

@ -244,6 +244,13 @@ export class SettingsPageObject extends FtrService {
});
}
async getRelationshipsTabCount() {
return await this.retry.try(async () => {
const text = await this.testSubjects.getVisibleText('tab-relationships');
return text.split(' ')[1].replace(/\((.*)\)/, '$1');
});
}
async getFieldNames() {
const fieldNameCells = await this.testSubjects.findAll('editIndexPattern > indexedFieldName');
return await Promise.all(
@ -562,6 +569,11 @@ export class SettingsPageObject extends FtrService {
await this.testSubjects.click('tab-sourceFilters');
}
async clickRelationshipsTab() {
this.log.debug('click Relationships tab');
await this.testSubjects.click('tab-relationships');
}
async editScriptedField(name: string) {
await this.filterField(name);
await this.find.clickByCssSelector('.euiTableRowCell--hasActions button:first-child');