mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 09:19:04 -04:00
[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:
parent
123e535754
commit
34e26250f0
40 changed files with 221 additions and 73 deletions
|
@ -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!}
|
||||
|
|
|
@ -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(() => {
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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: {
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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.',
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 });
|
||||
}
|
||||
|
||||
|
|
|
@ -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() },
|
||||
});
|
||||
|
||||
|
|
|
@ -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',
|
||||
},
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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>;
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
});
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -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?: {
|
||||
|
|
|
@ -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 && (
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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[];
|
||||
|
|
|
@ -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 (
|
||||
|
|
|
@ -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 = [
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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();
|
||||
});
|
|
@ -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',
|
||||
};
|
||||
}
|
|
@ -7,3 +7,4 @@
|
|||
*/
|
||||
|
||||
export { getVisualizationSavedObjectType } from './visualization';
|
||||
export { registerReadOnlyVisType } from './read_only_vis_type_registry';
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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 {}
|
||||
|
|
|
@ -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)',
|
||||
|
|
|
@ -134,7 +134,7 @@ export const EditorMenu: FC<Props> = ({ addElement }) => {
|
|||
}
|
||||
return 0;
|
||||
})
|
||||
.filter(({ hidden }: BaseVisType) => !hidden);
|
||||
.filter(({ disableCreate }: BaseVisType) => !disableCreate);
|
||||
|
||||
const visTypeAliases = visualizationsService
|
||||
.getAliases()
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -49,4 +49,5 @@ export const regionMapVisType = {
|
|||
},
|
||||
toExpressionAst,
|
||||
requiresSearch: true,
|
||||
disableCreate: true,
|
||||
} as VisTypeDefinition<RegionMapVisParams>;
|
||||
|
|
|
@ -50,4 +50,5 @@ export const tileMapVisType = {
|
|||
},
|
||||
toExpressionAst,
|
||||
requiresSearch: true,
|
||||
disableCreate: true,
|
||||
} as VisTypeDefinition<TileMapVisParams>;
|
||||
|
|
|
@ -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.',
|
||||
});
|
||||
|
|
|
@ -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({
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue