Skip to content

Commit

Permalink
Merge branch 'main' into fix/type-summarzer-windows-compat
Browse files Browse the repository at this point in the history
  • Loading branch information
Spencer committed Mar 7, 2022
2 parents 8c44eb0 + de3ae9b commit 78daff5
Show file tree
Hide file tree
Showing 10 changed files with 166 additions and 54 deletions.
3 changes: 2 additions & 1 deletion test/functional/apps/management/_scripted_fields_filter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
const esArchiver = getService('esArchiver');
const PageObjects = getPageObjects(['settings']);

describe('filter scripted fields', function describeIndexTests() {
// FLAKY: https://github.com/elastic/kibana/issues/126027
describe.skip('filter scripted fields', function describeIndexTests() {
before(async function () {
// delete .kibana index and then wait for Kibana to re-create it
await browser.setWindowSize(1200, 800);
Expand Down
3 changes: 2 additions & 1 deletion test/functional_ccs/apps/discover/_data_view_ccs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
await testSubjects.click('saveIndexPatternButton');
};

describe('discover integration with data view editor', function describeIndexTests() {
// FLAKY: https://github.com/elastic/kibana/issues/126658
describe.skip('discover integration with data view editor', function describeIndexTests() {
before(async function () {
await security.testUser.setRoles(['kibana_admin', 'test_logstash_reader']);
await esArchiver.loadIfNeeded('test/functional/fixtures/es_archiver/logstash_functional');
Expand Down
2 changes: 2 additions & 0 deletions x-pack/plugins/cloud/public/fullstory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,11 @@ export interface FullStoryDeps {
}

export type FullstoryUserVars = Record<string, any>;
export type FullstoryVars = Record<string, any>;

export interface FullStoryApi {
identify(userId: string, userVars?: FullstoryUserVars): void;
setVars(pageName: string, vars?: FullstoryVars): void;
setUserVars(userVars?: FullstoryUserVars): void;
event(eventName: string, eventProperties: Record<string, any>): void;
}
Expand Down
1 change: 1 addition & 0 deletions x-pack/plugins/cloud/public/plugin.test.mocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import type { FullStoryDeps, FullStoryApi, FullStoryService } from './fullstory'
export const fullStoryApiMock: jest.Mocked<FullStoryApi> = {
event: jest.fn(),
setUserVars: jest.fn(),
setVars: jest.fn(),
identify: jest.fn(),
};
export const initializeFullStoryMock = jest.fn<FullStoryService, [FullStoryDeps]>(() => ({
Expand Down
91 changes: 73 additions & 18 deletions x-pack/plugins/cloud/public/plugin.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { securityMock } from '../../security/public/mocks';
import { fullStoryApiMock, initializeFullStoryMock } from './plugin.test.mocks';
import { CloudPlugin, CloudConfigType, loadFullStoryUserId } from './plugin';
import { Observable, Subject } from 'rxjs';
import { KibanaExecutionContext } from 'kibana/public';

describe('Cloud Plugin', () => {
describe('#setup', () => {
Expand All @@ -24,12 +25,12 @@ describe('Cloud Plugin', () => {
config = {},
securityEnabled = true,
currentUserProps = {},
currentAppId$ = undefined,
currentContext$ = undefined,
}: {
config?: Partial<CloudConfigType>;
securityEnabled?: boolean;
currentUserProps?: Record<string, any>;
currentAppId$?: Observable<string | undefined>;
currentContext$?: Observable<KibanaExecutionContext>;
}) => {
const initContext = coreMock.createPluginInitializerContext({
id: 'cloudId',
Expand All @@ -51,8 +52,8 @@ describe('Cloud Plugin', () => {
const coreSetup = coreMock.createSetup();
const coreStart = coreMock.createStart();

if (currentAppId$) {
coreStart.application.currentAppId$ = currentAppId$;
if (currentContext$) {
coreStart.executionContext.context$ = currentContext$;
}

coreSetup.getStartServices.mockResolvedValue([coreStart, {}, undefined]);
Expand Down Expand Up @@ -94,44 +95,98 @@ describe('Cloud Plugin', () => {
});

expect(fullStoryApiMock.identify).toHaveBeenCalledWith(
'03ac674216f3e15c761ee1a5e255f067953623c8b388b4459e13f978d7c846f4',
'5ef112cfdae3dea57097bc276e275b2816e73ef2a398dc0ffaf5b6b4e3af2041',
{
version_str: 'version',
version_major_int: -1,
version_minor_int: -1,
version_patch_int: -1,
org_id_str: 'cloudId',
}
);
});

it('calls FS.setUserVars everytime an app changes', async () => {
const currentAppId$ = new Subject<string | undefined>();
it('user hash includes org id', async () => {
await setupPlugin({
config: { full_story: { enabled: true, org_id: 'foo' }, id: 'esOrg1' },
currentUserProps: {
username: '1234',
},
});

const hashId1 = fullStoryApiMock.identify.mock.calls[0][0];

await setupPlugin({
config: { full_story: { enabled: true, org_id: 'foo' }, id: 'esOrg2' },
currentUserProps: {
username: '1234',
},
});

const hashId2 = fullStoryApiMock.identify.mock.calls[1][0];

expect(hashId1).not.toEqual(hashId2);
});

it('calls FS.setVars everytime an app changes', async () => {
const currentContext$ = new Subject<KibanaExecutionContext>();
const { plugin } = await setupPlugin({
config: { full_story: { enabled: true, org_id: 'foo' } },
currentUserProps: {
username: '1234',
},
currentAppId$,
currentContext$,
});

expect(fullStoryApiMock.setUserVars).not.toHaveBeenCalled();
currentAppId$.next('App1');
expect(fullStoryApiMock.setUserVars).toHaveBeenCalledWith({
// takes the app name
expect(fullStoryApiMock.setVars).not.toHaveBeenCalled();
currentContext$.next({
name: 'App1',
description: '123',
});

expect(fullStoryApiMock.setVars).toHaveBeenCalledWith('page', {
pageName: 'App1',
app_id_str: 'App1',
});
currentAppId$.next();
expect(fullStoryApiMock.setUserVars).toHaveBeenCalledWith({
app_id_str: 'unknown',

// context clear
currentContext$.next({});
expect(fullStoryApiMock.setVars).toHaveBeenCalledWith('page', {
pageName: 'App1',
app_id_str: 'App1',
});

currentAppId$.next('App2');
expect(fullStoryApiMock.setUserVars).toHaveBeenCalledWith({
// different app
currentContext$.next({
name: 'App2',
page: 'page2',
id: '123',
});
expect(fullStoryApiMock.setVars).toHaveBeenCalledWith('page', {
pageName: 'App2:page2',
app_id_str: 'App2',
page_str: 'page2',
ent_id_str: '123',
});

// Back to first app
currentContext$.next({
name: 'App1',
page: 'page3',
id: '123',
});

expect(fullStoryApiMock.setVars).toHaveBeenCalledWith('page', {
pageName: 'App1:page3',
app_id_str: 'App1',
page_str: 'page3',
ent_id_str: '123',
});

expect(currentAppId$.observers.length).toBe(1);
expect(currentContext$.observers.length).toBe(1);
plugin.stop();
expect(currentAppId$.observers.length).toBe(0);
expect(currentContext$.observers.length).toBe(0);
});

it('does not call FS.identify when security is not available', async () => {
Expand Down
64 changes: 45 additions & 19 deletions x-pack/plugins/cloud/public/plugin.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,12 @@ import {
PluginInitializerContext,
HttpStart,
IBasePath,
ApplicationStart,
ExecutionContextStart,
} from 'src/core/public';
import { i18n } from '@kbn/i18n';
import useObservable from 'react-use/lib/useObservable';
import { BehaviorSubject, Subscription } from 'rxjs';
import { compact, isUndefined, omitBy } from 'lodash';
import type {
AuthenticatedUser,
SecurityPluginSetup,
Expand Down Expand Up @@ -83,8 +84,9 @@ export interface CloudSetup {
}

interface SetupFullstoryDeps extends CloudSetupDependencies {
application?: Promise<ApplicationStart>;
executionContextPromise?: Promise<ExecutionContextStart>;
basePath: IBasePath;
esOrgId?: string;
}

interface SetupChatDeps extends Pick<CloudSetupDependencies, 'security'> {
Expand All @@ -103,11 +105,16 @@ export class CloudPlugin implements Plugin<CloudSetup> {
}

public setup(core: CoreSetup, { home, security }: CloudSetupDependencies) {
const application = core.getStartServices().then(([coreStart]) => {
return coreStart.application;
const executionContextPromise = core.getStartServices().then(([coreStart]) => {
return coreStart.executionContext;
});

this.setupFullstory({ basePath: core.http.basePath, security, application }).catch((e) =>
this.setupFullstory({
basePath: core.http.basePath,
security,
executionContextPromise,
esOrgId: this.config.id,
}).catch((e) =>
// eslint-disable-next-line no-console
console.debug(`Error setting up FullStory: ${e.toString()}`)
);
Expand Down Expand Up @@ -223,9 +230,14 @@ export class CloudPlugin implements Plugin<CloudSetup> {
return user?.roles.includes('superuser') ?? true;
}

private async setupFullstory({ basePath, security, application }: SetupFullstoryDeps) {
const { enabled, org_id: orgId } = this.config.full_story;
if (!enabled || !orgId) {
private async setupFullstory({
basePath,
security,
executionContextPromise,
esOrgId,
}: SetupFullstoryDeps) {
const { enabled, org_id: fsOrgId } = this.config.full_story;
if (!enabled || !fsOrgId) {
return; // do not load any fullstory code in the browser if not enabled
}

Expand All @@ -243,7 +255,7 @@ export class CloudPlugin implements Plugin<CloudSetup> {

const { fullStory, sha256 } = initializeFullStory({
basePath,
orgId,
orgId: fsOrgId,
packageInfo: this.initializerContext.env.packageInfo,
});

Expand All @@ -252,16 +264,29 @@ export class CloudPlugin implements Plugin<CloudSetup> {
// This needs to be called syncronously to be sure that we populate the user ID soon enough to make sessions merging
// across domains work
if (userId) {
// Do the hashing here to keep it at clear as possible in our source code that we do not send literal user IDs
const hashedId = sha256(userId.toString());
application
?.then(async () => {
const appStart = await application;
this.appSubscription = appStart.currentAppId$.subscribe((appId) => {
// Update the current application every time it changes
fullStory.setUserVars({
app_id_str: appId ?? 'unknown',
});
// Join the cloud org id and the user to create a truly unique user id.
// The hashing here is to keep it at clear as possible in our source code that we do not send literal user IDs
const hashedId = sha256(esOrgId ? `${esOrgId}:${userId}` : `${userId}`);

executionContextPromise
?.then(async (executionContext) => {
this.appSubscription = executionContext.context$.subscribe((context) => {
const { name, page, id } = context;
// Update the current context every time it changes
fullStory.setVars(
'page',
omitBy(
{
// Read about the special pageName property
// https://help.fullstory.com/hc/en-us/articles/1500004101581-FS-setVars-API-Sending-custom-page-data-to-FullStory
pageName: `${compact([name, page]).join(':')}`,
app_id_str: name ?? 'unknown',
page_str: page,
ent_id_str: id,
},
isUndefined
)
);
});
})
.catch((e) => {
Expand All @@ -282,6 +307,7 @@ export class CloudPlugin implements Plugin<CloudSetup> {
version_major_int: parsedVer[0] ?? -1,
version_minor_int: parsedVer[1] ?? -1,
version_patch_int: parsedVer[2] ?? -1,
org_id_str: esOrgId,
});
}
} catch (e) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ describe('getRoleAbilities', () => {
// Has access
canViewAccountCredentials: true,
canManageEngines: true,
canManageMetaEngines: true,
// Does not have access
canViewMetaEngines: false,
canViewEngineAnalytics: false,
Expand All @@ -35,7 +36,6 @@ describe('getRoleAbilities', () => {
canViewMetaEngineSourceEngines: false,
canViewSettings: false,
canViewRoleMappings: false,
canManageMetaEngines: false,
canManageLogSettings: false,
canManageSettings: false,
canManageEngineCrawler: false,
Expand Down Expand Up @@ -81,10 +81,10 @@ describe('getRoleAbilities', () => {
expect(myRole.canManageMetaEngines).toEqual(true);
});

it('returns false when the user can manage any engines but the account does not have a platinum license', () => {
it('returns true when the user can manage any engines but the account does not have a platinum license', () => {
const myRole = getRoleAbilities({ ...mockRole, ...canManageEngines }, false);

expect(myRole.canManageMetaEngines).toEqual(false);
expect(myRole.canManageMetaEngines).toEqual(true);
});

it('returns false when has a platinum license but the user cannot manage any engines', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ export const getRoleAbilities = (role: Account['role'], hasPlatinumLicense = fal
canViewSettings: myRole.can('view', 'account_settings'),
canViewRoleMappings: myRole.can('view', 'role_mappings'),
canManageEngines: myRole.can('manage', 'account_engines'),
canManageMetaEngines: hasPlatinumLicense && myRole.can('manage', 'account_engines'),
canManageMetaEngines: myRole.can('manage', 'account_engines'),
canManageLogSettings: myRole.can('manage', 'account_log_settings'),
canManageSettings: myRole.can('manage', 'account_settings'),
canManageEngineCrawler: myRole.can('manage', 'engine_crawler'),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,17 @@ export class DataRecognizer {
private _resultsService: ReturnType<typeof resultsServiceProvider>;
private _calculateModelMemoryLimit: ReturnType<typeof calculateModelMemoryLimitProvider>;

/**
* A temporary cache of configs loaded from disk and from save object service.
* The configs from disk will not change while kibana is running.
* The configs from saved objects could potentially change while an instance of
* DataRecognizer exists, if a fleet package containing modules is installed.
* However the chance of this happening is very low and so the benefit of using
* this cache outweighs the risk of the cache being out of date during the short
* existence of a DataRecognizer instance.
*/
private _configCache: Config[] | null = null;

/**
* List of the module jobs that require model memory estimation
*/
Expand Down Expand Up @@ -181,6 +192,10 @@ export class DataRecognizer {
}

private async _loadConfigs(): Promise<Config[]> {
if (this._configCache !== null) {
return this._configCache;
}

const configs: Config[] = [];
const dirs = await this._listDirs(this._modulesDir);
await Promise.all(
Expand Down Expand Up @@ -211,7 +226,9 @@ export class DataRecognizer {
isSavedObject: true,
}));

return [...configs, ...savedObjectConfigs];
this._configCache = [...configs, ...savedObjectConfigs];

return this._configCache;
}

private async _loadSavedObjectModules() {
Expand Down
Loading

0 comments on commit 78daff5

Please sign in to comment.