[Search] Session feature controls (#85846)

* Use filter to bulk find

* Update x-pack/plugins/data_enhanced/server/search/session/session_service.ts

Co-authored-by: Lukas Olson <olson.lukas@gmail.com>

* Dashboard in space test

* Add warning on update failure

* fix merge

* Added functional test for sessions in space

* snapshot

* test cleanup

* sub perms

* test snapshots

* Update tests

* test

* code review

* snap

* Added discover test

* Update x-pack/plugins/data_enhanced/public/search/ui/connected_background_session_indicator/connected_background_session_indicator.tsx

Co-authored-by: Anton Dosov <dosantappdev@gmail.com>

Co-authored-by: Lukas Olson <olson.lukas@gmail.com>
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
Co-authored-by: Anton Dosov <dosantappdev@gmail.com>
This commit is contained in:
Liza Katz 2021-01-04 20:57:51 +02:00 committed by GitHub
parent 9ed0bbddde
commit 4415e548b5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 262 additions and 10 deletions

View file

@ -57,8 +57,35 @@ test('should show indicator in case there is an active search session', async ()
await waitFor(() => getByTestId('backgroundSessionIndicator')); await waitFor(() => getByTestId('backgroundSessionIndicator'));
}); });
test('should be disabled when permissions are off', async () => {
const state$ = new BehaviorSubject(SessionState.Loading);
coreStart.application.currentAppId$ = new BehaviorSubject('discover');
(coreStart.application.capabilities as any) = {
discover: {
storeSearchSession: false,
},
};
const BackgroundSessionIndicator = createConnectedBackgroundSessionIndicator({
sessionService: { ...sessionService, state$ },
application: coreStart.application,
timeFilter,
});
render(<BackgroundSessionIndicator />);
await waitFor(() => screen.getByTestId('backgroundSessionIndicator'));
expect(screen.getByTestId('backgroundSessionIndicator').querySelector('button')).toBeDisabled();
});
test('should be disabled during auto-refresh', async () => { test('should be disabled during auto-refresh', async () => {
const state$ = new BehaviorSubject(SessionState.Loading); const state$ = new BehaviorSubject(SessionState.Loading);
coreStart.application.currentAppId$ = new BehaviorSubject('discover');
(coreStart.application.capabilities as any) = {
discover: {
storeSearchSession: true,
},
};
const BackgroundSessionIndicator = createConnectedBackgroundSessionIndicator({ const BackgroundSessionIndicator = createConnectedBackgroundSessionIndicator({
sessionService: { ...sessionService, state$ }, sessionService: { ...sessionService, state$ },
application: coreStart.application, application: coreStart.application,

View file

@ -29,12 +29,35 @@ export const createConnectedBackgroundSessionIndicator = ({
.getRefreshIntervalUpdate$() .getRefreshIntervalUpdate$()
.pipe(map(isAutoRefreshEnabled), distinctUntilChanged()); .pipe(map(isAutoRefreshEnabled), distinctUntilChanged());
const getCapabilitiesByAppId = (
capabilities: ApplicationStart['capabilities'],
appId?: string
) => {
switch (appId) {
case 'dashboards':
return capabilities.dashboard;
case 'discover':
return capabilities.discover;
default:
return undefined;
}
};
return () => { return () => {
const state = useObservable(sessionService.state$.pipe(debounceTime(500))); const state = useObservable(sessionService.state$.pipe(debounceTime(500)));
const autoRefreshEnabled = useObservable(isAutoRefreshEnabled$, isAutoRefreshEnabled()); const autoRefreshEnabled = useObservable(isAutoRefreshEnabled$, isAutoRefreshEnabled());
const appId = useObservable(application.currentAppId$, undefined);
let disabled = false; let disabled = false;
let disabledReasonText: string = ''; let disabledReasonText: string = '';
if (getCapabilitiesByAppId(application.capabilities, appId)?.storeSearchSession !== true) {
disabled = true;
disabledReasonText = i18n.translate('xpack.data.backgroundSessionIndicator.noCapability', {
defaultMessage: "You don't have permissions to send to background.",
});
}
if (autoRefreshEnabled) { if (autoRefreshEnabled) {
disabled = true; disabled = true;
disabledReasonText = i18n.translate( disabledReasonText = i18n.translate(

View file

@ -73,6 +73,7 @@ Array [
"dashboard", "dashboard",
"query", "query",
"url", "url",
"background-session",
], ],
"read": Array [ "read": Array [
"index-pattern", "index-pattern",
@ -83,7 +84,6 @@ Array [
"lens", "lens",
"map", "map",
"tag", "tag",
"background-session",
], ],
}, },
"ui": Array [ "ui": Array [
@ -92,6 +92,7 @@ Array [
"showWriteControls", "showWriteControls",
"saveQuery", "saveQuery",
"createShortUrl", "createShortUrl",
"storeSearchSession",
], ],
}, },
"privilegeId": "all", "privilegeId": "all",
@ -205,8 +206,8 @@ Array [
"search", "search",
"query", "query",
"index-pattern", "index-pattern",
"background-session",
"url", "url",
"background-session",
], ],
"read": Array [], "read": Array [],
}, },
@ -215,6 +216,7 @@ Array [
"save", "save",
"saveQuery", "saveQuery",
"createShortUrl", "createShortUrl",
"storeSearchSession",
], ],
}, },
"privilegeId": "all", "privilegeId": "all",
@ -557,6 +559,7 @@ Array [
"dashboard", "dashboard",
"query", "query",
"url", "url",
"background-session",
], ],
"read": Array [ "read": Array [
"index-pattern", "index-pattern",
@ -567,7 +570,6 @@ Array [
"lens", "lens",
"map", "map",
"tag", "tag",
"background-session",
], ],
}, },
"ui": Array [ "ui": Array [
@ -576,6 +578,7 @@ Array [
"showWriteControls", "showWriteControls",
"saveQuery", "saveQuery",
"createShortUrl", "createShortUrl",
"storeSearchSession",
], ],
}, },
"privilegeId": "all", "privilegeId": "all",
@ -689,8 +692,8 @@ Array [
"search", "search",
"query", "query",
"index-pattern", "index-pattern",
"background-session",
"url", "url",
"background-session",
], ],
"read": Array [], "read": Array [],
}, },
@ -699,6 +702,7 @@ Array [
"save", "save",
"saveQuery", "saveQuery",
"createShortUrl", "createShortUrl",
"storeSearchSession",
], ],
}, },
"privilegeId": "all", "privilegeId": "all",

