[tsvb] read only mode (#157920)

part of https://github.com/elastic/kibana/issues/154307

PR adds ability to put TSVB into read only mode - preventing TSVB
visualizations from being created and edited.

To test:
* start kibana with `yarn start --serverless=es`
* add `vis_type_timeseries.readOnly: true` to kibana.yml

Visualization public plugin changes:
* Removes `hideTypes` from VisualizationSetup contract. Used by Maps
plugin to set "hidden" to true for tile_map and region_map visualization
types. In 8.0, tile_map and region_map visualization type registration
moved into maps plugin so `hideTypes` no longer needed.
* Renamed vis type definition `hidden` to `disableCreate`.
* Added `disableEdit` to vis type definition.
* Hide edit link in dashboard panel options when `disableEdit` is true
* Does not display links and edit action in listing table when
`disableEdit` is true

Visualization server plugin changes:
* Add `readOnlyVisType` registry to set up contract
* Update visualization savedObject.management.getInAppUrl to return
undefined when vis type has been registered as readOnly.
* Prevents "readOnly "visualization types from being displayed in global
search results
* Prevents "readOnly "visualization types from having links in saved
object management listing table.

Timeseries server plugin changes:
* Add `readOnly` yaml configuration
* Expose `readOnly` yaml configuration to public
* When `readOnly` is true, call
VisualizationsServerSetup.registerReadOnlyVisType to mark vis type as
read only

Timeseries public plugin changes:
* Set disableCreate and disableEdit to true when `readOnly` is true

---------

Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
Co-authored-by: Stratoula Kalafateli <efstratia.kalafateli@elastic.co>
This commit is contained in:
Nathan Reese 2023-05-24 15:28:25 -06:00 committed by GitHub
parent 123e535754
commit 34e26250f0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
40 changed files with 221 additions and 73 deletions

View file

@ -7,7 +7,7 @@
*/
import React, { useCallback, useMemo } from 'react';
import { EuiText, EuiLink, EuiTitle, EuiSpacer, EuiHighlight } from '@elastic/eui';
import { EuiText, EuiLink, EuiSpacer, EuiHighlight } from '@elastic/eui';
import { RedirectAppLinks } from '@kbn/shared-ux-link-redirect-app';
import type { Tag } from '../types';
@ -104,9 +104,9 @@ export function ItemDetails<T extends UserContentCommonSchema>({
return (
<div>
<EuiTitle size="xs">{renderTitle()}</EuiTitle>
<EuiText size="s">{renderTitle()}</EuiText>
{Boolean(description) && (
<EuiText size="s">
<EuiText size="s" color="subdued">
<p>
<EuiHighlight highlightAll search={escapeRegExp(searchTerm)}>
{description!}

View file

@ -87,7 +87,14 @@ export interface Props<T extends UserContentCommonSchema = UserContentCommonSche
onClickTitle?: (item: T) => void;
createItem?(): void;
deleteItems?(items: T[]): Promise<void>;
/**
* Edit action onClick handler. Edit action not provided when property is not provided
*/
editItem?(item: T): void;
/**
* Handler to set edit action visiblity per item.
*/
showEditActionForItem?(item: T): boolean;
/**
* Name for the column containing the "title" value.
*/
@ -251,6 +258,7 @@ function TableListViewComp<T extends UserContentCommonSchema>({
findItems,
createItem,
editItem,
showEditActionForItem,
deleteItems,
getDetailViewLink,
onClickTitle,
@ -523,6 +531,7 @@ function TableListViewComp<T extends UserContentCommonSchema>({
),
icon: 'pencil',
type: 'icon',
available: (v) => (showEditActionForItem ? showEditActionForItem(v) : true),
enabled: (v) => !(v as unknown as { error: string })?.error,
onClick: editItem,
});
@ -577,6 +586,7 @@ function TableListViewComp<T extends UserContentCommonSchema>({
DateFormatterComp,
contentEditor,
inspectItem,
showEditActionForItem,
]);
const itemsById = useMemo(() => {

View file

@ -56,14 +56,16 @@ export interface SavedObjectsTypeManagementDefinition<Attributes = any> {
* Function returning the url to use to redirect to this object from the management section.
* If not defined, redirecting to the object will not be allowed.
*
* @returns an object containing a `path` and `uiCapabilitiesPath` properties. the `path` is the path to
* @returns undefined or an object containing a `path` and `uiCapabilitiesPath` properties. the `path` is the path to
* the object page, relative to the base path. `uiCapabilitiesPath` is the path to check in the
* {@link Capabilities | uiCapabilities} to check if the user has permission to access the object.
*/
getInAppUrl?: (savedObject: SavedObject<Attributes>) => {
path: string;
uiCapabilitiesPath: string;
};
getInAppUrl?: (savedObject: SavedObject<Attributes>) =>
| {
path: string;
uiCapabilitiesPath: string;
}
| undefined;
/**
* An optional export transform function that can be used transform the objects of the registered type during
* the export process.

View file

@ -96,7 +96,7 @@ export const EditorMenu = ({ createNewVisType, createNewEmbeddable }: Props) =>
}
return 0;
})
.filter(({ hidden, stage }: BaseVisType) => !hidden);
.filter(({ disableCreate, stage }: BaseVisType) => !disableCreate);
const promotedVisTypes = getSortedVisTypesByGroup(VisGroups.PROMOTED);
const aggsBasedVisTypes = getSortedVisTypesByGroup(VisGroups.AGGBASED);

View file

@ -29,7 +29,7 @@ export function createInputControlVisTypeDefinition(
defaultMessage: 'Input controls are deprecated and will be removed in a future version.',
}),
stage: 'experimental',
hidden: true,
disableCreate: true,
isDeprecated: true,
visConfig: {
defaults: {

View file

@ -20,3 +20,4 @@ export const ROUTES = {
};
export const USE_KIBANA_INDEXES_KEY = 'use_kibana_indexes';
export const TSVB_DEFAULT_COLOR = '#68BC00';
export const VIS_TYPE = 'metrics';

View file

@ -11,6 +11,13 @@ import { schema, TypeOf } from '@kbn/config-schema';
export const config = schema.object({
enabled: schema.boolean({ defaultValue: true }),
readOnly: schema.conditional(
schema.contextRef('serverless'),
true,
schema.maybe(schema.boolean({ defaultValue: false })),
schema.never()
),
/** @deprecated **/
chartResolution: schema.number({ defaultValue: 150 }),
/** @deprecated **/
@ -18,3 +25,7 @@ export const config = schema.object({
});
export type VisTypeTimeseriesConfig = TypeOf<typeof config>;
export interface VisTypeTimeseriesPublicConfig {
readOnly?: boolean;
}

View file

@ -7,8 +7,11 @@
*/
import { PluginInitializerContext } from '@kbn/core/public';
import { VisTypeTimeseriesPublicConfig } from '../config';
import { MetricsPlugin as Plugin } from './plugin';
export function plugin(initializerContext: PluginInitializerContext) {
export function plugin(
initializerContext: PluginInitializerContext<VisTypeTimeseriesPublicConfig>
) {
return new Plugin(initializerContext);
}

View file

@ -23,7 +23,7 @@ import {
extractIndexPatternValues,
isStringTypeIndexPattern,
} from '../common/index_patterns_utils';
import { TSVB_DEFAULT_COLOR, UI_SETTINGS } from '../common/constants';
import { TSVB_DEFAULT_COLOR, UI_SETTINGS, VIS_TYPE } from '../common/constants';
import { toExpressionAst } from './to_ast';
import { getDataViewsStart, getUISettings } from './services';
import type { TimeseriesVisDefaultParams, TimeseriesVisParams } from './types';
@ -99,7 +99,7 @@ async function getUsedIndexPatterns(params: VisParams): Promise<DataView[]> {
export const metricsVisDefinition: VisTypeDefinition<
TimeseriesVisParams | TimeseriesVisDefaultParams
> = {
name: 'metrics',
name: VIS_TYPE,
title: i18n.translate('visTypeTimeseries.kbnVisTypes.metricsTitle', { defaultMessage: 'TSVB' }),
description: i18n.translate('visTypeTimeseries.kbnVisTypes.metricsDescription', {
defaultMessage: 'Perform advanced analysis of your time series data.',

View file

@ -20,6 +20,7 @@ import type { HttpSetup } from '@kbn/core-http-browser';
import type { ThemeServiceStart } from '@kbn/core-theme-browser';
import type { DocLinksStart } from '@kbn/core-doc-links-browser';
import type { IStorageWrapper } from '@kbn/kibana-utils-plugin/public';
import { VisTypeTimeseriesPublicConfig } from '../config';
import { EditorController, TSVB_EDITOR_NAME } from './application/editor_controller';
@ -71,13 +72,15 @@ export interface TimeseriesVisDependencies extends Partial<CoreStart> {
/** @internal */
export class MetricsPlugin implements Plugin<void, void> {
initializerContext: PluginInitializerContext;
initializerContext: PluginInitializerContext<VisTypeTimeseriesPublicConfig>;
constructor(initializerContext: PluginInitializerContext) {
constructor(initializerContext: PluginInitializerContext<VisTypeTimeseriesPublicConfig>) {
this.initializerContext = initializerContext;
}
public setup(core: CoreSetup, { expressions, visualizations }: MetricsPluginSetupDependencies) {
const { readOnly } = this.initializerContext.config.get<VisTypeTimeseriesPublicConfig>();
visualizations.visEditorsRegistry.register(TSVB_EDITOR_NAME, EditorController);
expressions.registerFunction(createMetricsFn);
expressions.registerRenderer(
@ -87,7 +90,11 @@ export class MetricsPlugin implements Plugin<void, void> {
})
);
setUISettings(core.uiSettings);
visualizations.createBaseVisualization(metricsVisDefinition);
visualizations.createBaseVisualization({
...metricsVisDefinition,
disableCreate: Boolean(readOnly),
disableEdit: Boolean(readOnly),
});
}
public start(

View file

@ -7,12 +7,17 @@
*/
import { PluginInitializerContext, PluginConfigDescriptor } from '@kbn/core/server';
import { VisTypeTimeseriesConfig, config as configSchema } from './config';
import { VisTypeTimeseriesConfig, config as configSchema } from '../config';
import { VisTypeTimeseriesPlugin } from './plugin';
export type { VisTypeTimeseriesSetup } from './plugin';
export const config: PluginConfigDescriptor<VisTypeTimeseriesConfig> = {
// exposeToBrowser specifies kibana.yml settings to expose to the browser
// the value `true` in this context signals configuration is exposed to browser
exposeToBrowser: {
readOnly: true,
},
schema: configSchema,
};

View file

@ -24,7 +24,9 @@ import type { DataViewsService } from '@kbn/data-views-plugin/common';
import type { FieldFormatsStart } from '@kbn/field-formats-plugin/server';
import type { PluginStart as DataViewsPublicPluginStart } from '@kbn/data-views-plugin/server';
import type { FieldFormatsRegistry } from '@kbn/field-formats-plugin/common';
import { VisTypeTimeseriesConfig } from './config';
import type { VisualizationsServerSetup } from '@kbn/visualizations-plugin/server';
import { VIS_TYPE } from '../common/constants';
import { VisTypeTimeseriesConfig } from '../config';
import { getVisData } from './lib/get_vis_data';
import { visDataRoutes } from './routes/vis';
import { fieldsRoutes } from './routes/fields';
@ -47,6 +49,7 @@ export interface LegacySetup {
interface VisTypeTimeseriesPluginSetupDependencies {
home?: HomeServerPluginSetup;
visualizations: VisualizationsServerSetup;
}
interface VisTypeTimeseriesPluginStartDependencies {
@ -126,6 +129,11 @@ export class VisTypeTimeseriesPlugin implements Plugin<VisTypeTimeseriesSetup> {
visDataRoutes(router, framework);
fieldsRoutes(router, framework);
const { readOnly } = this.initializerContext.config.get<VisTypeTimeseriesConfig>();
if (readOnly) {
plugins.visualizations.registerReadOnlyVisType(VIS_TYPE);
}
return {
getVisData: async (
requestContext: VisTypeTimeseriesRequestHandlerContext,

View file

@ -170,10 +170,12 @@ export class VisualizeEmbeddable
this.attributeService = attributeService;
if (this.attributeService) {
const readOnly = Boolean(vis.type.disableEdit);
const isByValue = !this.inputIsRefType(initialInput);
const editable =
capabilities.visualizeSave ||
(isByValue && capabilities.dashboardSave && capabilities.visualizeOpen);
const editable = readOnly
? false
: capabilities.visualizeSave ||
(isByValue && capabilities.dashboardSave && capabilities.visualizeOpen);
this.updateOutput({ ...this.getOutput(), editable });
}

View file

@ -33,7 +33,6 @@ import { Schema, VisualizationsSetup, VisualizationsStart } from '.';
const createSetupContract = (): VisualizationsSetup => ({
createBaseVisualization: jest.fn(),
registerAlias: jest.fn(),
hideTypes: jest.fn(),
visEditorsRegistry: { registerDefault: jest.fn(), register: jest.fn(), get: jest.fn() },
});

View file

@ -499,6 +499,8 @@ describe('saved_visualize_utils', () => {
},
{
id: 'wat',
image: undefined,
readOnly: false,
references: undefined,
icon: undefined,
savedObjectType: 'visualization',
@ -506,6 +508,7 @@ describe('saved_visualize_utils', () => {
type: 'test',
typeName: 'test',
typeTitle: undefined,
updatedAt: undefined,
title: 'WATEVER',
url: '#/edit/wat',
},

View file

@ -80,6 +80,7 @@ export function mapHitSource(
image?: BaseVisType['image'];
typeTitle?: BaseVisType['title'];
error?: string;
readOnly?: boolean;
} = {
id,
references,
@ -108,6 +109,7 @@ export function mapHitSource(
newAttributes.image = newAttributes.type?.image;
newAttributes.typeTitle = newAttributes.type?.title;
newAttributes.editUrl = `/edit/${id}`;
newAttributes.readOnly = Boolean(visTypes.get(typeName as string)?.disableEdit);
return newAttributes;
}

View file

@ -38,7 +38,8 @@ export class BaseVisType<TVisParams = VisParams> {
public readonly options: VisTypeOptions;
public readonly visConfig;
public readonly editorConfig;
public hidden;
public readonly disableCreate;
public readonly disableEdit;
public readonly requiresSearch;
public readonly suppressWarnings;
public readonly hasPartialRows;
@ -74,7 +75,8 @@ export class BaseVisType<TVisParams = VisParams> {
this.isDeprecated = opts.isDeprecated ?? false;
this.group = opts.group ?? VisGroups.AGGBASED;
this.titleInWizard = opts.titleInWizard ?? '';
this.hidden = opts.hidden ?? false;
this.disableCreate = opts.disableCreate ?? false;
this.disableEdit = opts.disableEdit ?? false;
this.requiresSearch = opts.requiresSearch ?? false;
this.setup = opts.setup;
this.hasPartialRows = opts.hasPartialRows ?? false;

View file

@ -199,7 +199,10 @@ export interface VisTypeDefinition<TVisParams> {
readonly updateVisTypeOnParamsChange?: (params: VisParams) => string | undefined;
readonly setup?: (vis: Vis<TVisParams>) => Promise<Vis<TVisParams>>;
hidden?: boolean;
disableCreate?: boolean;
disableEdit?: boolean;
readonly options?: Partial<VisTypeOptions>;

View file

@ -18,13 +18,8 @@ import { VisGroups } from './vis_groups_enum';
*/
export class TypesService {
private types: Record<string, BaseVisType<any>> = {};
private unregisteredHiddenTypes: string[] = [];
private registerVisualization<TVisParam>(visDefinition: BaseVisType<TVisParam>) {
if (this.unregisteredHiddenTypes.includes(visDefinition.name)) {
visDefinition.hidden = true;
}
if (this.types[visDefinition.name]) {
throw new Error('type already exists!');
}
@ -47,19 +42,6 @@ export class TypesService {
* @param {VisTypeAlias} config - visualization alias definition
*/
registerAlias: visTypeAliasRegistry.add,
/**
* allows to hide specific visualization types from create visualization dialog
* @param {string[]} typeNames - list of type ids to hide
*/
hideTypes: (typeNames: string[]): void => {
typeNames.forEach((name: string) => {
if (this.types[name]) {
this.types[name].hidden = true;
} else {
this.unregisteredHiddenTypes.push(name);
}
});
},
};
}

View file

@ -44,7 +44,14 @@ export interface VisTypeAlias {
note?: string;
getSupportedTriggers?: () => string[];
stage: VisualizationStage;
hidden?: boolean;
/*
* Set to true to hide visualization type in create UIs.
*/
disableCreate?: boolean;
/*
* Set to true to hide edit links for visualization type in UIs.
*/
disableEdit?: boolean;
isDeprecated?: boolean;
appExtensions?: {

View file

@ -42,6 +42,7 @@ interface VisualizeUserContent extends VisualizationListItem, UserContentCommonS
description?: string;
editApp: string;
editUrl: string;
readOnly: boolean;
error?: string;
};
}
@ -65,6 +66,7 @@ const toTableListViewSavedObject = (savedObject: Record<string, unknown>): Visua
description: savedObject.description as string,
editApp: savedObject.editApp as string,
editUrl: savedObject.editUrl as string,
readOnly: savedObject.readOnly as boolean,
error: savedObject.error as string,
},
};
@ -291,6 +293,9 @@ export const VisualizeListing = () => {
findItems={fetchItems}
deleteItems={visualizeCapabilities.delete ? deleteItems : undefined}
editItem={visualizeCapabilities.save ? editItem : undefined}
showEditActionForItem={({ attributes: { readOnly } }) =>
visualizeCapabilities.save && !readOnly
}
customTableColumn={getCustomColumn()}
listingLimit={listingLimit}
initialPageSize={initialPageSize}
@ -310,8 +315,10 @@ export const VisualizeListing = () => {
tableListTitle={i18n.translate('visualizations.listing.table.listTitle', {
defaultMessage: 'Visualize Library',
})}
getDetailViewLink={({ attributes: { editApp, editUrl, error } }) =>
getVisualizeListItemLink(core.application, kbnUrlStateStorage, editApp, editUrl, error)
getDetailViewLink={({ attributes: { editApp, editUrl, error, readOnly } }) =>
readOnly
? undefined
: getVisualizeListItemLink(core.application, kbnUrlStateStorage, editApp, editUrl, error)
}
>
{dashboardCapabilities.createNew && (

View file

@ -83,6 +83,16 @@ export const useSavedVisInstance = (
savedVisInstance = await getVisualizationInstance(services, visualizationIdFromUrl);
}
if (savedVisInstance.vis.type.disableEdit) {
throw new Error(
i18n.translate('visualizations.editVisualization.readOnlyErrorMessage', {
defaultMessage:
'{visTypeTitle} visualizations are read only and can not be opened in editor',
values: { visTypeTitle: savedVisInstance.vis.type.title },
})
);
}
if (embeddableInput && embeddableInput.timeRange) {
savedVisInstance.panelTimeRange = embeddableInput.timeRange;
}

View file

@ -100,7 +100,7 @@ class AggBasedSelection extends React.Component<AggBasedSelectionProps, AggBased
private filteredVisTypes(visTypes: TypesStart, query: string): VisTypeListEntry[] {
const types = visTypes.getByGroup(VisGroups.AGGBASED).filter((type) => {
// Filter out hidden visualizations and visualizations that are only aggregations based
return !type.hidden;
return !type.disableCreate;
});
let entries: VisTypeListEntry[];

View file

@ -57,7 +57,9 @@ function GroupSelection(props: GroupSelectionProps) {
[
...props.visTypesRegistry.getAliases(),
...props.visTypesRegistry.getByGroup(VisGroups.PROMOTED),
],
].filter((visDefinition) => {
return !Boolean(visDefinition.disableCreate);
}),
['promotion', 'title'],
['asc', 'asc']
),
@ -217,7 +219,7 @@ const ToolsGroup = ({ visType, onVisTypeSelected, showExperimental }: VisCardPro
}, [onVisTypeSelected, visType]);
// hide both the hidden visualizations and, if lab mode is not enabled, the experimental visualizations
// TODO: Remove the showExperimental logic as part of https://github.com/elastic/kibana/issues/152833
if (visType.hidden || (!showExperimental && visType.stage === 'experimental')) {
if (visType.disableCreate || (!showExperimental && visType.stage === 'experimental')) {
return null;
}
return (

View file

@ -17,7 +17,8 @@ import { savedObjectsManagementPluginMock } from '@kbn/saved-objects-management-
describe('NewVisModal', () => {
const defaultVisTypeParams = {
hidden: false,
disableCreate: false,
disableEdit: false,
requiresSearch: false,
};
const _visTypes = [

View file

@ -16,4 +16,4 @@ export function plugin(initializerContext: PluginInitializerContext) {
return new VisualizationsPlugin(initializerContext);
}
export type { VisualizationsPluginSetup, VisualizationsPluginStart } from './types';
export type { VisualizationsServerSetup, VisualizationsServerStart } from './types';

View file

@ -19,13 +19,13 @@ import { ContentManagementServerSetup } from '@kbn/content-management-plugin/ser
import { capabilitiesProvider } from './capabilities_provider';
import { VisualizationsStorage } from './content_management';
import type { VisualizationsPluginSetup, VisualizationsPluginStart } from './types';
import type { VisualizationsServerSetup, VisualizationsServerStart } from './types';
import { makeVisualizeEmbeddableFactory } from './embeddable/make_visualize_embeddable_factory';
import { getVisualizationSavedObjectType } from './saved_objects';
import { getVisualizationSavedObjectType, registerReadOnlyVisType } from './saved_objects';
import { CONTENT_ID, LATEST_VERSION } from '../common/content_management';
export class VisualizationsPlugin
implements Plugin<VisualizationsPluginSetup, VisualizationsPluginStart>
implements Plugin<VisualizationsServerSetup, VisualizationsServerStart>
{
private readonly logger: Logger;
@ -61,7 +61,7 @@ export class VisualizationsPlugin
},
});
return {};
return { registerReadOnlyVisType };
}
public start(core: CoreStart) {

View file

@ -0,0 +1,36 @@
/*
* 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 { VisualizationSavedObject } from '../../common/content_management';
import { registerReadOnlyVisType } from './read_only_vis_type_registry';
import { getInAppUrl } from './get_in_app_url';
registerReadOnlyVisType('myLegacyVis');
test('should return visualize edit url', () => {
const obj = {
id: '1',
attributes: {
visState: JSON.stringify({ type: 'vega' }),
},
} as unknown as VisualizationSavedObject;
expect(getInAppUrl(obj)).toEqual({
path: '/app/visualize#/edit/1',
uiCapabilitiesPath: 'visualize.show',
});
});
test('should return undefined when visualization type is read only', () => {
const obj = {
id: '1',
attributes: {
visState: JSON.stringify({ type: 'myLegacyVis' }),
},
} as unknown as VisualizationSavedObject;
expect(getInAppUrl(obj)).toBeUndefined();
});

View file

@ -0,0 +1,29 @@
/*
* 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 { VisualizationSavedObject } from '../../common/content_management';
import { isVisTypeReadOnly } from './read_only_vis_type_registry';
export function getInAppUrl(obj: VisualizationSavedObject) {
let visType: string | undefined;
if (obj.attributes.visState) {
try {
const visState = JSON.parse(obj.attributes.visState);
visType = visState?.type;
} catch (e) {
// let client display warning for unparsable visState
}
}
return isVisTypeReadOnly(visType)
? undefined
: {
path: `/app/visualize#/edit/${encodeURIComponent(obj.id)}`,
uiCapabilitiesPath: 'visualize.show',
};
}

View file

@ -7,3 +7,4 @@
*/
export { getVisualizationSavedObjectType } from './visualization';
export { registerReadOnlyVisType } from './read_only_vis_type_registry';

View file

@ -0,0 +1,17 @@
/*
* 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.
*/
const registry: string[] = [];
export function registerReadOnlyVisType(visType: string) {
registry.push(visType);
}
export function isVisTypeReadOnly(visType?: string) {
return visType ? registry.includes(visType) : false;
}

View file

@ -12,6 +12,7 @@ import { SavedObjectsType } from '@kbn/core/server';
import { MigrateFunctionsObject } from '@kbn/kibana-utils-plugin/common';
import { CONTENT_ID } from '../../common/content_management';
import { getAllMigrations } from '../migrations/visualization_saved_object_migrations';
import { getInAppUrl } from './get_in_app_url';
export const getVisualizationSavedObjectType = (
getSearchSourceMigrations: () => MigrateFunctionsObject
@ -28,12 +29,7 @@ export const getVisualizationSavedObjectType = (
getTitle(obj) {
return obj.attributes.title;
},
getInAppUrl(obj) {
return {
path: `/app/visualize#/edit/${encodeURIComponent(obj.id)}`,
uiCapabilitiesPath: 'visualize.show',
};
},
getInAppUrl,
},
mappings: {
dynamic: false, // declared here to prevent indexing root level attribute fields

View file

@ -6,7 +6,8 @@
* Side Public License, v 1.
*/
export interface VisualizationsServerSetup {
registerReadOnlyVisType: (visType: string) => void;
}
// eslint-disable-next-line @typescript-eslint/no-empty-interface
export interface VisualizationsPluginSetup {}
// eslint-disable-next-line @typescript-eslint/no-empty-interface
export interface VisualizationsPluginStart {}
export interface VisualizationsServerStart {}

View file

@ -160,6 +160,7 @@ export default function ({ getService }: PluginFunctionalProviderContext) {
'telemetry.sendUsageTo (any)',
'usageCollection.uiCounters.debug (boolean)',
'usageCollection.uiCounters.enabled (boolean)',
'vis_type_timeseries.readOnly (any)',
'vis_type_vega.enableExternalUrls (boolean)',
'xpack.actions.email.domain_allowlist (array)',
'xpack.apm.serviceMapEnabled (boolean)',

View file

@ -134,7 +134,7 @@ export const EditorMenu: FC<Props> = ({ addElement }) => {
}
return 0;
})
.filter(({ hidden }: BaseVisType) => !hidden);
.filter(({ disableCreate }: BaseVisType) => !disableCreate);
const visTypeAliases = visualizationsService
.getAliases()

View file

@ -33,8 +33,8 @@ const isAccessible = (
if (getInAppUrl === undefined) {
throw new Error('Trying to map an object from a type without management metadata');
}
const { uiCapabilitiesPath } = getInAppUrl(object);
return Boolean(get(capabilities, uiCapabilitiesPath) ?? false);
const inAppUrl = getInAppUrl(object);
return inAppUrl ? Boolean(get(capabilities, inAppUrl.uiCapabilitiesPath) ?? false) : false;
};
export const mapToResult = (
@ -52,7 +52,7 @@ export const mapToResult = (
title: getTitle ? getTitle(object) : (object.attributes as any)[defaultSearchField],
type: object.type,
icon: type.management?.icon ?? undefined,
url: getInAppUrl(object).path,
url: getInAppUrl(object)!.path,
score: object.score,
meta: {
tagIds: object.references.filter((ref) => ref.type === 'tag').map(({ id }) => id),

View file

@ -49,4 +49,5 @@ export const regionMapVisType = {
},
toExpressionAst,
requiresSearch: true,
disableCreate: true,
} as VisTypeDefinition<RegionMapVisParams>;

View file

@ -50,4 +50,5 @@ export const tileMapVisType = {
},
toExpressionAst,
requiresSearch: true,
disableCreate: true,
} as VisTypeDefinition<TileMapVisParams>;

View file

@ -6,7 +6,7 @@
*/
import { i18n } from '@kbn/i18n';
import type { VisualizationsSetup, VisualizationStage } from '@kbn/visualizations-plugin/public';
import type { VisualizationStage } from '@kbn/visualizations-plugin/public';
import type { MapItem } from '../common/content_management';
import {
APP_ID,
@ -17,9 +17,7 @@ import {
MAP_SAVED_OBJECT_TYPE,
} from '../common/constants';
export function getMapsVisTypeAlias(visualizations: VisualizationsSetup) {
visualizations.hideTypes(['region_map', 'tile_map']);
export function getMapsVisTypeAlias() {
const appDescription = i18n.translate('xpack.maps.visTypeAlias.description', {
defaultMessage: 'Create and style maps with multiple layers and indices.',
});

View file

@ -185,7 +185,7 @@ export class MapsPlugin
if (plugins.home) {
plugins.home.featureCatalogue.register(featureCatalogueEntry);
}
plugins.visualizations.registerAlias(getMapsVisTypeAlias(plugins.visualizations));
plugins.visualizations.registerAlias(getMapsVisTypeAlias());
plugins.embeddable.registerEmbeddableFactory(MAP_SAVED_OBJECT_TYPE, new MapEmbeddableFactory());
core.application.register({