Skip to content

Commit

Permalink
[7.12] [Time to Visualize] Allow By Value Flow Without Visualize Save…
Browse files Browse the repository at this point in the history
… Permissions (#95951) (#95976)

* [Time to Visualize] Allow By Value Flow Without Visualize Save Permissions (#95951)

* Made sure users can use by value workflow without visualize save permissions
# Conflicts:
#	x-pack/plugins/lens/public/editor_frame_service/embeddable/embeddable.test.tsx
#	x-pack/plugins/lens/public/editor_frame_service/embeddable/embeddable.tsx

* remove canEdit

* replace lens page function

* changed lens average to avg

Co-authored-by: Kibana Machine <[email protected]>
  • Loading branch information
ThomThomson and kibanamachine authored Apr 6, 2021
1 parent 34441e6 commit c35270d
Show file tree
Hide file tree
Showing 26 changed files with 468 additions and 65 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,15 @@ const start = doStart();
let container: DashboardContainer;
let embeddable: ContactCardEmbeddable & ReferenceOrValueEmbeddable;
let coreStart: CoreStart;
let capabilities: CoreStart['application']['capabilities'];

beforeEach(async () => {
coreStart = coreMock.createStart();
capabilities = {
...coreStart.application.capabilities,
visualize: { save: true },
maps: { save: true },
};

const containerOptions = {
ExitFullScreenButton: () => null,
Expand Down Expand Up @@ -83,7 +90,10 @@ beforeEach(async () => {
});

test('Add to library is incompatible with Error Embeddables', async () => {
const action = new AddToLibraryAction({ toasts: coreStart.notifications.toasts });
const action = new AddToLibraryAction({
toasts: coreStart.notifications.toasts,
capabilities,
});
const errorEmbeddable = new ErrorEmbeddable(
'Wow what an awful error',
{ id: ' 404' },
Expand All @@ -92,20 +102,37 @@ test('Add to library is incompatible with Error Embeddables', async () => {
expect(await action.isCompatible({ embeddable: errorEmbeddable })).toBe(false);
});

test('Add to library is incompatible on visualize embeddable without visualize save permissions', async () => {
const action = new AddToLibraryAction({
toasts: coreStart.notifications.toasts,
capabilities: { ...capabilities, visualize: { save: false } },
});
expect(await action.isCompatible({ embeddable })).toBe(false);
});

test('Add to library is compatible when embeddable on dashboard has value type input', async () => {
const action = new AddToLibraryAction({ toasts: coreStart.notifications.toasts });
const action = new AddToLibraryAction({
toasts: coreStart.notifications.toasts,
capabilities,
});
embeddable.updateInput(await embeddable.getInputAsValueType());
expect(await action.isCompatible({ embeddable })).toBe(true);
});

test('Add to library is not compatible when embeddable input is by reference', async () => {
const action = new AddToLibraryAction({ toasts: coreStart.notifications.toasts });
const action = new AddToLibraryAction({
toasts: coreStart.notifications.toasts,
capabilities,
});
embeddable.updateInput(await embeddable.getInputAsRefType());
expect(await action.isCompatible({ embeddable })).toBe(false);
});

test('Add to library is not compatible when view mode is set to view', async () => {
const action = new AddToLibraryAction({ toasts: coreStart.notifications.toasts });
const action = new AddToLibraryAction({
toasts: coreStart.notifications.toasts,
capabilities,
});
embeddable.updateInput(await embeddable.getInputAsRefType());
embeddable.updateInput({ viewMode: ViewMode.VIEW });
expect(await action.isCompatible({ embeddable })).toBe(false);
Expand All @@ -126,7 +153,10 @@ test('Add to library is not compatible when embeddable is not in a dashboard con
mockedByReferenceInput: { savedObjectId: 'test', id: orphanContactCard.id },
mockedByValueInput: { firstName: 'Kibanana', id: orphanContactCard.id },
});
const action = new AddToLibraryAction({ toasts: coreStart.notifications.toasts });
const action = new AddToLibraryAction({
toasts: coreStart.notifications.toasts,
capabilities,
});
expect(await action.isCompatible({ embeddable: orphanContactCard })).toBe(false);
});

Expand All @@ -135,7 +165,10 @@ test('Add to library replaces embeddableId and retains panel count', async () =>
const originalPanelCount = Object.keys(dashboard.getInput().panels).length;
const originalPanelKeySet = new Set(Object.keys(dashboard.getInput().panels));

const action = new AddToLibraryAction({ toasts: coreStart.notifications.toasts });
const action = new AddToLibraryAction({
toasts: coreStart.notifications.toasts,
capabilities,
});
await action.execute({ embeddable });
expect(Object.keys(container.getInput().panels).length).toEqual(originalPanelCount);

Expand All @@ -161,7 +194,10 @@ test('Add to library returns reference type input', async () => {
});
const dashboard = embeddable.getRoot() as IContainer;
const originalPanelKeySet = new Set(Object.keys(dashboard.getInput().panels));
const action = new AddToLibraryAction({ toasts: coreStart.notifications.toasts });
const action = new AddToLibraryAction({
toasts: coreStart.notifications.toasts,
capabilities,
});
await action.execute({ embeddable });
const newPanelId = Object.keys(container.getInput().panels).find(
(key) => !originalPanelKeySet.has(key)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import {
isReferenceOrValueEmbeddable,
isErrorEmbeddable,
} from '../../services/embeddable';
import { NotificationsStart } from '../../services/core';
import { ApplicationStart, NotificationsStart } from '../../services/core';
import { dashboardAddToLibraryAction } from '../../dashboard_strings';
import { DashboardPanelState, DASHBOARD_CONTAINER_TYPE, DashboardContainer } from '..';

Expand All @@ -33,7 +33,12 @@ export class AddToLibraryAction implements Action<AddToLibraryActionContext> {
public readonly id = ACTION_ADD_TO_LIBRARY;
public order = 15;

constructor(private deps: { toasts: NotificationsStart['toasts'] }) {}
constructor(
private deps: {
toasts: NotificationsStart['toasts'];
capabilities: ApplicationStart['capabilities'];
}
) {}

public getDisplayName({ embeddable }: AddToLibraryActionContext) {
if (!embeddable.getRoot() || !embeddable.getRoot().isContainer) {
Expand All @@ -50,8 +55,15 @@ export class AddToLibraryAction implements Action<AddToLibraryActionContext> {
}

public async isCompatible({ embeddable }: AddToLibraryActionContext) {
// TODO: Fix this, potentially by adding a 'canSave' function to embeddable interface
const canSave =
embeddable.type === 'map'
? this.deps.capabilities.maps?.save
: this.deps.capabilities.visualize.save;

return Boolean(
!isErrorEmbeddable(embeddable) &&
canSave &&
!isErrorEmbeddable(embeddable) &&
embeddable.getInput()?.viewMode !== ViewMode.VIEW &&
embeddable.getRoot() &&
embeddable.getRoot().isContainer &&
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,8 @@ export class ClonePanelAction implements Action<ClonePanelActionContext> {
embeddable.getInput()?.viewMode !== ViewMode.VIEW &&
embeddable.getRoot() &&
embeddable.getRoot().isContainer &&
embeddable.getRoot().type === DASHBOARD_CONTAINER_TYPE
embeddable.getRoot().type === DASHBOARD_CONTAINER_TYPE &&
embeddable.getOutput().editable
);
}

Expand Down
11 changes: 7 additions & 4 deletions src/plugins/dashboard/public/plugin.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -342,7 +342,7 @@ export class DashboardPlugin
}

public start(core: CoreStart, plugins: DashboardStartDependencies): DashboardStart {
const { notifications, overlays } = core;
const { notifications, overlays, application } = core;
const { uiActions, data, share, presentationUtil, embeddable } = plugins;

const SavedObjectFinder = getSavedObjectFinder(core.savedObjects, core.uiSettings);
Expand Down Expand Up @@ -370,7 +370,10 @@ export class DashboardPlugin
}

if (this.dashboardFeatureFlagConfig?.allowByValueEmbeddables) {
const addToLibraryAction = new AddToLibraryAction({ toasts: notifications.toasts });
const addToLibraryAction = new AddToLibraryAction({
toasts: notifications.toasts,
capabilities: application.capabilities,
});
uiActions.registerAction(addToLibraryAction);
uiActions.attachAction(CONTEXT_MENU_TRIGGER, addToLibraryAction.id);

Expand All @@ -386,8 +389,8 @@ export class DashboardPlugin
overlays,
embeddable.getStateTransfer(),
{
canCreateNew: Boolean(core.application.capabilities.dashboard.createNew),
canEditExisting: !Boolean(core.application.capabilities.dashboard.hideWriteControls),
canCreateNew: Boolean(application.capabilities.dashboard.createNew),
canEditExisting: !Boolean(application.capabilities.dashboard.hideWriteControls),
},
presentationUtil.ContextProvider
);
Expand Down
1 change: 1 addition & 0 deletions src/plugins/dashboard/public/services/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,5 @@ export {
PluginInitializerContext,
ScopedHistory,
NotificationsStart,
ApplicationStart,
} from '../../../../core/public';
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,15 @@ interface SaveModalDocumentInfo {

export interface SaveModalDashboardProps {
documentInfo: SaveModalDocumentInfo;
canSaveByReference: boolean;
objectType: string;
onClose: () => void;
onSave: (props: OnSaveProps & { dashboardId: string | null; addToLibrary: boolean }) => void;
tagOptions?: React.ReactNode | ((state: SaveModalState) => React.ReactNode);
}

export function SavedObjectSaveModalDashboard(props: SaveModalDashboardProps) {
const { documentInfo, tagOptions, objectType, onClose } = props;
const { documentInfo, tagOptions, objectType, onClose, canSaveByReference } = props;
const { id: documentId } = documentInfo;
const initialCopyOnSave = !Boolean(documentId);

Expand All @@ -49,7 +50,7 @@ export function SavedObjectSaveModalDashboard(props: SaveModalDashboardProps) {
documentId || disableDashboardOptions ? null : 'existing'
);
const [isAddToLibrarySelected, setAddToLibrary] = useState<boolean>(
!initialCopyOnSave || disableDashboardOptions
canSaveByReference && (!initialCopyOnSave || disableDashboardOptions)
);
const [selectedDashboard, setSelectedDashboard] = useState<{ id: string; name: string } | null>(
null
Expand All @@ -65,13 +66,16 @@ export function SavedObjectSaveModalDashboard(props: SaveModalDashboardProps) {
onChange={(option) => {
setDashboardOption(option);
}}
canSaveByReference={canSaveByReference}
{...{ copyOnSave, documentId, dashboardOption, setAddToLibrary, isAddToLibrarySelected }}
/>
)
: null;

const onCopyOnSaveChange = (newCopyOnSave: boolean) => {
setAddToLibrary(true);
if (canSaveByReference) {
setAddToLibrary(true);
}
setDashboardOption(null);
setCopyOnSave(newCopyOnSave);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,15 +33,21 @@ export default {
control: 'boolean',
defaultValue: true,
},
canSaveVisualizations: {
control: 'boolean',
defaultValue: true,
},
},
};

export function Example({
copyOnSave,
hasDocumentId,
canSaveVisualizations,
}: {
copyOnSave: boolean;
hasDocumentId: boolean;
canSaveVisualizations: boolean;
} & StorybookParams) {
const [dashboardOption, setDashboardOption] = useState<'new' | 'existing' | null>('existing');
const [isAddToLibrarySelected, setAddToLibrary] = useState(false);
Expand All @@ -52,6 +58,7 @@ export function Example({
onChange={setDashboardOption}
dashboardOption={dashboardOption}
copyOnSave={copyOnSave}
canSaveByReference={canSaveVisualizations}
documentId={hasDocumentId ? 'abc' : undefined}
isAddToLibrarySelected={isAddToLibrarySelected}
setAddToLibrary={setAddToLibrary}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ export interface SaveModalDashboardSelectorProps {
copyOnSave: boolean;
documentId?: string;
onSelectDashboard: DashboardPickerProps['onChange'];
canSaveByReference: boolean;
setAddToLibrary: (selected: boolean) => void;
isAddToLibrarySelected: boolean;
dashboardOption: 'new' | 'existing' | null;
Expand All @@ -40,6 +41,7 @@ export function SaveModalDashboardSelector(props: SaveModalDashboardSelectorProp
const {
documentId,
onSelectDashboard,
canSaveByReference,
setAddToLibrary,
isAddToLibrarySelected,
dashboardOption,
Expand Down Expand Up @@ -114,7 +116,7 @@ export function SaveModalDashboardSelector(props: SaveModalDashboardSelectorProp
setAddToLibrary(true);
onChange(null);
}}
disabled={isDisabled}
disabled={isDisabled || !canSaveByReference}
/>
</div>
</EuiPanel>
Expand All @@ -127,7 +129,7 @@ export function SaveModalDashboardSelector(props: SaveModalDashboardSelectorProp
defaultMessage: 'Add to library',
})}
checked={isAddToLibrarySelected}
disabled={dashboardOption === null || isDisabled}
disabled={dashboardOption === null || isDisabled || !canSaveByReference}
onChange={(event) => setAddToLibrary(event.target.checked)}
/>
</EuiFlexItem>
Expand Down
1 change: 1 addition & 0 deletions src/plugins/presentation_util/public/services/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export interface PresentationDashboardsService {
export interface PresentationCapabilitiesService {
canAccessDashboards: () => boolean;
canCreateNewDashboards: () => boolean;
canSaveVisualizations: () => boolean;
}

export interface PresentationUtilServices {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,11 @@ export type CapabilitiesServiceFactory = KibanaPluginServiceFactory<
>;

export const capabilitiesServiceFactory: CapabilitiesServiceFactory = ({ coreStart }) => {
const { dashboard } = coreStart.application.capabilities;
const { dashboard, visualize } = coreStart.application.capabilities;

return {
canAccessDashboards: () => Boolean(dashboard.show),
canCreateNewDashboards: () => Boolean(dashboard.createNew),
canSaveVisualizations: () => Boolean(visualize.save),
};
};
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,13 @@ export const capabilitiesServiceFactory: CapabilitiesServiceFactory = ({
canAccessDashboards,
canCreateNewDashboards,
canEditDashboards,
canSaveVisualizations,
}) => {
const check = (value: boolean = true) => value;
return {
canAccessDashboards: () => check(canAccessDashboards),
canCreateNewDashboards: () => check(canCreateNewDashboards),
canEditDashboards: () => check(canEditDashboards),
canSaveVisualizations: () => check(canSaveVisualizations),
};
};
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export interface StorybookParams {
canAccessDashboards?: boolean;
canCreateNewDashboards?: boolean;
canEditDashboards?: boolean;
canSaveVisualizations?: boolean;
}

export const providers: PluginServiceProviders<PresentationUtilServices, StorybookParams> = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,5 @@ export const capabilitiesServiceFactory: CapabilitiesServiceFactory = () => ({
canAccessDashboards: () => true,
canCreateNewDashboards: () => true,
canEditDashboards: () => true,
canSaveVisualizations: () => true,
});
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,10 @@ export const createVisEmbeddableFromObject = (deps: VisualizeEmbeddableFactoryDe
indexPatterns = [vis.data.indexPattern];
}

const editable = getCapabilities().visualize.save as boolean;
const capabilities = {
visualizeSave: Boolean(getCapabilities().visualize.save),
dashboardSave: Boolean(getCapabilities().dashboard?.showWriteControls),
};

return new VisualizeEmbeddable(
getTimeFilter(),
Expand All @@ -76,8 +79,8 @@ export const createVisEmbeddableFromObject = (deps: VisualizeEmbeddableFactoryDe
indexPatterns,
editPath,
editUrl,
editable,
deps,
capabilities,
},
input,
attributeService,
Expand Down
Loading

0 comments on commit c35270d

Please sign in to comment.