View file

@ -28,7 +28,7 @@ export const buildOSSFeatures = ({ savedObjectTypes, includeTimelion }: BuildOSS
app: ['discover', 'kibana'], app: ['discover', 'kibana'],
catalogue: ['discover'], catalogue: ['discover'],
savedObject: { savedObject: {
all: ['search', 'query', 'index-pattern', 'background-session'], all: ['search', 'query', 'index-pattern'],
read: [], read: [],
}, },
ui: ['show', 'save', 'saveQuery'], ui: ['show', 'save', 'saveQuery'],
@ -71,6 +71,33 @@ export const buildOSSFeatures = ({ savedObjectTypes, includeTimelion }: BuildOSS
}, },
], ],
}, },
{
name: i18n.translate('xpack.features.ossFeatures.discoverSearchSessionsFeatureName', {
defaultMessage: 'Store Search Sessions',
}),
privilegeGroups: [
{
groupType: 'independent',
privileges: [
{
id: 'store_search_session',
name: i18n.translate(
'xpack.features.ossFeatures.discoverStoreSearchSessionsPrivilegeName',
{
defaultMessage: 'Store Search Sessions',
}
),
includeIn: 'all',
savedObject: {
all: ['background-session'],
read: [],
},
ui: ['storeSearchSession'],
},
],
},
],
},
], ],
}, },
{ {
@ -156,7 +183,6 @@ export const buildOSSFeatures = ({ savedObjectTypes, includeTimelion }: BuildOSS
'lens', 'lens',
'map', 'map',
'tag', 'tag',
'background-session',
], ],
}, },
ui: ['createNew', 'show', 'showWriteControls', 'saveQuery'], ui: ['createNew', 'show', 'showWriteControls', 'saveQuery'],
@ -210,6 +236,33 @@ export const buildOSSFeatures = ({ savedObjectTypes, includeTimelion }: BuildOSS
}, },
], ],
}, },
{
name: i18n.translate('xpack.features.ossFeatures.dashboardSearchSessionsFeatureName', {
defaultMessage: 'Store Search Sessions',
}),
privilegeGroups: [
{
groupType: 'independent',
privileges: [
{
id: 'store_search_session',
name: i18n.translate(
'xpack.features.ossFeatures.dashboardStoreSearchSessionsPrivilegeName',
{
defaultMessage: 'Store Search Sessions',
}
),
includeIn: 'all',
savedObject: {
all: ['background-session'],
read: [],
},
ui: ['storeSearchSession'],
},
],
},
],
},
], ],
}, },
{ {

View file

@ -24,6 +24,7 @@ export default function ({ getService }: FtrProviderContext) {
'minimal_all', 'minimal_all',
'minimal_read', 'minimal_read',
'url_create', 'url_create',
'store_search_session',
]; ];
const trialPrivileges = await supertest const trialPrivileges = await supertest
.get('/api/security/privileges') .get('/api/security/privileges')

View file

@ -20,9 +20,23 @@ export default function ({ getService }: FtrProviderContext) {
// Roles are associated with these privileges, and we shouldn't be removing them in a minor version. // Roles are associated with these privileges, and we shouldn't be removing them in a minor version.
const expected = { const expected = {
features: { features: {
discover: ['all', 'read', 'minimal_all', 'minimal_read', 'url_create'], discover: [
'all',
'read',
'minimal_all',
'minimal_read',
'url_create',
'store_search_session',
],
visualize: ['all', 'read', 'minimal_all', 'minimal_read', 'url_create'], visualize: ['all', 'read', 'minimal_all', 'minimal_read', 'url_create'],
dashboard: ['all', 'read', 'minimal_all', 'minimal_read', 'url_create'], dashboard: [
'all',
'read',
'minimal_all',
'minimal_read',
'url_create',
'store_search_session',
],
dev_tools: ['all', 'read'], dev_tools: ['all', 'read'],
advancedSettings: ['all', 'read'], advancedSettings: ['all', 'read'],
indexPatterns: ['all', 'read'], indexPatterns: ['all', 'read'],

View file

@ -20,7 +20,10 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) {
reportName: 'X-Pack Background Search UI (Enabled WIP Feature)', reportName: 'X-Pack Background Search UI (Enabled WIP Feature)',
}, },
testFiles: [resolve(__dirname, './tests/apps/dashboard/async_search')], testFiles: [
resolve(__dirname, './tests/apps/dashboard/async_search'),
resolve(__dirname, './tests/apps/discover'),
],
kbnTestServer: { kbnTestServer: {
...xpackFunctionalConfig.get('kbnTestServer'), ...xpackFunctionalConfig.get('kbnTestServer'),

View file

@ -34,7 +34,9 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
}, },
kibana: [ kibana: [
{ {
base: ['all'], feature: {
dashboard: ['minimal_read', 'store_search_session'],
},
spaces: ['another-space'], spaces: ['another-space'],
}, },
], ],
@ -54,6 +56,9 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
}); });
after(async () => { after(async () => {
await security.role.delete('data_analyst');
await security.user.delete('analyst');
await esArchiver.unload('dashboard/session_in_space'); await esArchiver.unload('dashboard/session_in_space');
await PageObjects.security.forceLogout(); await PageObjects.security.forceLogout();
}); });

