Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Dashboard] Unlink panels when cloning managed dashboards #172383

Closed
Tracked by #172393
drewdaemon opened this issue Dec 1, 2023 · 1 comment · Fixed by #176006
Closed
Tracked by #172393

[Dashboard] Unlink panels when cloning managed dashboards #172383

drewdaemon opened this issue Dec 1, 2023 · 1 comment · Fixed by #176006
Assignees
Labels
enhancement New value added to drive a business result Team:Presentation Presentation Team for Dashboard, Input Controls, and Canvas

Comments

@drewdaemon
Copy link
Contributor

drewdaemon commented Dec 1, 2023

When a managed dashboard is cloned, all by-reference panels should be unlinked (become by-value).

Relevant file: src/plugins/dashboard/public/dashboard_container/embeddable/api/run_save_functions.tsx

Code to start from:

/*
 * 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 { batch } from 'react-redux';
import { showSaveModal } from '@kbn/saved-objects-plugin/public';

import { reportPerformanceMetricEvent } from '@kbn/ebt-tools';
import { DASHBOARD_CONTENT_ID, SAVED_OBJECT_POST_TIME } from '../../../dashboard_constants';
import { DashboardSaveOptions, DashboardStateFromSaveModal } from '../../types';
import { DashboardSaveModal } from './overlays/save_modal';
import { DashboardContainer } from '../dashboard_container';
import { pluginServices } from '../../../services/plugin_services';
import { DashboardContainerInput, DashboardPanelMap } from '../../../../common';
import { SaveDashboardReturn } from '../../../services/dashboard_content_management/types';
import { extractTitleAndCount } from './lib/extract_title_and_count';
import { EmbeddableInput, isReferenceOrValueEmbeddable } from '@kbn/embeddable-plugin/public';

export function runSaveAs(this: DashboardContainer) {
  const {
    data: {
      query: {
        timefilter: { timefilter },
      },
    },
    savedObjectsTagging: { hasApi: hasSavedObjectsTagging },
    dashboardContentManagement: { checkForDuplicateDashboardTitle, saveDashboardState },
  } = pluginServices.getServices();

  const {
    explicitInput: currentState,
    componentState: { lastSavedId, managed },
  } = this.getState();

  return new Promise<SaveDashboardReturn | undefined>((resolve) => {
    if (managed) resolve(undefined);
    const onSave = async ({
      newTags,
      newTitle,
      newDescription,
      newCopyOnSave,
      newTimeRestore,
      onTitleDuplicate,
      isTitleDuplicateConfirmed,
    }: DashboardSaveOptions): Promise<SaveDashboardReturn> => {
      const saveOptions = {
        confirmOverwrite: false,
        isTitleDuplicateConfirmed,
        onTitleDuplicate,
        saveAsCopy: newCopyOnSave,
      };
      const stateFromSaveModal: DashboardStateFromSaveModal = {
        title: newTitle,
        tags: [] as string[],
        description: newDescription,
        timeRestore: newTimeRestore,
        timeRange: newTimeRestore ? timefilter.getTime() : undefined,
        refreshInterval: newTimeRestore ? timefilter.getRefreshInterval() : undefined,
      };
      if (hasSavedObjectsTagging && newTags) {
        // remove `hasSavedObjectsTagging` once the savedObjectsTagging service is optional
        stateFromSaveModal.tags = newTags;
      }
      if (
        !(await checkForDuplicateDashboardTitle({
          title: newTitle,
          onTitleDuplicate,
          lastSavedTitle: currentState.title,
          copyOnSave: newCopyOnSave,
          isTitleDuplicateConfirmed,
        }))
      ) {
        // do not save if title is duplicate and is unconfirmed
        return {};
      }
      const stateToSave: DashboardContainerInput = {
        ...currentState,
        ...stateFromSaveModal,
      };
      const beforeAddTime = window.performance.now();
      const saveResult = await saveDashboardState({
        currentState: stateToSave,
        saveOptions,
        lastSavedId,
      });
      const addDuration = window.performance.now() - beforeAddTime;
      reportPerformanceMetricEvent(pluginServices.getServices().analytics, {
        eventName: SAVED_OBJECT_POST_TIME,
        duration: addDuration,
        meta: {
          saved_object_type: DASHBOARD_CONTENT_ID,
        },
      });

      stateFromSaveModal.lastSavedId = saveResult.id;
      if (saveResult.id) {
        batch(() => {
          this.dispatch.setStateFromSaveModal(stateFromSaveModal);
          this.dispatch.setLastSavedInput(stateToSave);
        });
      }
      resolve(saveResult);
      return saveResult;
    };

    const dashboardSaveModal = (
      <DashboardSaveModal
        tags={currentState.tags}
        title={currentState.title}
        onClose={() => resolve(undefined)}
        timeRestore={currentState.timeRestore}
        description={currentState.description ?? ''}
        showCopyOnSave={lastSavedId ? true : false}
        onSave={onSave}
      />
    );
    this.clearOverlays();
    showSaveModal(dashboardSaveModal);
  });
}

/**
 * Save the current state of this dashboard to a saved object without showing any save modal.
 */
