Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
b28b798
:lipstick: Enable read only mode for the flyout
dej611 Jan 28, 2025
5f78ceb
:white_check_mark: Add tests
dej611 Jan 28, 2025
26f96b6
:sparkles: New show config action
dej611 Jan 28, 2025
2a67834
:white_check_mark: Add tests
dej611 Jan 28, 2025
7c3a49d
:sparkles: Enabled read only api
dej611 Jan 28, 2025
6c6f7f3
:white_check_mark: Add tests
dej611 Jan 28, 2025
88bea79
:wrench: Add read only typeguards
dej611 Jan 28, 2025
1ab9a8a
:sparkles: Add action complementary functions
dej611 Jan 28, 2025
ff5cdcd
:wrench: Export new action
dej611 Jan 28, 2025
9536d39
:sparkles: Enable show config action on hover quick actions
dej611 Jan 28, 2025
233ae77
:recycle: Refactor edit/show logic
dej611 Jan 28, 2025
4d5f159
:white_check_mark: Add tests
dej611 Jan 28, 2025
ba66538
:lipstick: Change icon to wrench
dej611 Jan 28, 2025
34ff73b
:sparkles: Enable read only panels in embeddable
dej611 Jan 28, 2025
8fa0749
Merge remote-tracking branch 'upstream/main' into fix/106553
dej611 Jan 28, 2025
8980e3c
:label: Fix types
dej611 Jan 29, 2025
af5eb85
:label: Fix type
dej611 Jan 29, 2025
903d98b
:wrench: Updated limits
dej611 Jan 29, 2025
77606e6
Update src/platform/packages/shared/presentation/presentation_publish…
dej611 Jan 29, 2025
37adbd8
:fire: Remove unused module
dej611 Jan 29, 2025
2078fc3
:wrench: Update limits
dej611 Jan 29, 2025
f9d4fa0
Merge branch 'fix/106553' of https://github.com/dej611/kibana into fi…
dej611 Jan 29, 2025
1cf4957
:sparkles: Enhance return type
dej611 Jan 31, 2025
8857562
:sparkles: Make title and icon dynamic based on permissions
dej611 Jan 31, 2025
faaf03f
:sparkles: Make title dynamic based on context
dej611 Jan 31, 2025
57baf06
:white_check_mark: Enhance return type and add more test scenarios
dej611 Jan 31, 2025
faa7c27
:ok_hand: Integrated feedback
dej611 Jan 31, 2025
2c17bc5
Merge remote-tracking branch 'upstream/main' into fix/106553
dej611 Jan 31, 2025
554bb50
:label: Fix types with the new names
dej611 Jan 31, 2025
ad3ec2c
Merge branch 'main' into fix/106553
dej611 Feb 3, 2025
8bb8030
:ok_hand: Integrate new ux feedback
dej611 Feb 5, 2025
ed6f8f6
:white_check_mark: Update tests with managed scenarios
dej611 Feb 5, 2025
22607f2
:fire: remove unused translations
dej611 Feb 5, 2025
d90ca72
Merge branch 'main' into fix/106553
dej611 Feb 6, 2025
1818e1e
Merge branch 'main' into fix/106553
dej611 Feb 7, 2025
101890d
Merge branch 'main' into fix/106553
dej611 Feb 10, 2025
c661857
:ok_hand: Apply suggestions from code review
dej611 Feb 11, 2025
659235f
Merge branch 'main' into fix/106553
dej611 Feb 11, 2025
98af290
:fire: Removed unused translations
dej611 Feb 11, 2025
4ae9ab1
Merge remote-tracking branch 'upstream/main' into fix/106553
dej611 Feb 18, 2025
22f90b3
Merge remote-tracking branch 'upstream/main' into fix/106553
dej611 Mar 3, 2025
ba46222
:recycle: Sync with new changes
dej611 Mar 3, 2025
c3df978
:ok_hand: Integrate feedback
dej611 Mar 3, 2025
830429c
:white_check_mark: Adapt tests for new API
dej611 Mar 3, 2025
7662660
:white_check_mark: Add FTR test
dej611 Mar 3, 2025
139ca11
Merge remote-tracking branch 'upstream/main' into fix/106553
dej611 Mar 3, 2025
54deb76
:white_check_mark: Fix tests
dej611 Mar 3, 2025
dc028c6
Merge branch 'main' into fix/106553
dej611 Mar 5, 2025
d9d56e5
Update src/platform/plugins/private/presentation_panel/public/panel_a…
dej611 Mar 7, 2025
daabf8a
Merge branch 'main' into fix/106553
dej611 Mar 7, 2025
f9e9cf6
Update x-pack/platform/plugins/shared/lens/public/app_plugin/shared/e…
dej611 Mar 13, 2025
7077e65
Merge remote-tracking branch 'upstream/main' into fix/106553
dej611 Mar 14, 2025
File filter

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,10 @@ export {
type HasDisableTriggers,
} from './interfaces/has_disable_triggers';
export { hasEditCapabilities, type HasEditCapabilities } from './interfaces/has_edit_capabilities';
export {
hasReadOnlyCapabilities,
type HasReadOnlyCapabilities,
} from './interfaces/has_read_only_capabilities';
export {
apiHasExecutionContext,
type HasExecutionContext,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
* 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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/

import { HasTypeDisplayName } from './has_type';

/**
* An interface which determines whether or not a given API offers to show the config for read only permissions.
* In order to be read only, the api requires a show config function to execute the action
* a getTypeDisplayName function to display to the user which type of chart is being
* shown, and an isReadOnlyEnabled function.
*/
export interface HasReadOnlyCapabilities extends HasTypeDisplayName {
onShowConfig: () => Promise<void>;
isReadOnlyEnabled: () => { read: boolean; write: boolean };
}

/**
* A type guard which determines whether or not a given API is editable.
*/
export const hasReadOnlyCapabilities = (root: unknown): root is HasReadOnlyCapabilities => {
return Boolean(
root &&
typeof (root as HasReadOnlyCapabilities).onShowConfig === 'function' &&
typeof (root as HasReadOnlyCapabilities).getTypeDisplayName === 'function' &&
typeof (root as HasReadOnlyCapabilities).isReadOnlyEnabled === 'function'
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ export class EditPanelAction
public getDisplayName({ embeddable }: EmbeddableApiContext) {
if (!isApiCompatible(embeddable)) throw new IncompatibleActionError();
return i18n.translate('presentationPanel.action.editPanel.displayName', {
defaultMessage: 'Edit {value}',
defaultMessage: 'Edit {value} configuration',
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Change this to align with the show_config_action one when user has edit capabilities. Ideally both actions should be merged into either a single action or with a better controller in a follow up

values: {
value: embeddable.getTypeDisplayName(),
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
CUSTOM_TIME_RANGE_BADGE,
} from './customize_panel_action/constants';
import { CONTEXT_MENU_TRIGGER, PANEL_BADGE_TRIGGER } from './triggers';
import { ACTION_SHOW_CONFIG_PANEL } from './show_config_panel_action/constants';

export const registerActions = () => {
uiActions.registerActionAsync(ACTION_REMOVE_PANEL, async () => {
Expand Down Expand Up @@ -47,4 +48,10 @@ export const registerActions = () => {
return new CustomizePanelAction();
});
uiActions.attachAction(CONTEXT_MENU_TRIGGER, ACTION_CUSTOMIZE_PANEL);

uiActions.registerActionAsync(ACTION_SHOW_CONFIG_PANEL, async () => {
const { ShowConfigPanelAction } = await import('../panel_component/panel_module');
return new ShowConfigPanelAction();
});
uiActions.attachAction(CONTEXT_MENU_TRIGGER, ACTION_SHOW_CONFIG_PANEL);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/*
* 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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/

export const ACTION_SHOW_CONFIG_PANEL = 'ACTION_SHOW_CONFIG_PANEL';
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/*
* 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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/

import { PublishesViewMode, ViewMode } from '@kbn/presentation-publishing';
import { BehaviorSubject, take } from 'rxjs';
import { ShowConfigPanelAction, ShowConfigPanelActionApi } from './show_config_panel_action';

describe('Show config panel action', () => {
let action: ShowConfigPanelAction;
let context: { embeddable: ShowConfigPanelActionApi };
let updateViewMode: (viewMode: ViewMode) => void;

beforeEach(() => {
const viewModeSubject = new BehaviorSubject<ViewMode>('view');
updateViewMode = jest.fn((viewMode) => viewModeSubject.next(viewMode));

action = new ShowConfigPanelAction();
context = {
embeddable: {
viewMode$: viewModeSubject,
onShowConfig: jest.fn(),
isReadOnlyEnabled: jest.fn().mockReturnValue({ read: true, write: false }),
getTypeDisplayName: jest.fn().mockReturnValue('A very fun panel type'),
},
};
});

it('is compatible when api meets all conditions', async () => {
expect(await action.isCompatible(context)).toBe(true);
});

it('is incompatible when context lacks necessary functions', async () => {
const emptyContext = {
embeddable: {},
};
expect(await action.isCompatible(emptyContext)).toBe(false);
});

it('is incompatible when view mode is edit', async () => {
(context.embeddable as PublishesViewMode).viewMode$ = new BehaviorSubject<ViewMode>('edit');
expect(await action.isCompatible(context)).toBe(false);
});

it('is incompatible when view is not enabled', async () => {
context.embeddable.isReadOnlyEnabled = jest.fn().mockReturnValue({ read: false, write: false });
expect(await action.isCompatible(context)).toBe(false);
});

it('is incompatible when view mode is view but user has write permissions', async () => {
context.embeddable.isReadOnlyEnabled = jest.fn().mockReturnValue({ read: true, write: true });
expect(await action.isCompatible(context)).toBe(false);
});

it('should trigger a change ont he subject when changing viewMode', (done) => {
const subject$ = action.getCompatibilityChangesSubject(context);
subject$?.pipe(take(1)).subscribe(() => {
done();
});
updateViewMode('edit');
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
/*
* 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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/

import { i18n } from '@kbn/i18n';

import {
EmbeddableApiContext,
CanAccessViewMode,
apiCanAccessViewMode,
getInheritedViewMode,
getViewModeSubject,
HasReadOnlyCapabilities,
hasReadOnlyCapabilities,
} from '@kbn/presentation-publishing';
import {
Action,
FrequentCompatibilityChangeAction,
IncompatibleActionError,
} from '@kbn/ui-actions-plugin/public';
import { map } from 'rxjs';
import { ACTION_SHOW_CONFIG_PANEL } from './constants';

export type ShowConfigPanelActionApi = CanAccessViewMode & HasReadOnlyCapabilities;

const isApiCompatible = (api: unknown | null): api is ShowConfigPanelActionApi => {
return hasReadOnlyCapabilities(api) && apiCanAccessViewMode(api);
};

export class ShowConfigPanelAction
implements Action<EmbeddableApiContext>, FrequentCompatibilityChangeAction<EmbeddableApiContext>
{
public readonly type = ACTION_SHOW_CONFIG_PANEL;
public readonly id = ACTION_SHOW_CONFIG_PANEL;
public order = 50;

constructor() {}

public getDisplayName({ embeddable }: EmbeddableApiContext) {
if (!isApiCompatible(embeddable)) throw new IncompatibleActionError();
Comment thread
nreese marked this conversation as resolved.
return i18n.translate('presentationPanel.action.showConfigPanel.displayName', {
defaultMessage: 'Show {value} configuration',
values: {
value: embeddable.getTypeDisplayName(),
},
});
}

public getCompatibilityChangesSubject({ embeddable }: EmbeddableApiContext) {
return apiCanAccessViewMode(embeddable)
? getViewModeSubject(embeddable)?.pipe(map(() => undefined))
: undefined;
}

public couldBecomeCompatible({ embeddable }: EmbeddableApiContext) {
return isApiCompatible(embeddable);
}

public getIconType({ embeddable }: EmbeddableApiContext) {
if (!isApiCompatible(embeddable)) throw new IncompatibleActionError();
return 'glasses';
}

/**
* The compatible check is scoped to the read only capabilities
* Note: it does not take into account write permissions
*/
public async isCompatible({ embeddable }: EmbeddableApiContext) {
if (!isApiCompatible(embeddable) || getInheritedViewMode(embeddable) !== 'view') {
return false;
}
const { read: canRead, write: canWrite } = embeddable.isReadOnlyEnabled();
return Boolean(
// No option to view or edit the configuration is offered for users with write permission.
canRead && !canWrite
);
}

public async execute({ embeddable }: EmbeddableApiContext) {
if (!isApiCompatible(embeddable)) throw new IncompatibleActionError();
await embeddable.onShowConfig();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,13 @@ const QUICK_ACTION_IDS = {
'ACTION_OPEN_IN_DISCOVER',
'ACTION_VIEW_SAVED_SEARCH',
],
view: ['ACTION_OPEN_IN_DISCOVER', 'ACTION_VIEW_SAVED_SEARCH', 'openInspector', 'togglePanel'],
view: [
'ACTION_SHOW_CONFIG_PANEL',
'ACTION_OPEN_IN_DISCOVER',
'ACTION_VIEW_SAVED_SEARCH',
'openInspector',
'togglePanel',
],
} as const;

const ALLOWED_NOTIFICATIONS = ['ACTION_FILTERS_NOTIFICATION'] as const;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,5 @@ export { RemovePanelAction } from '../panel_actions/remove_panel_action/remove_p
export { CustomTimeRangeBadge } from '../panel_actions/customize_panel_action';
export { CustomizePanelAction } from '../panel_actions/customize_panel_action';
export { EditPanelAction } from '../panel_actions/edit_panel_action/edit_panel_action';
export { ShowConfigPanelAction } from '../panel_actions/show_config_panel_action/show_config_panel_action';
export { InspectPanelAction } from '../panel_actions/inspect_panel_action/inspect_panel_action';
10 changes: 10 additions & 0 deletions test/functional/page_objects/dashboard_page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,16 @@ export class DashboardPageObject extends FtrService {
return panels.length === dragHandles.length;
});
}
public async switchToViewMode() {
this.log.debug('Switching to view mode');
if (await this.testSubjects.exists('dashboardViewOnlyMode')) {
await this.testSubjects.click('dashboardViewOnlyMode');
}
// wait until edit button appears
await this.retry.waitFor('in view mode', async () => {
return this.testSubjects.exists('dashboardEditMode');
});
}

public async getIsInViewMode() {
this.log.debug('getIsInViewMode');
Expand Down
11 changes: 11 additions & 0 deletions test/functional/services/dashboard/panel_actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import { WebElementWrapper } from '@kbn/ftr-common-functional-ui-services';
import { FtrService } from '../../ftr_provider_context';

const ACTION_SHOW_CONFIG_PANEL_SUBJ = 'embeddablePanelAction-ACTION_SHOW_CONFIG_PANEL';
const REMOVE_PANEL_DATA_TEST_SUBJ = 'embeddablePanelAction-deletePanel';
const EDIT_PANEL_DATA_TEST_SUBJ = 'embeddablePanelAction-editPanel';
const EDIT_IN_LENS_EDITOR_DATA_TEST_SUBJ = 'navigateToLensEditorLink';
Expand Down Expand Up @@ -303,6 +304,11 @@ export class DashboardPanelActionsService extends FtrService {
await this.expectExistsPanelAction(TOGGLE_EXPAND_PANEL_DATA_TEST_SUBJ, title);
}

async expectExistsShowConfigPanelAction(title = '') {
this.log.debug('expectExistsShowConfigPanelAction');
await this.expectExistsPanelAction(ACTION_SHOW_CONFIG_PANEL_SUBJ, title);
}

async expectMissingPanelAction(testSubject: string, title = '') {
this.log.debug('expectMissingPanelAction', testSubject, title);
const wrapper = await this.getPanelWrapper(title);
Expand Down Expand Up @@ -331,6 +337,11 @@ export class DashboardPanelActionsService extends FtrService {
await this.expectMissingPanelAction(REMOVE_PANEL_DATA_TEST_SUBJ, title);
}

async expectMissingShowConfigPanelAction(title = '') {
this.log.debug('expectMissingShowConfigPanelAction');
await this.expectMissingPanelAction(ACTION_SHOW_CONFIG_PANEL_SUBJ, title);
}

async getPanelHeading(title = '') {
this.log.debug(`getPanelHeading(${title})`);
if (!title) return await this.find.byClassName('embPanel__wrapper');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24519,15 +24519,12 @@
"xpack.lens.colorMapping.techPreviewLabel": "Préversion technique",
"xpack.lens.colorMapping.tryLabel": "Utiliser la nouvelle fonctionnalité de mapping des couleurs",
"xpack.lens.colorSiblingFlyoutTitle": "Couleur",
"xpack.lens.config.applyFlyoutAriaLabel": "Appliquer les modifications",
"xpack.lens.config.applyFlyoutLabel": "Appliquer et fermer",
"xpack.lens.config.cancelFlyoutAriaLabel": "Annuler les changements appliqués",
"xpack.lens.config.cancelFlyoutLabel": "Annuler",
"xpack.lens.config.configFlyoutCallout": "Affichage d'une partie limitée des champs disponibles. Ajoutez-en plus depuis le panneau de configuration.",
"xpack.lens.config.createVisualizationLabel": "Créer la visualisation {lang}",
"xpack.lens.config.editLabel": "Modifier la configuration",
"xpack.lens.config.editLinkLabel": "Modifier dans Lens",
"xpack.lens.config.editVisualizationLabel": "Modifier la visualisation {lang}",
"xpack.lens.config.ESQLQueryResultsTitle": "Résultats de la requête ES|QL",
"xpack.lens.config.experimentalLabelDataview.content": "L'édition en ligne offre actuellement des options de configuration limitées.",
"xpack.lens.config.experimentalLabelDataview.title": "Version d'évaluation technique",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24498,15 +24498,12 @@
"xpack.lens.colorMapping.techPreviewLabel": "テクニカルプレビュー",
"xpack.lens.colorMapping.tryLabel": "新しい色マッピング機能を使用",
"xpack.lens.colorSiblingFlyoutTitle": "色",
"xpack.lens.config.applyFlyoutAriaLabel": "変更を適用",
"xpack.lens.config.applyFlyoutLabel": "適用して閉じる",
"xpack.lens.config.cancelFlyoutAriaLabel": "適用された変更をキャンセル",
"xpack.lens.config.cancelFlyoutLabel": "キャンセル",
"xpack.lens.config.configFlyoutCallout": "使用可能なフィールドの一部を表示します。構成パネルからその他の項目を追加します。",
"xpack.lens.config.createVisualizationLabel": "{lang}ビジュアライゼーションを作成",
"xpack.lens.config.editLabel": "構成の編集",
"xpack.lens.config.editLinkLabel": "Lensで編集",
"xpack.lens.config.editVisualizationLabel": "{lang}ビジュアライゼーションを編集",
"xpack.lens.config.ESQLQueryResultsTitle": "ES|QLクエリ結果",
"xpack.lens.config.experimentalLabelDataview.content": "現在、インライン編集では、構成オプションは限られています。",
"xpack.lens.config.experimentalLabelDataview.title": "テクニカルプレビュー",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24546,15 +24546,12 @@
"xpack.lens.colorMapping.techPreviewLabel": "技术预览",
"xpack.lens.colorMapping.tryLabel": "使用新的颜色映射功能",
"xpack.lens.colorSiblingFlyoutTitle": "颜色",
"xpack.lens.config.applyFlyoutAriaLabel": "应用更改",
"xpack.lens.config.applyFlyoutLabel": "应用并关闭",
"xpack.lens.config.cancelFlyoutAriaLabel": "取消应用的更改",
"xpack.lens.config.cancelFlyoutLabel": "取消",
"xpack.lens.config.configFlyoutCallout": "正在显示有限数量的可用字段。从配置面板添加更多字段。",
"xpack.lens.config.createVisualizationLabel": "创建 {lang} 可视化",
"xpack.lens.config.editLabel": "编辑配置",
"xpack.lens.config.editLinkLabel": "在 Lens 中编辑",
"xpack.lens.config.editVisualizationLabel": "编辑 {lang} 可视化",
"xpack.lens.config.ESQLQueryResultsTitle": "ES|QL 查询结果",
"xpack.lens.config.experimentalLabelDataview.content": "内联编辑当前提供的配置选项数量有限。",
"xpack.lens.config.experimentalLabelDataview.title": "技术预览",
Expand Down
Loading