View file

@ -0,0 +1,22 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { FtrProviderContext } from '../../../ftr_provider_context';
export default function ({ loadTestFile, getService }: FtrProviderContext) {
const kibanaServer = getService('kibanaServer');
const esArchiver = getService('esArchiver');
describe('async search', function () {
this.tags('ciGroup3');
before(async () => {
await esArchiver.loadIfNeeded('logstash_functional');
await kibanaServer.uiSettings.replace({ defaultIndex: 'logstash-*' });
});
loadTestFile(require.resolve('./sessions_in_space'));
});
}

View file

@ -0,0 +1,100 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { FtrProviderContext } from '../../../ftr_provider_context';
export default function ({ getService, getPageObjects }: FtrProviderContext) {
const testSubjects = getService('testSubjects');
const esArchiver = getService('esArchiver');
const security = getService('security');
const inspector = getService('inspector');
const PageObjects = getPageObjects([
'common',
'header',
'discover',
'visChart',
'security',
'timePicker',
]);
const browser = getService('browser');
const sendToBackground = getService('sendToBackground');
describe('discover in space', () => {
describe('Send to background in space', () => {
before(async () => {
await esArchiver.load('dashboard/session_in_space');
await security.role.create('data_analyst', {
elasticsearch: {
indices: [{ names: ['logstash-*'], privileges: ['all'] }],
},
kibana: [
{
feature: {
discover: ['all'],
},
spaces: ['another-space'],
},
],
});
await security.user.create('analyst', {
password: 'analyst-password',
roles: ['data_analyst'],
full_name: 'test user',
});
await PageObjects.security.forceLogout();
await PageObjects.security.login('analyst', 'analyst-password', {
expectSpaceSelector: false,
});
});
after(async () => {
await security.role.delete('data_analyst');
await security.user.delete('analyst');
await esArchiver.unload('dashboard/session_in_space');
await PageObjects.security.forceLogout();
});
it('Saves and restores a session', async () => {
await PageObjects.common.navigateToApp('discover', { basePath: 's/another-space' });
await PageObjects.discover.selectIndexPattern('logstash-*');
await PageObjects.timePicker.setAbsoluteRange(
'Sep 1, 2015 @ 00:00:00.000',
'Oct 1, 2015 @ 00:00:00.000'
);
await PageObjects.discover.waitForDocTableLoadingComplete();
await sendToBackground.expectState('completed');
await sendToBackground.save();
await sendToBackground.expectState('backgroundCompleted');
await inspector.open();
const savedSessionId = await (
await testSubjects.find('inspectorRequestSearchSessionId')
).getAttribute('data-search-session-id');
await inspector.close();
// load URL to restore a saved session
const url = await browser.getCurrentUrl();
const savedSessionURL = `${url}&searchSessionId=${savedSessionId}`;
await browser.get(savedSessionURL);
await PageObjects.header.waitUntilLoadingHasFinished();
await PageObjects.discover.waitForDocTableLoadingComplete();
// Check that session is restored
await sendToBackground.expectState('restored');
await testSubjects.missingOrFail('embeddableErrorLabel');
});
});
});
}