export async function runQuickSave(this: DashboardContainer) {
  const {
    dashboardContentManagement: { saveDashboardState },
  } = pluginServices.getServices();

  const {
    explicitInput: currentState,
    componentState: { lastSavedId, managed },
  } = this.getState();

  if (managed) return;

  const saveResult = await saveDashboardState({
    lastSavedId,
    currentState,
    saveOptions: {},
  });
  this.dispatch.setLastSavedInput(currentState);

  return saveResult;
}

export async function runClone(this: DashboardContainer) {
  const {
    dashboardContentManagement: { saveDashboardState, checkForDuplicateDashboardTitle },
  } = pluginServices.getServices();

  const { explicitInput: currentState } = this.getState();

  return new Promise<SaveDashboardReturn | undefined>(async (resolve, reject) => {
    try {
      const [baseTitle, baseCount] = extractTitleAndCount(currentState.title);
      let copyCount = baseCount;
      let newTitle = `${baseTitle} (${copyCount})`;
      while (
        !(await checkForDuplicateDashboardTitle({
          title: newTitle,
          lastSavedTitle: currentState.title,
          copyOnSave: true,
          isTitleDuplicateConfirmed: false,
        }))
      ) {
        copyCount++;
        newTitle = `${baseTitle} (${copyCount})`;
      }

      // unlink all by reference embeddables on clone
      const isManaged = true; // temp
      const newPanels = await (async () => {
        if (!isManaged) return currentState.panels;
        const unlinkedPanels: DashboardPanelMap = {};
        for (const [panelId, panel] of Object.entries(currentState.panels)) {
          const child = this.getChild(panelId);
          if (
            child &&
            isReferenceOrValueEmbeddable(child) &&
            child.inputIsRefType(child.getInput() as EmbeddableInput)
          ) {
            const valueTypeInput = await child.getInputAsValueType();
            unlinkedPanels[panelId] = {
              ...panel,
              explicitInput: valueTypeInput,
            };
            continue;
          }
          unlinkedPanels[panelId] = panel;
        }
        return unlinkedPanels;
      })();

      const saveResult = await saveDashboardState({
        saveOptions: {
          saveAsCopy: true,
        },
        currentState: {
          ...currentState,
          panels: newPanels,
          title: newTitle,
        },
      });
      resolve(saveResult);
      return saveResult.id
        ? {
            id: saveResult.id,
          }
        : {
            error: saveResult.error,
          };
    } catch (error) {
      reject(error);
    }
  });
}
@botelastic botelastic bot added the needs-team Issues missing a team label label Dec 1, 2023
@drewdaemon drewdaemon added enhancement New value added to drive a business result Team:Presentation Presentation Team for Dashboard, Input Controls, and Canvas labels Dec 1, 2023
@elasticmachine
Copy link
Contributor

Pinging @elastic/kibana-presentation (Team:Presentation)

@botelastic botelastic bot removed the needs-team Issues missing a team label label Dec 1, 2023
@drewdaemon drewdaemon self-assigned this Dec 1, 2023
@drewdaemon drewdaemon changed the title [Dashboard] Unlink panels when cloning managed dashboard [Dashboard] Unlink panels when cloning managed dashboards Dec 1, 2023
drewdaemon added a commit that referenced this issue Feb 7, 2024
## Summary

Close #172383
Close #172384

This PR introduces a [new embeddable-related
registry](https://github.com/elastic/kibana/pull/176006/files#diff-1401b355377c76ab6458756aa0e3177beef5ec56796c58b7a52b5e003f85b5cf)
which clients can use to define a custom transformation from saved
object to embeddable input during the add-panel-from-library sequence.

Then, each content type uses this to communicate whether a particular
object should be added by-ref or by-val based on the presence of
`managed: true` on the saved object
([example](https://github.com/elastic/kibana/pull/176006/files#diff-3baaeaeef5893a5a4db6379a1ed888406a8584cb9d0c7440f273040e4aa28166R157-R167)).



### Managed panels are added by-value to dashboards

<img width="400" alt="Screenshot 2024-02-01 at 12 24 06 PM"
src="https://github.com/elastic/kibana/assets/315764/42a695d4-fccf-45bf-bd6a-8d8fc606d04e">

### Cloning a managed dashboard inlines all by-ref panels


https://github.com/elastic/kibana/assets/315764/ca6e763c-cc02-46cb-9164-abd91deca081

### Checklist

Delete any items that are not applicable to this PR.

- [x] Any text added follows [EUI's writing
guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses
sentence case text and includes [i18n
support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md)
- [ ]
[Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html)
was added for features that require explanation or tutorials — will
happen in #175150
- [x] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios
- [x] [Flaky Test
Runner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was
used on any tests changed —
https://buildkite.com/elastic/kibana-flaky-test-suite-runner/builds/5031
- [x] Any UI touched in this PR is usable by keyboard only (learn more
about [keyboard accessibility](https://webaim.org/techniques/keyboard/))

---------

Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
fkanout pushed a commit to fkanout/kibana that referenced this issue Feb 8, 2024
…176006)

## Summary

Close elastic#172383
Close elastic#172384

This PR introduces a [new embeddable-related
registry](https://github.com/elastic/kibana/pull/176006/files#diff-1401b355377c76ab6458756aa0e3177beef5ec56796c58b7a52b5e003f85b5cf)
which clients can use to define a custom transformation from saved
object to embeddable input during the add-panel-from-library sequence.

Then, each content type uses this to communicate whether a particular
object should be added by-ref or by-val based on the presence of
`managed: true` on the saved object
([example](https://github.com/elastic/kibana/pull/176006/files#diff-3baaeaeef5893a5a4db6379a1ed888406a8584cb9d0c7440f273040e4aa28166R157-R167)).



### Managed panels are added by-value to dashboards

<img width="400" alt="Screenshot 2024-02-01 at 12 24 06 PM"
src="https://github.com/elastic/kibana/assets/315764/42a695d4-fccf-45bf-bd6a-8d8fc606d04e">

### Cloning a managed dashboard inlines all by-ref panels


https://github.com/elastic/kibana/assets/315764/ca6e763c-cc02-46cb-9164-abd91deca081

### Checklist

Delete any items that are not applicable to this PR.

- [x] Any text added follows [EUI's writing
guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses
sentence case text and includes [i18n
support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md)
- [ ]
[Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html)
was added for features that require explanation or tutorials — will
happen in elastic#175150
- [x] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios
- [x] [Flaky Test
Runner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was
used on any tests changed —
https://buildkite.com/elastic/kibana-flaky-test-suite-runner/builds/5031
- [x] Any UI touched in this PR is usable by keyboard only (learn more
about [keyboard accessibility](https://webaim.org/techniques/keyboard/))

---------

Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
CoenWarmer pushed a commit to CoenWarmer/kibana that referenced this issue Feb 15, 2024
…176006)

## Summary

Close elastic#172383
Close elastic#172384

This PR introduces a [new embeddable-related
registry](https://github.com/elastic/kibana/pull/176006/files#diff-1401b355377c76ab6458756aa0e3177beef5ec56796c58b7a52b5e003f85b5cf)
which clients can use to define a custom transformation from saved
object to embeddable input during the add-panel-from-library sequence.

Then, each content type uses this to communicate whether a particular
object should be added by-ref or by-val based on the presence of
`managed: true` on the saved object
([example](https://github.com/elastic/kibana/pull/176006/files#diff-3baaeaeef5893a5a4db6379a1ed888406a8584cb9d0c7440f273040e4aa28166R157-R167)).



### Managed panels are added by-value to dashboards

<img width="400" alt="Screenshot 2024-02-01 at 12 24 06 PM"
src="https://github.com/elastic/kibana/assets/315764/42a695d4-fccf-45bf-bd6a-8d8fc606d04e">

### Cloning a managed dashboard inlines all by-ref panels


https://github.com/elastic/kibana/assets/315764/ca6e763c-cc02-46cb-9164-abd91deca081

### Checklist

Delete any items that are not applicable to this PR.

- [x] Any text added follows [EUI's writing
guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses
sentence case text and includes [i18n
support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md)
- [ ]
[Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html)
was added for features that require explanation or tutorials — will
happen in elastic#175150
- [x] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios
- [x] [Flaky Test
Runner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was
used on any tests changed —
https://buildkite.com/elastic/kibana-flaky-test-suite-runner/builds/5031
- [x] Any UI touched in this PR is usable by keyboard only (learn more
about [keyboard accessibility](https://webaim.org/techniques/keyboard/))

---------

Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
fkanout pushed a commit to fkanout/kibana that referenced this issue Mar 4, 2024
…176006)

## Summary

Close elastic#172383
Close elastic#172384

This PR introduces a [new embeddable-related
registry](https://github.com/elastic/kibana/pull/176006/files#diff-1401b355377c76ab6458756aa0e3177beef5ec56796c58b7a52b5e003f85b5cf)
which clients can use to define a custom transformation from saved
object to embeddable input during the add-panel-from-library sequence.

Then, each content type uses this to communicate whether a particular
object should be added by-ref or by-val based on the presence of
`managed: true` on the saved object
([example](https://github.com/elastic/kibana/pull/176006/files#diff-3baaeaeef5893a5a4db6379a1ed888406a8584cb9d0c7440f273040e4aa28166R157-R167)).



### Managed panels are added by-value to dashboards

<img width="400" alt="Screenshot 2024-02-01 at 12 24 06 PM"
src="https://github.com/elastic/kibana/assets/315764/42a695d4-fccf-45bf-bd6a-8d8fc606d04e">

### Cloning a managed dashboard inlines all by-ref panels


https://github.com/elastic/kibana/assets/315764/ca6e763c-cc02-46cb-9164-abd91deca081

### Checklist

Delete any items that are not applicable to this PR.

- [x] Any text added follows [EUI's writing
guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses
sentence case text and includes [i18n
support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md)
- [ ]
[Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html)
was added for features that require explanation or tutorials — will
happen in elastic#175150
- [x] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios
- [x] [Flaky Test
Runner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was
used on any tests changed —
https://buildkite.com/elastic/kibana-flaky-test-suite-runner/builds/5031
- [x] Any UI touched in this PR is usable by keyboard only (learn more
about [keyboard accessibility](https://webaim.org/techniques/keyboard/))

---------

Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New value added to drive a business result Team:Presentation Presentation Team for Dashboard, Input Controls, and Canvas
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants