Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

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 @@ -116,7 +116,8 @@ class TableListViewUi extends React.Component {
isDeletingItems: true
});
try {
await this.props.deleteItems(this.state.selectedIds);
const itemsById = _.indexBy(this.state.items, 'id');
await this.props.deleteItems(this.state.selectedIds, this.state.selectedIds.map(id => itemsById[id]));
} catch (error) {
toastNotifications.addDanger({
title: (
Expand Down Expand Up @@ -316,6 +317,7 @@ class TableListViewUi extends React.Component {
};

const selection = this.props.deleteItems ? {
selectable: this.props.selectable,
onSelectionChange: (selection) => {
this.setState({
selectedIds: selection.map(item => { return item.id; })
Expand Down Expand Up @@ -468,6 +470,7 @@ TableListViewUi.propTypes = {
deleteItems: PropTypes.func,
createItem: PropTypes.func,
editItem: PropTypes.func,
selectable: PropTypes.func,

listingLimit: PropTypes.number,
initialFilter: PropTypes.string,
Expand All @@ -482,4 +485,3 @@ TableListViewUi.defaultProps = {
};

export const TableListView = injectI18n(TableListViewUi);

Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,11 @@ import chrome from 'ui/chrome';
import { wrapInI18nContext } from 'ui/i18n';
import { toastNotifications } from 'ui/notify';
import { addHelpMenuToAppChrome } from '../help_menu/help_menu_util';

import { SavedObjectsClientProvider } from 'ui/saved_objects';
import { VisualizeListingTable } from './visualize_listing_table';
import { NewVisModal } from '../wizard/new_vis_modal';
import { createVisualizeEditUrl, VisualizeConstants } from '../visualize_constants';
import { VisualizeConstants } from '../visualize_constants';
import { visualizations } from 'plugins/visualizations';

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

const app = uiModules.get('app/visualize', ['ngRoute', 'react']);
Expand All @@ -42,6 +41,7 @@ export function VisualizeListingController($injector, createNewVis) {
const Private = $injector.get('Private');
const config = $injector.get('config');
const kbnUrl = $injector.get('kbnUrl');
const savedObjectClient = Private(SavedObjectsClientProvider);

this.visTypeRegistry = Private(VisTypesRegistryProvider);
this.visTypeAliases = visualizations.types.visTypeAliasRegistry.get();
Expand All @@ -55,13 +55,13 @@ export function VisualizeListingController($injector, createNewVis) {
this.showNewVisModal = true;
};

this.editItem = ({ id }) => {
this.editItem = ({ editURL }) => {
// for visualizations the edit and view URLs are the same
kbnUrl.change(createVisualizeEditUrl(id));
window.location = chrome.addBasePath(editURL);
};

this.getViewUrl = ({ id }) => {
return chrome.addBasePath(`#${createVisualizeEditUrl(id)}`);
this.getViewUrl = ({ editURL }) => {
return chrome.addBasePath(editURL);
};

this.closeNewVisModal = () => {
Expand All @@ -83,7 +83,7 @@ export function VisualizeListingController($injector, createNewVis) {

this.fetchItems = (filter) => {
const isLabsEnabled = config.get('visualize:enableLabs');
return visualizationService.find(filter, config.get('savedObjects:listingLimit'))
return visualizationService.findListItems(filter, config.get('savedObjects:listingLimit'))
.then(result => {
this.totalItems = result.total;

Expand All @@ -94,15 +94,18 @@ export function VisualizeListingController($injector, createNewVis) {
});
};

this.deleteSelectedItems = function deleteSelectedItems(selectedIds) {
return visualizationService.delete(selectedIds)
.catch(error => {
toastNotifications.addError(error, {
title: i18n.translate('kbn.visualize.visualizeListingDeleteErrorTitle', {
defaultMessage: 'Error deleting visualization',
}),
});
this.deleteSelectedItems = function deleteSelectedItems(_ids, selectedItems) {
return Promise.all(
selectedItems.map(item => {
return savedObjectClient.delete(item.savedObjectType, item.id);
}),
).catch(error => {
toastNotifications.addError(error, {
title: i18n.translate('kbn.visualize.visualizeListingDeleteErrorTitle', {
defaultMessage: 'Error deleting visualization',
}),
});
});
};

chrome.breadcrumbs.set([{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,12 @@ import React, { Component, Fragment } from 'react';
import PropTypes from 'prop-types';
import { injectI18n, FormattedMessage } from '@kbn/i18n/react';
import { i18n } from '@kbn/i18n';
import { capabilities } from 'ui/capabilities';
import { TableListView } from './../../table_list_view';

import {
EuiIcon,
EuiBetaBadge,
EuiButtonIcon,
EuiLink,
EuiButton,
EuiEmptyPrompt,
Expand All @@ -46,10 +46,10 @@ class VisualizeListingTableUi extends Component {
// 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}
deleteItems={this.props.deleteItems}
tableColumns={this.getTableColumns()}
listingLimit={this.props.listingLimit}
selectable={item => item.canDelete}
initialFilter={''}
noItemsFragment={this.getNoItemsMessage()}
entityName={
Expand Down Expand Up @@ -94,7 +94,7 @@ class VisualizeListingTableUi extends Component {
)
},
{
field: 'type.title',
field: 'typeTitle',
name: intl.formatMessage({
id: 'kbn.visualize.listing.table.typeColumnName',
defaultMessage: 'Type',
Expand All @@ -103,10 +103,31 @@ class VisualizeListingTableUi extends Component {
render: (field, record) => (
<span>
{this.renderItemTypeIcon(record)}
{record.type.title}
{record.typeTitle}
{this.getExperimentalBadge(record)}
</span>
)
},
{
field: 'canEdit',
name: intl.formatMessage({
id: 'kbn.visualize.listing.table.actionsColumnName',
defaultMessage: 'Actions',
}),
align: 'right',
width: '100px',
render: (_field, record) => (
<EuiButtonIcon
className="euiButtonIcon euiButtonIcon--primary euiTableCellContent__hoverItem"
onClick={() => this.props.editItem(record)}
iconType="pencil"
aria-label={intl.formatMessage({
id: 'kbn.visualize.listing.table.editActionDescription',
defaultMessage: 'Edit',
})}
disabled={!record.canEdit}
/>
),
}
];

Expand Down Expand Up @@ -175,13 +196,13 @@ class VisualizeListingTableUi extends Component {

renderItemTypeIcon(item) {
let icon;
if (item.type.image) {
if (item.image) {
icon = (
<img
className="visListingTable__typeImage"
aria-hidden="true"
alt=""
src={item.type.image}
src={item.image}
/>
);
} else {
Expand All @@ -199,7 +220,7 @@ class VisualizeListingTableUi extends Component {
}

getExperimentalBadge(item) {
return item.type.shouldMarkAsExperimentalInUI() && (
return item.isExperimental && (
<EuiBetaBadge
className="visListingTable__experimentalIcon"
label="E"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

import _ from 'lodash';

/**
* Search for visualizations and convert them into a list display-friendly format.
*/
export async function findListItems({ visTypes, search, size, savedObjectsClient, mapSavedObjectApiHits }) {
const extensions = _.compact(visTypes.map(v => v.appExtensions && v.appExtensions.visualizations));
const extensionByType = extensions.reduce((acc, m) => {
return m.docTypes.reduce((_acc, type) => {
acc[type] = m;
return acc;
}, acc);
}, {});
const searchOption = (field, ...defaults) =>
_(extensions)
.pluck(field)
.concat(defaults)
.compact()
.flatten()
.uniq()
.value();
const searchOptions = {
type: searchOption('docTypes', 'visualization'),
searchFields: searchOption('searchFields', 'title^3', 'description'),
search: search ? `${search}*` : undefined,
perPage: size,
page: 1,
defaultSearchOperator: 'AND'
};

const { total, savedObjects } = await savedObjectsClient.find(searchOptions);

return {
total,
hits: savedObjects
.map((savedObject) => {
const config = extensionByType[savedObject.type];

if (config) {
return config.toListItem(savedObject);
} else {
return mapSavedObjectApiHits(savedObject);
}
})
};
}
Loading