Improve extend search session UI (#89126)
* Fix extend search session UI * code review
This commit is contained in:
parent
1994c5bfd9
commit
de44af3eeb
|
@ -66,8 +66,8 @@ export class DataEnhancedPlugin
|
|||
|
||||
this.config = this.initializerContext.config.get<ConfigSchema>();
|
||||
if (this.config.search.sessions.enabled) {
|
||||
const { management: sessionsMgmtConfig } = this.config.search.sessions;
|
||||
registerSearchSessionsMgmt(core, sessionsMgmtConfig, { management });
|
||||
const sessionsConfig = this.config.search.sessions;
|
||||
registerSearchSessionsMgmt(core, sessionsConfig, { management });
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@ import type {
|
|||
AppDependencies,
|
||||
IManagementSectionsPluginsSetup,
|
||||
IManagementSectionsPluginsStart,
|
||||
SessionsMgmtConfigSchema,
|
||||
SessionsConfigSchema,
|
||||
} from '../';
|
||||
import { APP } from '../';
|
||||
import { SearchSessionsMgmtAPI } from '../lib/api';
|
||||
|
@ -20,7 +20,7 @@ import { renderApp } from './render';
|
|||
export class SearchSessionsMgmtApp {
|
||||
constructor(
|
||||
private coreSetup: CoreSetup<IManagementSectionsPluginsStart>,
|
||||
private config: SessionsMgmtConfigSchema,
|
||||
private config: SessionsConfigSchema,
|
||||
private params: ManagementAppMountParams,
|
||||
private pluginsSetup: IManagementSectionsPluginsSetup
|
||||
) {}
|
||||
|
|
|
@ -8,6 +8,8 @@ import { EuiConfirmModal, EuiOverlayMask } from '@elastic/eui';
|
|||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import React, { useState } from 'react';
|
||||
import { Duration } from 'moment';
|
||||
import moment from 'moment';
|
||||
import { SearchSessionsMgmtAPI } from '../../lib/api';
|
||||
import { TableText } from '../';
|
||||
import { OnActionComplete } from './types';
|
||||
|
@ -15,6 +17,8 @@ import { OnActionComplete } from './types';
|
|||
interface ExtendButtonProps {
|
||||
id: string;
|
||||
name: string;
|
||||
expires: string | null;
|
||||
extendBy: Duration;
|
||||
api: SearchSessionsMgmtAPI;
|
||||
onActionComplete: OnActionComplete;
|
||||
}
|
||||
|
@ -23,8 +27,11 @@ const ExtendConfirm = ({
|
|||
onConfirmDismiss,
|
||||
...props
|
||||
}: ExtendButtonProps & { onConfirmDismiss: () => void }) => {
|
||||
const { id, name, api, onActionComplete } = props;
|
||||
const { id, name, expires, api, extendBy, onActionComplete } = props;
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const extendByDuration = moment.duration(extendBy);
|
||||
|
||||
const newExpiration = moment(expires).add(extendByDuration);
|
||||
|
||||
const title = i18n.translate('xpack.data.mgmt.searchSessions.extendModal.title', {
|
||||
defaultMessage: 'Extend search session expiration',
|
||||
|
@ -36,9 +43,10 @@ const ExtendConfirm = ({
|
|||
defaultMessage: 'Cancel',
|
||||
});
|
||||
const message = i18n.translate('xpack.data.mgmt.searchSessions.extendModal.extendMessage', {
|
||||
defaultMessage: "When would you like the search session '{name}' to expire?",
|
||||
defaultMessage: "The search session '{name}' expiration would be extended until {newExpires}.",
|
||||
values: {
|
||||
name,
|
||||
newExpires: newExpiration.toLocaleString(),
|
||||
},
|
||||
});
|
||||
|
||||
|
@ -49,7 +57,7 @@ const ExtendConfirm = ({
|
|||
onCancel={onConfirmDismiss}
|
||||
onConfirm={async () => {
|
||||
setIsLoading(true);
|
||||
await api.sendExtend(id, '1');
|
||||
await api.sendExtend(id, `${extendByDuration.asMilliseconds()}ms`);
|
||||
onActionComplete();
|
||||
}}
|
||||
confirmButtonText={confirm}
|
||||
|
|
|
@ -17,7 +17,7 @@ import { ACTION, OnActionComplete } from './types';
|
|||
export const getAction = (
|
||||
api: SearchSessionsMgmtAPI,
|
||||
actionType: string,
|
||||
{ id, name, reloadUrl }: UISession,
|
||||
{ id, name, expires, reloadUrl }: UISession,
|
||||
onActionComplete: OnActionComplete
|
||||
): IClickActionDescriptor | null => {
|
||||
switch (actionType) {
|
||||
|
@ -39,7 +39,16 @@ export const getAction = (
|
|||
return {
|
||||
iconType: extendSessionIcon,
|
||||
textColor: 'default',
|
||||
label: <ExtendButton api={api} id={id} name={name} onActionComplete={onActionComplete} />,
|
||||
label: (
|
||||
<ExtendButton
|
||||
api={api}
|
||||
id={id}
|
||||
name={name}
|
||||
expires={expires}
|
||||
extendBy={api.getExtendByDuration()}
|
||||
onActionComplete={onActionComplete}
|
||||
/>
|
||||
),
|
||||
};
|
||||
|
||||
default:
|
||||
|
|
|
@ -12,7 +12,7 @@ import React from 'react';
|
|||
import { act } from 'react-dom/test-utils';
|
||||
import { coreMock } from 'src/core/public/mocks';
|
||||
import { SessionsClient } from 'src/plugins/data/public/search';
|
||||
import { SessionsMgmtConfigSchema } from '..';
|
||||
import { SessionsConfigSchema } from '..';
|
||||
import { SearchSessionsMgmtAPI } from '../lib/api';
|
||||
import { AsyncSearchIntroDocumentation } from '../lib/documentation';
|
||||
import { LocaleWrapper, mockUrls } from '../__mocks__';
|
||||
|
@ -20,7 +20,7 @@ import { SearchSessionsMgmtMain } from './main';
|
|||
|
||||
let mockCoreSetup: MockedKeys<CoreSetup>;
|
||||
let mockCoreStart: MockedKeys<CoreStart>;
|
||||
let mockConfig: SessionsMgmtConfigSchema;
|
||||
let mockConfig: SessionsConfigSchema;
|
||||
let sessionsClient: SessionsClient;
|
||||
let api: SearchSessionsMgmtAPI;
|
||||
|
||||
|
@ -29,11 +29,14 @@ describe('Background Search Session Management Main', () => {
|
|||
mockCoreSetup = coreMock.createSetup();
|
||||
mockCoreStart = coreMock.createStart();
|
||||
mockConfig = {
|
||||
defaultExpiration: moment.duration('7d'),
|
||||
management: {
|
||||
expiresSoonWarning: moment.duration(1, 'days'),
|
||||
maxSessions: 2000,
|
||||
refreshInterval: moment.duration(1, 'seconds'),
|
||||
refreshTimeout: moment.duration(10, 'minutes'),
|
||||
};
|
||||
},
|
||||
} as any;
|
||||
|
||||
sessionsClient = new SessionsClient({ http: mockCoreSetup.http });
|
||||
|
||||
|
|
|
@ -17,7 +17,7 @@ import {
|
|||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import type { CoreStart, HttpStart } from 'kibana/public';
|
||||
import React from 'react';
|
||||
import type { SessionsMgmtConfigSchema } from '../';
|
||||
import type { SessionsConfigSchema } from '../';
|
||||
import type { SearchSessionsMgmtAPI } from '../lib/api';
|
||||
import type { AsyncSearchIntroDocumentation } from '../lib/documentation';
|
||||
import { TableText } from './';
|
||||
|
@ -29,7 +29,7 @@ interface Props {
|
|||
api: SearchSessionsMgmtAPI;
|
||||
http: HttpStart;
|
||||
timezone: string;
|
||||
config: SessionsMgmtConfigSchema;
|
||||
config: SessionsConfigSchema;
|
||||
}
|
||||
|
||||
export function SearchSessionsMgmtMain({ documentation, ...tableProps }: Props) {
|
||||
|
|
|
@ -13,14 +13,14 @@ import React from 'react';
|
|||
import { coreMock } from 'src/core/public/mocks';
|
||||
import { SessionsClient } from 'src/plugins/data/public/search';
|
||||
import { SearchSessionStatus } from '../../../../../common/search';
|
||||
import { SessionsMgmtConfigSchema } from '../../';
|
||||
import { SessionsConfigSchema } from '../../';
|
||||
import { SearchSessionsMgmtAPI } from '../../lib/api';
|
||||
import { LocaleWrapper, mockUrls } from '../../__mocks__';
|
||||
import { SearchSessionsMgmtTable } from './table';
|
||||
|
||||
let mockCoreSetup: MockedKeys<CoreSetup>;
|
||||
let mockCoreStart: CoreStart;
|
||||
let mockConfig: SessionsMgmtConfigSchema;
|
||||
let mockConfig: SessionsConfigSchema;
|
||||
let sessionsClient: SessionsClient;
|
||||
let api: SearchSessionsMgmtAPI;
|
||||
|
||||
|
@ -29,11 +29,14 @@ describe('Background Search Session Management Table', () => {
|
|||
mockCoreSetup = coreMock.createSetup();
|
||||
mockCoreStart = coreMock.createStart();
|
||||
mockConfig = {
|
||||
defaultExpiration: moment.duration('7d'),
|
||||
management: {
|
||||
expiresSoonWarning: moment.duration(1, 'days'),
|
||||
maxSessions: 2000,
|
||||
refreshInterval: moment.duration(1, 'seconds'),
|
||||
refreshTimeout: moment.duration(10, 'minutes'),
|
||||
};
|
||||
},
|
||||
} as any;
|
||||
|
||||
sessionsClient = new SessionsClient({ http: mockCoreSetup.http });
|
||||
api = new SearchSessionsMgmtAPI(sessionsClient, mockConfig, {
|
||||
|
@ -134,7 +137,10 @@ describe('Background Search Session Management Table', () => {
|
|||
sessionsClient.find = jest.fn();
|
||||
mockConfig = {
|
||||
...mockConfig,
|
||||
management: {
|
||||
...mockConfig.management,
|
||||
refreshInterval: moment.duration(10, 'seconds'),
|
||||
},
|
||||
};
|
||||
|
||||
await act(async () => {
|
||||
|
@ -162,8 +168,11 @@ describe('Background Search Session Management Table', () => {
|
|||
|
||||
mockConfig = {
|
||||
...mockConfig,
|
||||
management: {
|
||||
...mockConfig.management,
|
||||
refreshInterval: moment.duration(1, 'day'),
|
||||
refreshTimeout: moment.duration(2, 'days'),
|
||||
},
|
||||
};
|
||||
|
||||
await act(async () => {
|
||||
|
|
|
@ -12,7 +12,7 @@ import React, { useCallback, useMemo, useRef, useEffect, useState } from 'react'
|
|||
import useDebounce from 'react-use/lib/useDebounce';
|
||||
import useInterval from 'react-use/lib/useInterval';
|
||||
import { TableText } from '../';
|
||||
import { SessionsMgmtConfigSchema } from '../..';
|
||||
import { SessionsConfigSchema } from '../..';
|
||||
import { SearchSessionsMgmtAPI } from '../../lib/api';
|
||||
import { getColumns } from '../../lib/get_columns';
|
||||
import { UISession } from '../../types';
|
||||
|
@ -26,7 +26,7 @@ interface Props {
|
|||
core: CoreStart;
|
||||
api: SearchSessionsMgmtAPI;
|
||||
timezone: string;
|
||||
config: SessionsMgmtConfigSchema;
|
||||
config: SessionsConfigSchema;
|
||||
}
|
||||
|
||||
export function SearchSessionsMgmtTable({ core, api, timezone, config, ...props }: Props) {
|
||||
|
@ -35,9 +35,10 @@ export function SearchSessionsMgmtTable({ core, api, timezone, config, ...props
|
|||
const [debouncedIsLoading, setDebouncedIsLoading] = useState(false);
|
||||
const [pagination, setPagination] = useState({ pageIndex: 0 });
|
||||
const showLatestResultsHandler = useRef<Function>();
|
||||
const refreshInterval = useMemo(() => moment.duration(config.refreshInterval).asMilliseconds(), [
|
||||
config.refreshInterval,
|
||||
]);
|
||||
const refreshInterval = useMemo(
|
||||
() => moment.duration(config.management.refreshInterval).asMilliseconds(),
|
||||
[config.management.refreshInterval]
|
||||
);
|
||||
|
||||
// Debounce rendering the state of the Refresh button
|
||||
useDebounce(
|
||||
|
|
|
@ -33,7 +33,7 @@ export interface AppDependencies {
|
|||
api: SearchSessionsMgmtAPI;
|
||||
http: HttpStart;
|
||||
i18n: I18nStart;
|
||||
config: SessionsMgmtConfigSchema;
|
||||
config: SessionsConfigSchema;
|
||||
}
|
||||
|
||||
export const APP = {
|
||||
|
@ -44,11 +44,11 @@ export const APP = {
|
|||
}),
|
||||
};
|
||||
|
||||
export type SessionsMgmtConfigSchema = ConfigSchema['search']['sessions']['management'];
|
||||
export type SessionsConfigSchema = ConfigSchema['search']['sessions'];
|
||||
|
||||
export function registerSearchSessionsMgmt(
|
||||
coreSetup: CoreSetup<DataEnhancedStartDependencies>,
|
||||
config: SessionsMgmtConfigSchema,
|
||||
config: SessionsConfigSchema,
|
||||
services: IManagementSectionsPluginsSetup
|
||||
) {
|
||||
services.management.sections.section.kibana.registerApp({
|
||||
|
|
|
@ -11,14 +11,14 @@ import { coreMock } from 'src/core/public/mocks';
|
|||
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
|
||||
import type { SavedObjectsFindResponse } from 'src/core/server';
|
||||
import { SessionsClient } from 'src/plugins/data/public/search';
|
||||
import type { SessionsMgmtConfigSchema } from '../';
|
||||
import type { SessionsConfigSchema } from '../';
|
||||
import { SearchSessionStatus } from '../../../../common/search';
|
||||
import { mockUrls } from '../__mocks__';
|
||||
import { SearchSessionsMgmtAPI } from './api';
|
||||
|
||||
let mockCoreSetup: MockedKeys<CoreSetup>;
|
||||
let mockCoreStart: MockedKeys<CoreStart>;
|
||||
let mockConfig: SessionsMgmtConfigSchema;
|
||||
let mockConfig: SessionsConfigSchema;
|
||||
let sessionsClient: SessionsClient;
|
||||
|
||||
describe('Search Sessions Management API', () => {
|
||||
|
@ -26,11 +26,14 @@ describe('Search Sessions Management API', () => {
|
|||
mockCoreSetup = coreMock.createSetup();
|
||||
mockCoreStart = coreMock.createStart();
|
||||
mockConfig = {
|
||||
expiresSoonWarning: moment.duration('1d'),
|
||||
defaultExpiration: moment.duration('7d'),
|
||||
management: {
|
||||
expiresSoonWarning: moment.duration(1, 'days'),
|
||||
maxSessions: 2000,
|
||||
refreshInterval: moment.duration('1s'),
|
||||
refreshTimeout: moment.duration('10m'),
|
||||
};
|
||||
refreshInterval: moment.duration(1, 'seconds'),
|
||||
refreshTimeout: moment.duration(10, 'minutes'),
|
||||
},
|
||||
} as any;
|
||||
|
||||
sessionsClient = new SessionsClient({ http: mockCoreSetup.http });
|
||||
});
|
||||
|
@ -93,8 +96,11 @@ describe('Search Sessions Management API', () => {
|
|||
test('handle timeout error', async () => {
|
||||
mockConfig = {
|
||||
...mockConfig,
|
||||
management: {
|
||||
...mockConfig.management,
|
||||
refreshInterval: moment.duration(1, 'hours'),
|
||||
refreshTimeout: moment.duration(1, 'seconds'),
|
||||
},
|
||||
};
|
||||
|
||||
sessionsClient.find = jest.fn().mockImplementation(async () => {
|
||||
|
|
|
@ -10,7 +10,7 @@ import moment from 'moment';
|
|||
import { from, race, timer } from 'rxjs';
|
||||
import { mapTo, tap } from 'rxjs/operators';
|
||||
import type { SharePluginStart } from 'src/plugins/share/public';
|
||||
import { SessionsMgmtConfigSchema } from '../';
|
||||
import { SessionsConfigSchema } from '../';
|
||||
import type { ISessionsClient } from '../../../../../../../src/plugins/data/public';
|
||||
import type { SearchSessionSavedObjectAttributes } from '../../../../common';
|
||||
import { SearchSessionStatus } from '../../../../common/search';
|
||||
|
@ -47,10 +47,9 @@ async function getUrlFromState(
|
|||
}
|
||||
|
||||
// Helper: factory for a function to map server objects to UI objects
|
||||
const mapToUISession = (
|
||||
urls: UrlGeneratorsStart,
|
||||
{ expiresSoonWarning }: SessionsMgmtConfigSchema
|
||||
) => async (savedObject: SavedObject<SearchSessionSavedObjectAttributes>): Promise<UISession> => {
|
||||
const mapToUISession = (urls: UrlGeneratorsStart, config: SessionsConfigSchema) => async (
|
||||
savedObject: SavedObject<SearchSessionSavedObjectAttributes>
|
||||
): Promise<UISession> => {
|
||||
const {
|
||||
name,
|
||||
appId,
|
||||
|
@ -92,7 +91,7 @@ interface SearcgSessuibManagementDeps {
|
|||
export class SearchSessionsMgmtAPI {
|
||||
constructor(
|
||||
private sessionsClient: ISessionsClient,
|
||||
private config: SessionsMgmtConfigSchema,
|
||||
private config: SessionsConfigSchema,
|
||||
private deps: SearcgSessuibManagementDeps
|
||||
) {}
|
||||
|
||||
|
@ -101,12 +100,14 @@ export class SearchSessionsMgmtAPI {
|
|||
saved_objects: object[];
|
||||
}
|
||||
|
||||
const refreshTimeout = moment.duration(this.config.refreshTimeout);
|
||||
const mgmtConfig = this.config.management;
|
||||
|
||||
const refreshTimeout = moment.duration(mgmtConfig.refreshTimeout);
|
||||
|
||||
const fetch$ = from(
|
||||
this.sessionsClient.find({
|
||||
page: 1,
|
||||
perPage: this.config.maxSessions,
|
||||
perPage: mgmtConfig.maxSessions,
|
||||
sortField: 'created',
|
||||
sortOrder: 'asc',
|
||||
})
|
||||
|
@ -149,6 +150,10 @@ export class SearchSessionsMgmtAPI {
|
|||
this.deps.application.navigateToUrl(reloadUrl);
|
||||
}
|
||||
|
||||
public getExtendByDuration() {
|
||||
return this.config.defaultExpiration;
|
||||
}
|
||||
|
||||
// Cancel and expire
|
||||
public async sendCancel(id: string): Promise<void> {
|
||||
try {
|
||||
|
|
|
@ -12,7 +12,7 @@ import moment from 'moment';
|
|||
import { ReactElement } from 'react';
|
||||
import { coreMock } from 'src/core/public/mocks';
|
||||
import { SessionsClient } from 'src/plugins/data/public/search';
|
||||
import { SessionsMgmtConfigSchema } from '../';
|
||||
import { SessionsConfigSchema } from '../';
|
||||
import { SearchSessionStatus } from '../../../../common/search';
|
||||
import { OnActionComplete } from '../components';
|
||||
import { UISession } from '../types';
|
||||
|
@ -22,7 +22,7 @@ import { getColumns } from './get_columns';
|
|||
|
||||
let mockCoreSetup: MockedKeys<CoreSetup>;
|
||||
let mockCoreStart: CoreStart;
|
||||
let mockConfig: SessionsMgmtConfigSchema;
|
||||
let mockConfig: SessionsConfigSchema;
|
||||
let api: SearchSessionsMgmtAPI;
|
||||
let sessionsClient: SessionsClient;
|
||||
let handleAction: OnActionComplete;
|
||||
|
@ -35,11 +35,14 @@ describe('Search Sessions Management table column factory', () => {
|
|||
mockCoreSetup = coreMock.createSetup();
|
||||
mockCoreStart = coreMock.createStart();
|
||||
mockConfig = {
|
||||
defaultExpiration: moment.duration('7d'),
|
||||
management: {
|
||||
expiresSoonWarning: moment.duration(1, 'days'),
|
||||
maxSessions: 2000,
|
||||
refreshInterval: moment.duration(1, 'seconds'),
|
||||
refreshTimeout: moment.duration(10, 'minutes'),
|
||||
};
|
||||
},
|
||||
} as any;
|
||||
sessionsClient = new SessionsClient({ http: mockCoreSetup.http });
|
||||
|
||||
api = new SearchSessionsMgmtAPI(sessionsClient, mockConfig, {
|
||||
|
|
|
@ -20,7 +20,7 @@ import { capitalize } from 'lodash';
|
|||
import React from 'react';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import { RedirectAppLinks } from '../../../../../../../src/plugins/kibana_react/public';
|
||||
import { SessionsMgmtConfigSchema } from '../';
|
||||
import { SessionsConfigSchema } from '../';
|
||||
import { SearchSessionStatus } from '../../../../common/search';
|
||||
import { TableText } from '../components';
|
||||
import { OnActionComplete, PopoverActionsMenu } from '../components';
|
||||
|
@ -45,7 +45,7 @@ function isSessionRestorable(status: SearchSessionStatus) {
|
|||
export const getColumns = (
|
||||
core: CoreStart,
|
||||
api: SearchSessionsMgmtAPI,
|
||||
config: SessionsMgmtConfigSchema,
|
||||
config: SessionsConfigSchema,
|
||||
timezone: string,
|
||||
onActionComplete: OnActionComplete
|
||||
): Array<EuiBasicTableColumn<UISession>> => {
|
||||
|
|
|
@ -6,9 +6,9 @@
|
|||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import moment from 'moment';
|
||||
import { SessionsMgmtConfigSchema } from '../';
|
||||
import { SessionsConfigSchema } from '../';
|
||||
|
||||
export const getExpirationStatus = (config: SessionsMgmtConfigSchema, expires: string | null) => {
|
||||
export const getExpirationStatus = (config: SessionsConfigSchema, expires: string | null) => {
|
||||
const tNow = moment.utc().valueOf();
|
||||
const tFuture = moment.utc(expires).valueOf();
|
||||
|
||||
|
@ -16,7 +16,7 @@ export const getExpirationStatus = (config: SessionsMgmtConfigSchema, expires: s
|
|||
// and the session was early expired when the browser refreshed the listing
|
||||
const durationToExpire = moment.duration(tFuture - tNow);
|
||||
const expiresInDays = Math.floor(durationToExpire.asDays());
|
||||
const sufficientDays = Math.ceil(moment.duration(config.expiresSoonWarning).asDays());
|
||||
const sufficientDays = Math.ceil(moment.duration(config.management.expiresSoonWarning).asDays());
|
||||
|
||||
let toolTipContent = i18n.translate('xpack.data.mgmt.searchSessions.status.expiresSoonInDays', {
|
||||
defaultMessage: 'Expires in {numDays} days',
|
||||
|
|
|
@ -76,7 +76,7 @@ export async function scheduleSearchSessionsTasks(
|
|||
params: {},
|
||||
});
|
||||
|
||||
logger.debug(`Background search task, scheduled to run`);
|
||||
logger.debug(`Search sessions task, scheduled to run`);
|
||||
} catch (e) {
|
||||
logger.debug(`Error scheduling task, received ${e.message}`);
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue