示例#1
0
 render() {
   const { intl } = this.props;
   return (
     <TableListView
       // we allow users to create visualizations even if they can't save them
       // for data exploration purposes
       createItem={this.props.createItem}
       findItems={this.props.findItems}
       deleteItems={capabilities.get().visualize.delete ? this.props.deleteItems : null}
       editItem={capabilities.get().visualize.save ? this.props.editItem : null}
       tableColumns={this.getTableColumns()}
       listingLimit={100}
       initialFilter={''}
       noItemsFragment={this.getNoItemsMessage()}
       entityName={
         intl.formatMessage({
           id: 'kbn.visualize.listing.table.entityName',
           defaultMessage: 'visualization',
         })
       }
       entityNamePlural={
         intl.formatMessage({
           id: 'kbn.visualize.listing.table.entityNamePlural',
           defaultMessage: 'visualizations',
         })
       }
       tableListTitle={
         intl.formatMessage({
           id: 'kbn.visualize.listing.table.listTitle',
           defaultMessage: 'Visualizations',
         })
       }
     />
   );
 }
示例#2
0
文件: logs.js 项目: elastic/kibana
  renderCallout() {
    const uiCapabilities = capabilities.get();
    const show = uiCapabilities.logs && uiCapabilities.logs.show;
    const { logs: { enabled }, nodeId, clusterUuid, indexUuid } = this.props;
    if (!enabled || !show) {
      return null;
    }

    return (
      <EuiCallOut
        size="m"
        title={i18n.translate('xpack.monitoring.logs.listing.calloutTitle', {
          defaultMessage: 'Want to see more logs?'
        })}
        iconType="loggingApp"
      >
        <p>
          <FormattedMessage
            id="xpack.monitoring.logs.listing.linkText"
            defaultMessage="Visit {link} to dive deeper."
            values={{
              link: (
                <EuiLink href={getLogsUiLink(clusterUuid, nodeId, indexUuid)}>
                  {i18n.translate('xpack.monitoring.logs.listing.calloutLinkText', {
                    defaultMessage: 'Logs'
                  })}
                </EuiLink>
              )
            }}
          />
        </p>
      </EuiCallOut>
    );
  }
示例#3
0
 run: (menuItem, navController, anchorElement) => {
   const hasUnappliedChanges = vis.dirty;
   const hasUnsavedChanges = $appStatus.dirty;
   showShareContextMenu({
     anchorElement,
     allowEmbed: true,
     allowShortUrl: capabilities.get().visualize.createShortUrl,
     getUnhashableStates,
     objectId: savedVis.id,
     objectType: 'visualization',
     shareContextMenuExtensions,
     sharingData: {
       title: savedVis.title,
     },
     isDirty: hasUnappliedChanges || hasUnsavedChanges,
   });
 }
示例#4
0
function VisEditor(
  $scope,
  $element,
  $route,
  AppState,
  $window,
  $injector,
  indexPatterns,
  kbnUrl,
  redirectWhenMissing,
  Private,
  Promise,
  config,
  kbnBaseUrl,
  localStorage,
  i18n
) {
  const docTitle = Private(DocTitleProvider);
  const queryFilter = Private(FilterBarQueryFilterProvider);
  const getUnhashableStates = Private(getUnhashableStatesProvider);
  const shareContextMenuExtensions = Private(ShareContextMenuExtensionsRegistryProvider);

  // Retrieve the resolved SavedVis instance.
  const savedVis = $route.current.locals.savedVis;
  // vis is instance of src/legacy/ui/public/vis/vis.js.
  // SearchSource is a promise-based stream of search results that can inherit from other search sources.
  const { vis, searchSource } = savedVis;

  $scope.vis = vis;

  const $appStatus = this.appStatus = {
    dirty: !savedVis.id
  };

  $scope.topNavMenu = [...(capabilities.get().visualize.save ? [{
    key: i18n('kbn.topNavMenu.saveVisualizationButtonLabel', { defaultMessage: 'save' }),
    description: i18n('kbn.visualize.topNavMenu.saveVisualizationButtonAriaLabel', {
      defaultMessage: 'Save Visualization',
    }),
    testId: 'visualizeSaveButton',
    disableButton() {
      return Boolean(vis.dirty);
    },
    tooltip() {
      if (vis.dirty) {
        return i18n('kbn.visualize.topNavMenu.saveVisualizationDisabledButtonTooltip', {
          defaultMessage: 'Apply or Discard your changes before saving'
        });
      }
    },
    run: async () => {
      const onSave = ({ newTitle, newCopyOnSave, isTitleDuplicateConfirmed, onTitleDuplicate }) => {
        const currentTitle = savedVis.title;
        savedVis.title = newTitle;
        savedVis.copyOnSave = newCopyOnSave;
        const saveOptions = {
          confirmOverwrite: false,
          isTitleDuplicateConfirmed,
          onTitleDuplicate,
        };
        return doSave(saveOptions).then(({ id, error }) => {
          // If the save wasn't successful, put the original values back.
          if (!id || error) {
            savedVis.title = currentTitle;
          }
          return { id, error };
        });
      };

      const confirmButtonLabel = $scope.isAddToDashMode() ? (
        <FormattedMessage
          id="kbn.visualize.saveDialog.saveAndAddToDashboardButtonLabel"
          defaultMessage="Save and add to dashboard"
        />
      ) : null;

      const saveModal = (
        <SavedObjectSaveModal
          onSave={onSave}
          onClose={() => {}}
          title={savedVis.title}
          showCopyOnSave={savedVis.id ? true : false}
          objectType="visualization"
          confirmButtonLabel={confirmButtonLabel}
        />);
      showSaveModal(saveModal);
    }
  }] : []), {
    key: i18n('kbn.topNavMenu.shareVisualizationButtonLabel', { defaultMessage: 'share' }),
    description: i18n('kbn.visualize.topNavMenu.shareVisualizationButtonAriaLabel', {
      defaultMessage: 'Share Visualization',
    }),
    testId: 'shareTopNavButton',
    run: (menuItem, navController, anchorElement) => {
      const hasUnappliedChanges = vis.dirty;
      const hasUnsavedChanges = $appStatus.dirty;
      showShareContextMenu({
        anchorElement,
        allowEmbed: true,
        allowShortUrl: capabilities.get().visualize.createShortUrl,
        getUnhashableStates,
        objectId: savedVis.id,
        objectType: 'visualization',
        shareContextMenuExtensions,
        sharingData: {
          title: savedVis.title,
        },
        isDirty: hasUnappliedChanges || hasUnsavedChanges,
      });
    }
  }, {
    key: i18n('kbn.topNavMenu.openInspectorButtonLabel', { defaultMessage: 'inspect' }),
    description: i18n('kbn.visualize.topNavMenu.openInspectorButtonAriaLabel', {
      defaultMessage: 'Open Inspector for visualization',
    }),
    testId: 'openInspectorButton',
    disableButton() {
      return !vis.hasInspector || !vis.hasInspector();
    },
    run() {
      const inspectorSession = vis.openInspector();
      // Close the inspector if this scope is destroyed (e.g. because the user navigates away).
      const removeWatch = $scope.$on('$destroy', () => inspectorSession.close());
      // Remove that watch in case the user closes the inspector session herself.
      inspectorSession.onClose.finally(removeWatch);
    },
    tooltip() {
      if (!vis.hasInspector || !vis.hasInspector()) {
        return i18n('kbn.visualize.topNavMenu.openInspectorDisabledButtonTooltip', {
          defaultMessage: `This visualization doesn't support any inspectors.`,
        });
      }
    }
  }, {
    key: i18n('kbn.topNavMenu.refreshButtonLabel', { defaultMessage: 'refresh' }),
    description: i18n('kbn.visualize.topNavMenu.refreshButtonAriaLabel', {
      defaultMessage: 'Refresh',
    }),
    run: function () {
      vis.forceReload();
    },
    testId: 'visualizeRefreshButton',
  }];

  let stateMonitor;

  if (savedVis.id) {
    docTitle.change(savedVis.title);
  }

  // Extract visualization state with filtered aggs. You can see these filtered aggs in the URL.
  // Consists of things like aggs, params, listeners, title, type, etc.
  const savedVisState = vis.getState();
  const stateDefaults = {
    uiState: savedVis.uiStateJSON ? JSON.parse(savedVis.uiStateJSON) : {},
    linked: !!savedVis.savedSearchId,
    query: searchSource.getOwnField('query') || {
      query: '',
      language: localStorage.get('kibana.userQueryLanguage') || config.get('search:queryLanguage')
    },
    filters: searchSource.getOwnField('filter') || [],
    vis: savedVisState
  };

  // Instance of app_state.js.
  const $state = (function initState() {
    // This is used to sync visualization state with the url when `appState.save()` is called.
    const appState = new AppState(stateDefaults);

    // Initializing appState does two things - first it translates the defaults into AppState,
    // second it updates appState based on the url (the url trumps the defaults). This means if
    // we update the state format at all and want to handle BWC, we must not only migrate the
    // data stored with saved vis, but also any old state in the url.
    migrateAppState(appState);

    // The savedVis is pulled from elasticsearch, but the appState is pulled from the url, with the
    // defaults applied. If the url was from a previous session which included modifications to the
    // appState then they won't be equal.
    if (!angular.equals(appState.vis, savedVisState)) {
      Promise.try(function () {
        vis.setState(appState.vis);
      })
        .catch(redirectWhenMissing({
          'index-pattern-field': '/visualize'
        }));
    }

    return appState;
  }());

  $scope.filters = queryFilter.getFilters();

  $scope.onFiltersUpdated = filters => {
    // The filters will automatically be set when the queryFilter emits an update event (see below)
    queryFilter.setFilters(filters);
  };

  $scope.onCancelApplyFilters = () => {
    $scope.state.$newFilters = [];
  };

  $scope.onApplyFilters = filters => {
    queryFilter.addFiltersAndChangeTimeFilter(filters);
    $scope.state.$newFilters = [];
  };

  $scope.$watch('state.$newFilters', (filters = []) => {
    if (filters.length === 1) {
      $scope.onApplyFilters(filters);
    }
  });

  function init() {
    // export some objects
    $scope.savedVis = savedVis;
    if (vis.indexPattern) {
      $scope.indexPattern = vis.indexPattern;
    } else {
      indexPatterns.getDefault().then(defaultIndexPattern => {
        $scope.indexPattern = defaultIndexPattern;
      });
    }

    $scope.searchSource = searchSource;
    $scope.state = $state;

    // Create a PersistedState instance.
    $scope.uiState = $state.makeStateful('uiState');
    $scope.appStatus = $appStatus;

    const addToDashMode = $route.current.params[DashboardConstants.ADD_VISUALIZATION_TO_DASHBOARD_MODE_PARAM];
    kbnUrl.removeParam(DashboardConstants.ADD_VISUALIZATION_TO_DASHBOARD_MODE_PARAM);

    $scope.isAddToDashMode = () => addToDashMode;

    $scope.showQueryBar = () => {
      return vis.type.requiresSearch && vis.type.options.showQueryBar;
    };

    $scope.timeRange = timefilter.getTime();
    $scope.opts = _.pick($scope, 'savedVis', 'isAddToDashMode');

    stateMonitor = stateMonitorFactory.create($state, stateDefaults);
    stateMonitor.ignoreProps([ 'vis.listeners' ]).onChange((status) => {
      $appStatus.dirty = status.dirty || !savedVis.id;
    });

    $scope.$watch('state.query', (newQuery) => {
      const query = migrateLegacyQuery(newQuery);
      $scope.updateQueryAndFetch({ query });
    });

    $state.replace();

    $scope.$watchMulti([
      'searchSource.getField("index")',
      'vis.type.options.showTimePicker',
      $scope.showQueryBar,
    ], function ([index, requiresTimePicker, showQueryBar]) {
      const showTimeFilter = Boolean((!index || index.timeFieldName) && requiresTimePicker);

      if (showQueryBar) {
        timefilter.disableTimeRangeSelector();
        timefilter.disableAutoRefreshSelector();
        $scope.enableQueryBarTimeRangeSelector = true;
        $scope.showAutoRefreshOnlyInQueryBar = !showTimeFilter;
      }
      else if (showTimeFilter) {
        timefilter.enableTimeRangeSelector();
        timefilter.enableAutoRefreshSelector();
        $scope.enableQueryBarTimeRangeSelector = false;
        $scope.showAutoRefreshOnlyInQueryBar = false;
      }
      else {
        timefilter.disableTimeRangeSelector();
        timefilter.enableAutoRefreshSelector();
        $scope.enableQueryBarTimeRangeSelector = false;
        $scope.showAutoRefreshOnlyInQueryBar = false;
      }
    });

    const updateTimeRange = () => {
      $scope.timeRange = timefilter.getTime();
      // In case we are running in embedded mode (i.e. we used the visualize loader to embed)
      // the visualization, we need to update the timeRange on the visualize handler.
      if ($scope._handler) {
        $scope._handler.update({
          timeRange: $scope.timeRange,
        });
      }
    };

    const updateRefreshInterval = () => {
      $scope.refreshInterval = timefilter.getRefreshInterval();
    };

    $scope.$listenAndDigestAsync(timefilter, 'timeUpdate', updateTimeRange);
    $scope.$listenAndDigestAsync(timefilter, 'refreshIntervalUpdate', updateRefreshInterval);

    // update the searchSource when filters update
    $scope.$listen(queryFilter, 'update', function () {
      $scope.filters = queryFilter.getFilters();
      $scope.fetch();
    });

    // update the searchSource when query updates
    $scope.fetch = function () {
      $state.save();
      savedVis.searchSource.setField('query', $state.query);
      savedVis.searchSource.setField('filter', $state.filters);
      $scope.globalFilters = queryFilter.getGlobalFilters();
      $scope.vis.forceReload();
    };

    $scope.$on('$destroy', function () {
      if ($scope._handler) {
        $scope._handler.destroy();
      }
      savedVis.destroy();
      stateMonitor.destroy();
    });

    if (!$scope.chrome.getVisible()) {
      getVisualizeLoader().then(loader => {
        $scope._handler = loader.embedVisualizationWithSavedObject($element.find('.visualize')[0], savedVis, {
          timeRange: $scope.timeRange,
          uiState: $scope.uiState,
          appState: $state,
          listenOnChange: false
        });
      });
    }
  }

  $scope.updateQueryAndFetch = function ({ query, dateRange }) {
    timefilter.setTime(dateRange);
    $state.query = query;
    $scope.fetch();
  };

  $scope.onRefreshChange = function ({ isPaused, refreshInterval }) {
    timefilter.setRefreshInterval({
      pause: isPaused,
      value: refreshInterval ? refreshInterval : $scope.refreshInterval.value
    });
  };

  /**
   * Called when the user clicks "Save" button.
   */
  function doSave(saveOptions) {
    // vis.title was not bound and it's needed to reflect title into visState
    $state.vis.title = savedVis.title;
    $state.vis.type = savedVis.type || $state.vis.type;
    savedVis.visState = $state.vis;
    savedVis.uiStateJSON = angular.toJson($scope.uiState.getChanges());

    return savedVis.save(saveOptions)
      .then(function (id) {
        $scope.$evalAsync(() => {
          stateMonitor.setInitialState($state.toJSON());

          if (id) {
            toastNotifications.addSuccess({
              title: i18n('kbn.visualize.topNavMenu.saveVisualization.successNotificationText', {
                defaultMessage: `Saved '{visTitle}'`,
                values: {
                  visTitle: savedVis.title,
                },
              }),
              'data-test-subj': 'saveVisualizationSuccess',
            });

            if ($scope.isAddToDashMode()) {
              const savedVisualizationParsedUrl = new KibanaParsedUrl({
                basePath: chrome.getBasePath(),
                appId: kbnBaseUrl.slice('/app/'.length),
                appPath: kbnUrl.eval(`${VisualizeConstants.EDIT_PATH}/{{id}}`, { id: savedVis.id }),
              });
              // Manually insert a new url so the back button will open the saved visualization.
              $window.history.pushState({}, '', savedVisualizationParsedUrl.getRootRelativePath());
              // Since we aren't reloading the page, only inserting a new browser history item, we need to manually update
              // the last url for this app, so directly clicking on the Visualize tab will also bring the user to the saved
              // url, not the unsaved one.
              chrome.trackSubUrlForApp('kibana:visualize', savedVisualizationParsedUrl);

              const lastDashboardAbsoluteUrl = chrome.getNavLinkById('kibana:dashboard').lastSubUrl;
              const dashboardParsedUrl = absoluteToParsedUrl(lastDashboardAbsoluteUrl, chrome.getBasePath());
              dashboardParsedUrl.addQueryParameter(DashboardConstants.NEW_VISUALIZATION_ID_PARAM, savedVis.id);
              kbnUrl.change(dashboardParsedUrl.appPath);
            } else if (savedVis.id === $route.current.params.id) {
              docTitle.change(savedVis.lastSavedTitle);
              chrome.breadcrumbs.set($injector.invoke(getEditBreadcrumbs));
            } else {
              kbnUrl.change(`${VisualizeConstants.EDIT_PATH}/{{id}}`, { id: savedVis.id });
            }
          }
        });
        return { id };
      }, (error) => {
        // eslint-disable-next-line
        console.error(error);
        toastNotifications.addDanger({
          title: i18n('kbn.visualize.topNavMenu.saveVisualization.failureNotificationText', {
            defaultMessage: `Error on saving '{visTitle}'`,
            values: {
              visTitle: savedVis.title,
            },
          }),
          text: error.message,
          'data-test-subj': 'saveVisualizationError',
        });
        return { error };
      });
  }

  $scope.unlink = function () {
    if (!$state.linked) return;

    $state.linked = false;
    const searchSourceParent = searchSource.getParent();
    const searchSourceGrandparent = searchSourceParent.getParent();

    delete savedVis.savedSearchId;
    delete vis.savedSearchId;
    searchSourceParent.setField('filter', _.union(searchSource.getOwnField('filter'), searchSourceParent.getOwnField('filter')));

    $state.query = searchSourceParent.getField('query');
    $state.filters = searchSourceParent.getField('filter');
    searchSource.setField('index', searchSourceParent.getField('index'));
    searchSource.setParent(searchSourceGrandparent);

    toastNotifications.addSuccess(
      i18n('kbn.visualize.linkedToSearch.unlinkSuccessNotificationText', {
        defaultMessage: `Unlinked from saved search '{searchTitle}'`,
        values: {
          searchTitle: savedVis.savedSearch.title
        }
      })
    );

    $scope.fetch();
  };


  $scope.getAdditionalMessage = () => {
    return (
      '<i class="kuiIcon fa-flask"></i>' +
      i18n('kbn.visualize.experimentalVisInfoText', { defaultMessage: 'This visualization is marked as experimental.' }) +
      ' ' +
      vis.type.feedbackMessage
    );
  };

  init();
}