mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
Bump prettier/eslint packages related to Typescript after TS upgrade (#121119)
This commit is contained in:
parent
4af89667c2
commit
af960b61ff
47 changed files with 2168 additions and 2123 deletions
|
@ -665,9 +665,9 @@
|
|||
"@types/yargs": "^15.0.0",
|
||||
"@types/yauzl": "^2.9.1",
|
||||
"@types/zen-observable": "^0.8.0",
|
||||
"@typescript-eslint/eslint-plugin": "^5.2.0",
|
||||
"@typescript-eslint/parser": "^5.2.0",
|
||||
"@typescript-eslint/typescript-estree": "^5.2.0",
|
||||
"@typescript-eslint/eslint-plugin": "^5.6.0",
|
||||
"@typescript-eslint/parser": "^5.6.0",
|
||||
"@typescript-eslint/typescript-estree": "^5.6.0",
|
||||
"@yarnpkg/lockfile": "^1.1.0",
|
||||
"abab": "^2.0.4",
|
||||
"aggregate-error": "^3.1.0",
|
||||
|
@ -804,7 +804,7 @@
|
|||
"postcss": "^7.0.32",
|
||||
"postcss-loader": "^3.0.0",
|
||||
"postcss-prefix-selector": "^1.7.2",
|
||||
"prettier": "^2.4.0",
|
||||
"prettier": "^2.5.1",
|
||||
"pretty-ms": "5.0.0",
|
||||
"q": "^1.5.1",
|
||||
"react-test-renderer": "^16.12.0",
|
||||
|
|
|
@ -55,72 +55,74 @@ export const createIndex = ({
|
|||
mappings,
|
||||
aliases = [],
|
||||
}: CreateIndexParams): TaskEither.TaskEither<RetryableEsClientError, 'create_index_succeeded'> => {
|
||||
const createIndexTask: TaskEither.TaskEither<RetryableEsClientError, AcknowledgeResponse> =
|
||||
() => {
|
||||
const aliasesObject = aliasArrayToRecord(aliases);
|
||||
const createIndexTask: TaskEither.TaskEither<
|
||||
RetryableEsClientError,
|
||||
AcknowledgeResponse
|
||||
> = () => {
|
||||
const aliasesObject = aliasArrayToRecord(aliases);
|
||||
|
||||
return client.indices
|
||||
.create(
|
||||
{
|
||||
index: indexName,
|
||||
// wait until all shards are available before creating the index
|
||||
// (since number_of_shards=1 this does not have any effect atm)
|
||||
wait_for_active_shards: WAIT_FOR_ALL_SHARDS_TO_BE_ACTIVE,
|
||||
// Wait up to 60s for the cluster state to update and all shards to be
|
||||
// started
|
||||
timeout: DEFAULT_TIMEOUT,
|
||||
body: {
|
||||
mappings,
|
||||
aliases: aliasesObject,
|
||||
settings: {
|
||||
index: {
|
||||
// ES rule of thumb: shards should be several GB to 10's of GB, so
|
||||
// Kibana is unlikely to cross that limit.
|
||||
number_of_shards: 1,
|
||||
auto_expand_replicas: INDEX_AUTO_EXPAND_REPLICAS,
|
||||
// Set an explicit refresh interval so that we don't inherit the
|
||||
// value from incorrectly configured index templates (not required
|
||||
// after we adopt system indices)
|
||||
refresh_interval: '1s',
|
||||
// Bump priority so that recovery happens before newer indices
|
||||
priority: 10,
|
||||
},
|
||||
return client.indices
|
||||
.create(
|
||||
{
|
||||
index: indexName,
|
||||
// wait until all shards are available before creating the index
|
||||
// (since number_of_shards=1 this does not have any effect atm)
|
||||
wait_for_active_shards: WAIT_FOR_ALL_SHARDS_TO_BE_ACTIVE,
|
||||
// Wait up to 60s for the cluster state to update and all shards to be
|
||||
// started
|
||||
timeout: DEFAULT_TIMEOUT,
|
||||
body: {
|
||||
mappings,
|
||||
aliases: aliasesObject,
|
||||
settings: {
|
||||
index: {
|
||||
// ES rule of thumb: shards should be several GB to 10's of GB, so
|
||||
// Kibana is unlikely to cross that limit.
|
||||
number_of_shards: 1,
|
||||
auto_expand_replicas: INDEX_AUTO_EXPAND_REPLICAS,
|
||||
// Set an explicit refresh interval so that we don't inherit the
|
||||
// value from incorrectly configured index templates (not required
|
||||
// after we adopt system indices)
|
||||
refresh_interval: '1s',
|
||||
// Bump priority so that recovery happens before newer indices
|
||||
priority: 10,
|
||||
},
|
||||
},
|
||||
},
|
||||
{ maxRetries: 0 /** handle retry ourselves for now */ }
|
||||
)
|
||||
.then((res) => {
|
||||
},
|
||||
{ maxRetries: 0 /** handle retry ourselves for now */ }
|
||||
)
|
||||
.then((res) => {
|
||||
/**
|
||||
* - acknowledged=false, we timed out before the cluster state was
|
||||
* updated on all nodes with the newly created index, but it
|
||||
* probably will be created sometime soon.
|
||||
* - shards_acknowledged=false, we timed out before all shards were
|
||||
* started
|
||||
* - acknowledged=true, shards_acknowledged=true, index creation complete
|
||||
*/
|
||||
return Either.right({
|
||||
acknowledged: Boolean(res.body.acknowledged),
|
||||
shardsAcknowledged: res.body.shards_acknowledged,
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
if (error?.body?.error?.type === 'resource_already_exists_exception') {
|
||||
/**
|
||||
* - acknowledged=false, we timed out before the cluster state was
|
||||
* updated on all nodes with the newly created index, but it
|
||||
* probably will be created sometime soon.
|
||||
* - shards_acknowledged=false, we timed out before all shards were
|
||||
* started
|
||||
* - acknowledged=true, shards_acknowledged=true, index creation complete
|
||||
* If the target index already exists it means a previous create
|
||||
* operation had already been started. However, we can't be sure
|
||||
* that all shards were started so return shardsAcknowledged: false
|
||||
*/
|
||||
return Either.right({
|
||||
acknowledged: Boolean(res.body.acknowledged),
|
||||
shardsAcknowledged: res.body.shards_acknowledged,
|
||||
acknowledged: true,
|
||||
shardsAcknowledged: false,
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
if (error?.body?.error?.type === 'resource_already_exists_exception') {
|
||||
/**
|
||||
* If the target index already exists it means a previous create
|
||||
* operation had already been started. However, we can't be sure
|
||||
* that all shards were started so return shardsAcknowledged: false
|
||||
*/
|
||||
return Either.right({
|
||||
acknowledged: true,
|
||||
shardsAcknowledged: false,
|
||||
});
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
})
|
||||
.catch(catchRetryableEsClientErrors);
|
||||
};
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
})
|
||||
.catch(catchRetryableEsClientErrors);
|
||||
};
|
||||
|
||||
return pipe(
|
||||
createIndexTask,
|
||||
|
|
|
@ -41,33 +41,35 @@ export const updateAndPickupMappings = ({
|
|||
RetryableEsClientError,
|
||||
UpdateAndPickupMappingsResponse
|
||||
> => {
|
||||
const putMappingTask: TaskEither.TaskEither<RetryableEsClientError, 'update_mappings_succeeded'> =
|
||||
() => {
|
||||
return client.indices
|
||||
.putMapping({
|
||||
index,
|
||||
timeout: DEFAULT_TIMEOUT,
|
||||
body: mappings,
|
||||
})
|
||||
.then((res) => {
|
||||
// Ignore `acknowledged: false`. When the coordinating node accepts
|
||||
// the new cluster state update but not all nodes have applied the
|
||||
// update within the timeout `acknowledged` will be false. However,
|
||||
// retrying this update will always immediately result in `acknowledged:
|
||||
// true` even if there are still nodes which are falling behind with
|
||||
// cluster state updates.
|
||||
// For updateAndPickupMappings this means that there is the potential
|
||||
// that some existing document's fields won't be picked up if the node
|
||||
// on which the Kibana shard is running has fallen behind with cluster
|
||||
// state updates and the mapping update wasn't applied before we run
|
||||
// `pickupUpdatedMappings`. ES tries to limit this risk by blocking
|
||||
// index operations (including update_by_query used by
|
||||
// updateAndPickupMappings) if there are pending mappings changes. But
|
||||
// not all mapping changes will prevent this.
|
||||
return Either.right('update_mappings_succeeded' as const);
|
||||
})
|
||||
.catch(catchRetryableEsClientErrors);
|
||||
};
|
||||
const putMappingTask: TaskEither.TaskEither<
|
||||
RetryableEsClientError,
|
||||
'update_mappings_succeeded'
|
||||
> = () => {
|
||||
return client.indices
|
||||
.putMapping({
|
||||
index,
|
||||
timeout: DEFAULT_TIMEOUT,
|
||||
body: mappings,
|
||||
})
|
||||
.then((res) => {
|
||||
// Ignore `acknowledged: false`. When the coordinating node accepts
|
||||
// the new cluster state update but not all nodes have applied the
|
||||
// update within the timeout `acknowledged` will be false. However,
|
||||
// retrying this update will always immediately result in `acknowledged:
|
||||
// true` even if there are still nodes which are falling behind with
|
||||
// cluster state updates.
|
||||
// For updateAndPickupMappings this means that there is the potential
|
||||
// that some existing document's fields won't be picked up if the node
|
||||
// on which the Kibana shard is running has fallen behind with cluster
|
||||
// state updates and the mapping update wasn't applied before we run
|
||||
// `pickupUpdatedMappings`. ES tries to limit this risk by blocking
|
||||
// index operations (including update_by_query used by
|
||||
// updateAndPickupMappings) if there are pending mappings changes. But
|
||||
// not all mapping changes will prevent this.
|
||||
return Either.right('update_mappings_succeeded' as const);
|
||||
})
|
||||
.catch(catchRetryableEsClientErrors);
|
||||
};
|
||||
|
||||
return pipe(
|
||||
putMappingTask,
|
||||
|
|
|
@ -53,107 +53,108 @@ const setTabIndex = (items: Array<EuiSideNavItemType<{}>>, isHidden: boolean) =>
|
|||
/**
|
||||
* A wrapper around EuiSideNav but also creates the appropriate title with optional solution logo
|
||||
*/
|
||||
export const KibanaPageTemplateSolutionNav: FunctionComponent<KibanaPageTemplateSolutionNavProps> =
|
||||
({ name, icon, items, isOpenOnDesktop = false, onCollapse, ...rest }) => {
|
||||
// The EuiShowFor and EuiHideFor components are not in sync with the euiBreakpoint() function :(
|
||||
const isSmallerBreakpoint = useIsWithinBreakpoints(['xs', 's']);
|
||||
const isMediumBreakpoint = useIsWithinBreakpoints(['m']);
|
||||
const isLargerBreakpoint = useIsWithinBreakpoints(['l', 'xl']);
|
||||
export const KibanaPageTemplateSolutionNav: FunctionComponent<
|
||||
KibanaPageTemplateSolutionNavProps
|
||||
> = ({ name, icon, items, isOpenOnDesktop = false, onCollapse, ...rest }) => {
|
||||
// The EuiShowFor and EuiHideFor components are not in sync with the euiBreakpoint() function :(
|
||||
const isSmallerBreakpoint = useIsWithinBreakpoints(['xs', 's']);
|
||||
const isMediumBreakpoint = useIsWithinBreakpoints(['m']);
|
||||
const isLargerBreakpoint = useIsWithinBreakpoints(['l', 'xl']);
|
||||
|
||||
// This is used for both the EuiSideNav and EuiFlyout toggling
|
||||
const [isSideNavOpenOnMobile, setIsSideNavOpenOnMobile] = useState(false);
|
||||
const toggleOpenOnMobile = () => {
|
||||
setIsSideNavOpenOnMobile(!isSideNavOpenOnMobile);
|
||||
};
|
||||
// This is used for both the EuiSideNav and EuiFlyout toggling
|
||||
const [isSideNavOpenOnMobile, setIsSideNavOpenOnMobile] = useState(false);
|
||||
const toggleOpenOnMobile = () => {
|
||||
setIsSideNavOpenOnMobile(!isSideNavOpenOnMobile);
|
||||
};
|
||||
|
||||
const isHidden = isLargerBreakpoint && !isOpenOnDesktop;
|
||||
const isHidden = isLargerBreakpoint && !isOpenOnDesktop;
|
||||
|
||||
/**
|
||||
* Create the avatar
|
||||
*/
|
||||
let solutionAvatar;
|
||||
if (icon) {
|
||||
solutionAvatar = <KibanaPageTemplateSolutionNavAvatar iconType={icon} name={name} />;
|
||||
}
|
||||
/**
|
||||
* Create the avatar
|
||||
*/
|
||||
let solutionAvatar;
|
||||
if (icon) {
|
||||
solutionAvatar = <KibanaPageTemplateSolutionNavAvatar iconType={icon} name={name} />;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the titles
|
||||
*/
|
||||
const titleText = (
|
||||
<Fragment>
|
||||
{solutionAvatar}
|
||||
<strong>{name}</strong>
|
||||
</Fragment>
|
||||
);
|
||||
const mobileTitleText = (
|
||||
<FormattedMessage
|
||||
id="kibana-react.solutionNav.mobileTitleText"
|
||||
defaultMessage="{solutionName} Menu"
|
||||
values={{ solutionName: name || 'Navigation' }}
|
||||
/**
|
||||
* Create the titles
|
||||
*/
|
||||
const titleText = (
|
||||
<Fragment>
|
||||
{solutionAvatar}
|
||||
<strong>{name}</strong>
|
||||
</Fragment>
|
||||
);
|
||||
const mobileTitleText = (
|
||||
<FormattedMessage
|
||||
id="kibana-react.solutionNav.mobileTitleText"
|
||||
defaultMessage="{solutionName} Menu"
|
||||
values={{ solutionName: name || 'Navigation' }}
|
||||
/>
|
||||
);
|
||||
|
||||
/**
|
||||
* Create the side nav component
|
||||
*/
|
||||
let sideNav;
|
||||
if (items) {
|
||||
const sideNavClasses = classNames('kbnPageTemplateSolutionNav', {
|
||||
'kbnPageTemplateSolutionNav--hidden': isHidden,
|
||||
});
|
||||
|
||||
sideNav = (
|
||||
<EuiSideNav
|
||||
aria-hidden={isHidden}
|
||||
className={sideNavClasses}
|
||||
heading={titleText}
|
||||
mobileTitle={
|
||||
<Fragment>
|
||||
{solutionAvatar}
|
||||
{mobileTitleText}
|
||||
</Fragment>
|
||||
}
|
||||
toggleOpenOnMobile={toggleOpenOnMobile}
|
||||
isOpenOnMobile={isSideNavOpenOnMobile}
|
||||
items={setTabIndex(items, isHidden)}
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the side nav component
|
||||
*/
|
||||
let sideNav;
|
||||
if (items) {
|
||||
const sideNavClasses = classNames('kbnPageTemplateSolutionNav', {
|
||||
'kbnPageTemplateSolutionNav--hidden': isHidden,
|
||||
});
|
||||
|
||||
sideNav = (
|
||||
<EuiSideNav
|
||||
aria-hidden={isHidden}
|
||||
className={sideNavClasses}
|
||||
heading={titleText}
|
||||
mobileTitle={
|
||||
<Fragment>
|
||||
{solutionAvatar}
|
||||
{mobileTitleText}
|
||||
</Fragment>
|
||||
}
|
||||
toggleOpenOnMobile={toggleOpenOnMobile}
|
||||
isOpenOnMobile={isSideNavOpenOnMobile}
|
||||
items={setTabIndex(items, isHidden)}
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
{isSmallerBreakpoint && sideNav}
|
||||
{isMediumBreakpoint && (
|
||||
<Fragment>
|
||||
{isSideNavOpenOnMobile && (
|
||||
<EuiFlyout
|
||||
ownFocus={false}
|
||||
outsideClickCloses
|
||||
onClose={() => setIsSideNavOpenOnMobile(false)}
|
||||
side="left"
|
||||
size={248}
|
||||
closeButtonPosition="outside"
|
||||
className="kbnPageTemplateSolutionNav__flyout"
|
||||
>
|
||||
{sideNav}
|
||||
</EuiFlyout>
|
||||
)}
|
||||
<KibanaPageTemplateSolutionNavCollapseButton
|
||||
isCollapsed={true}
|
||||
onClick={toggleOpenOnMobile}
|
||||
/>
|
||||
</Fragment>
|
||||
)}
|
||||
{isLargerBreakpoint && (
|
||||
<Fragment>
|
||||
{sideNav}
|
||||
<KibanaPageTemplateSolutionNavCollapseButton
|
||||
isCollapsed={!isOpenOnDesktop}
|
||||
onClick={onCollapse}
|
||||
/>
|
||||
</Fragment>
|
||||
)}
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
return (
|
||||
<Fragment>
|
||||
{isSmallerBreakpoint && sideNav}
|
||||
{isMediumBreakpoint && (
|
||||
<Fragment>
|
||||
{isSideNavOpenOnMobile && (
|
||||
<EuiFlyout
|
||||
ownFocus={false}
|
||||
outsideClickCloses
|
||||
onClose={() => setIsSideNavOpenOnMobile(false)}
|
||||
side="left"
|
||||
size={248}
|
||||
closeButtonPosition="outside"
|
||||
className="kbnPageTemplateSolutionNav__flyout"
|
||||
>
|
||||
{sideNav}
|
||||
</EuiFlyout>
|
||||
)}
|
||||
<KibanaPageTemplateSolutionNavCollapseButton
|
||||
isCollapsed={true}
|
||||
onClick={toggleOpenOnMobile}
|
||||
/>
|
||||
</Fragment>
|
||||
)}
|
||||
{isLargerBreakpoint && (
|
||||
<Fragment>
|
||||
{sideNav}
|
||||
<KibanaPageTemplateSolutionNavCollapseButton
|
||||
isCollapsed={!isOpenOnDesktop}
|
||||
onClick={onCollapse}
|
||||
/>
|
||||
</Fragment>
|
||||
)}
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -22,22 +22,23 @@ export type KibanaPageTemplateSolutionNavAvatarProps = DistributiveOmit<EuiAvata
|
|||
/**
|
||||
* Applies extra styling to a typical EuiAvatar
|
||||
*/
|
||||
export const KibanaPageTemplateSolutionNavAvatar: FunctionComponent<KibanaPageTemplateSolutionNavAvatarProps> =
|
||||
({ className, size, ...rest }) => {
|
||||
return (
|
||||
// @ts-ignore Complains about ExclusiveUnion between `iconSize` and `iconType`, but works fine
|
||||
<EuiAvatar
|
||||
className={classNames(
|
||||
'kbnPageTemplateSolutionNavAvatar',
|
||||
{
|
||||
[`kbnPageTemplateSolutionNavAvatar--${size}`]: size,
|
||||
},
|
||||
className
|
||||
)}
|
||||
color="plain"
|
||||
size={size === 'xxl' ? 'xl' : size}
|
||||
iconSize={size}
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
};
|
||||
export const KibanaPageTemplateSolutionNavAvatar: FunctionComponent<
|
||||
KibanaPageTemplateSolutionNavAvatarProps
|
||||
> = ({ className, size, ...rest }) => {
|
||||
return (
|
||||
// @ts-ignore Complains about ExclusiveUnion between `iconSize` and `iconType`, but works fine
|
||||
<EuiAvatar
|
||||
className={classNames(
|
||||
'kbnPageTemplateSolutionNavAvatar',
|
||||
{
|
||||
[`kbnPageTemplateSolutionNavAvatar--${size}`]: size,
|
||||
},
|
||||
className
|
||||
)}
|
||||
color="plain"
|
||||
size={size === 'xxl' ? 'xl' : size}
|
||||
iconSize={size}
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -24,33 +24,34 @@ export type KibanaPageTemplateSolutionNavCollapseButtonProps =
|
|||
/**
|
||||
* Creates the styled icon button for showing/hiding solution nav
|
||||
*/
|
||||
export const KibanaPageTemplateSolutionNavCollapseButton: FunctionComponent<KibanaPageTemplateSolutionNavCollapseButtonProps> =
|
||||
({ className, isCollapsed, ...rest }) => {
|
||||
const classes = classNames(
|
||||
'kbnPageTemplateSolutionNavCollapseButton',
|
||||
{
|
||||
'kbnPageTemplateSolutionNavCollapseButton-isCollapsed': isCollapsed,
|
||||
},
|
||||
className
|
||||
);
|
||||
export const KibanaPageTemplateSolutionNavCollapseButton: FunctionComponent<
|
||||
KibanaPageTemplateSolutionNavCollapseButtonProps
|
||||
> = ({ className, isCollapsed, ...rest }) => {
|
||||
const classes = classNames(
|
||||
'kbnPageTemplateSolutionNavCollapseButton',
|
||||
{
|
||||
'kbnPageTemplateSolutionNavCollapseButton-isCollapsed': isCollapsed,
|
||||
},
|
||||
className
|
||||
);
|
||||
|
||||
const collapseLabel = i18n.translate('kibana-react.solutionNav.collapsibleLabel', {
|
||||
defaultMessage: 'Collapse side navigation',
|
||||
});
|
||||
const collapseLabel = i18n.translate('kibana-react.solutionNav.collapsibleLabel', {
|
||||
defaultMessage: 'Collapse side navigation',
|
||||
});
|
||||
|
||||
const openLabel = i18n.translate('kibana-react.solutionNav.openLabel', {
|
||||
defaultMessage: 'Open side navigation',
|
||||
});
|
||||
const openLabel = i18n.translate('kibana-react.solutionNav.openLabel', {
|
||||
defaultMessage: 'Open side navigation',
|
||||
});
|
||||
|
||||
return (
|
||||
<EuiButtonIcon
|
||||
className={classes}
|
||||
size="s"
|
||||
color="text"
|
||||
iconType={isCollapsed ? 'menuRight' : 'menuLeft'}
|
||||
aria-label={isCollapsed ? openLabel : collapseLabel}
|
||||
title={isCollapsed ? openLabel : collapseLabel}
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
};
|
||||
return (
|
||||
<EuiButtonIcon
|
||||
className={classes}
|
||||
size="s"
|
||||
color="text"
|
||||
iconType={isCollapsed ? 'menuRight' : 'menuLeft'}
|
||||
aria-label={isCollapsed ? openLabel : collapseLabel}
|
||||
title={isCollapsed ? openLabel : collapseLabel}
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -13,11 +13,12 @@ import { decorateObject } from './decorate_object';
|
|||
|
||||
export const decoratorId = 'tag';
|
||||
|
||||
export const tagDecoratorFactory: SavedObjectDecoratorFactory<InternalTagDecoratedSavedObject> =
|
||||
() => {
|
||||
return {
|
||||
getId: () => decoratorId,
|
||||
decorateConfig,
|
||||
decorateObject,
|
||||
};
|
||||
export const tagDecoratorFactory: SavedObjectDecoratorFactory<
|
||||
InternalTagDecoratedSavedObject
|
||||
> = () => {
|
||||
return {
|
||||
getId: () => decoratorId,
|
||||
decorateConfig,
|
||||
decorateObject,
|
||||
};
|
||||
};
|
||||
|
|
|
@ -13,5 +13,7 @@ import {
|
|||
IndexPatternsTestPluginStart,
|
||||
} from './plugin';
|
||||
|
||||
export const plugin: PluginInitializer<IndexPatternsTestPluginSetup, IndexPatternsTestPluginStart> =
|
||||
() => new IndexPatternsTestPlugin();
|
||||
export const plugin: PluginInitializer<
|
||||
IndexPatternsTestPluginSetup,
|
||||
IndexPatternsTestPluginStart
|
||||
> = () => new IndexPatternsTestPlugin();
|
||||
|
|
|
@ -18,29 +18,30 @@ interface FunctionFormContextPendingProps {
|
|||
updateContext: (element?: CanvasElement) => void;
|
||||
}
|
||||
|
||||
export const FunctionFormContextPending: React.FunctionComponent<FunctionFormContextPendingProps> =
|
||||
(props) => {
|
||||
const { contextExpression, expressionType, context, updateContext } = props;
|
||||
const prevContextExpression = usePrevious(contextExpression);
|
||||
const fetchContext = useCallback(
|
||||
(force = false) => {
|
||||
// dispatch context update if none is provided
|
||||
if (force || (context == null && expressionType.requiresContext)) {
|
||||
updateContext();
|
||||
}
|
||||
},
|
||||
[context, expressionType.requiresContext, updateContext]
|
||||
);
|
||||
export const FunctionFormContextPending: React.FunctionComponent<
|
||||
FunctionFormContextPendingProps
|
||||
> = (props) => {
|
||||
const { contextExpression, expressionType, context, updateContext } = props;
|
||||
const prevContextExpression = usePrevious(contextExpression);
|
||||
const fetchContext = useCallback(
|
||||
(force = false) => {
|
||||
// dispatch context update if none is provided
|
||||
if (force || (context == null && expressionType.requiresContext)) {
|
||||
updateContext();
|
||||
}
|
||||
},
|
||||
[context, expressionType.requiresContext, updateContext]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const forceUpdate =
|
||||
expressionType.requiresContext && prevContextExpression !== contextExpression;
|
||||
fetchContext(forceUpdate);
|
||||
}, [contextExpression, expressionType, fetchContext, prevContextExpression]);
|
||||
useEffect(() => {
|
||||
const forceUpdate =
|
||||
expressionType.requiresContext && prevContextExpression !== contextExpression;
|
||||
fetchContext(forceUpdate);
|
||||
}, [contextExpression, expressionType, fetchContext, prevContextExpression]);
|
||||
|
||||
return (
|
||||
<div className="canvasFunctionForm canvasFunctionForm--loading">
|
||||
<Loading />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
return (
|
||||
<div className="canvasFunctionForm canvasFunctionForm--loading">
|
||||
<Loading />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -24,163 +24,164 @@ import * as i18n from './translations';
|
|||
import { ConnectorTypes, ResilientFieldsType } from '../../../../common/api';
|
||||
import { ConnectorCard } from '../card';
|
||||
|
||||
const ResilientFieldsComponent: React.FunctionComponent<ConnectorFieldsProps<ResilientFieldsType>> =
|
||||
({ isEdit = true, fields, connector, onChange }) => {
|
||||
const init = useRef(true);
|
||||
const { incidentTypes = null, severityCode = null } = fields ?? {};
|
||||
const ResilientFieldsComponent: React.FunctionComponent<
|
||||
ConnectorFieldsProps<ResilientFieldsType>
|
||||
> = ({ isEdit = true, fields, connector, onChange }) => {
|
||||
const init = useRef(true);
|
||||
const { incidentTypes = null, severityCode = null } = fields ?? {};
|
||||
|
||||
const { http, notifications } = useKibana().services;
|
||||
const { http, notifications } = useKibana().services;
|
||||
|
||||
const { isLoading: isLoadingIncidentTypes, incidentTypes: allIncidentTypes } =
|
||||
useGetIncidentTypes({
|
||||
http,
|
||||
toastNotifications: notifications.toasts,
|
||||
connector,
|
||||
});
|
||||
|
||||
const { isLoading: isLoadingSeverity, severity } = useGetSeverity({
|
||||
const { isLoading: isLoadingIncidentTypes, incidentTypes: allIncidentTypes } =
|
||||
useGetIncidentTypes({
|
||||
http,
|
||||
toastNotifications: notifications.toasts,
|
||||
connector,
|
||||
});
|
||||
|
||||
const severitySelectOptions: EuiSelectOption[] = useMemo(
|
||||
() =>
|
||||
severity.map((s) => ({
|
||||
value: s.id.toString(),
|
||||
text: s.name,
|
||||
})),
|
||||
[severity]
|
||||
);
|
||||
const { isLoading: isLoadingSeverity, severity } = useGetSeverity({
|
||||
http,
|
||||
toastNotifications: notifications.toasts,
|
||||
connector,
|
||||
});
|
||||
|
||||
const incidentTypesComboBoxOptions: Array<EuiComboBoxOptionOption<string>> = useMemo(
|
||||
() =>
|
||||
allIncidentTypes
|
||||
? allIncidentTypes.map((type: { id: number; name: string }) => ({
|
||||
label: type.name,
|
||||
value: type.id.toString(),
|
||||
}))
|
||||
: [],
|
||||
[allIncidentTypes]
|
||||
);
|
||||
const listItems = useMemo(
|
||||
() => [
|
||||
...(incidentTypes != null && incidentTypes.length > 0
|
||||
? [
|
||||
{
|
||||
title: i18n.INCIDENT_TYPES_LABEL,
|
||||
description: allIncidentTypes
|
||||
.filter((type) => incidentTypes.includes(type.id.toString()))
|
||||
.map((type) => type.name)
|
||||
.join(', '),
|
||||
},
|
||||
]
|
||||
: []),
|
||||
...(severityCode != null && severityCode.length > 0
|
||||
? [
|
||||
{
|
||||
title: i18n.SEVERITY_LABEL,
|
||||
description:
|
||||
severity.find((severityObj) => severityObj.id.toString() === severityCode)
|
||||
?.name ?? '',
|
||||
},
|
||||
]
|
||||
: []),
|
||||
],
|
||||
[incidentTypes, severityCode, allIncidentTypes, severity]
|
||||
);
|
||||
const severitySelectOptions: EuiSelectOption[] = useMemo(
|
||||
() =>
|
||||
severity.map((s) => ({
|
||||
value: s.id.toString(),
|
||||
text: s.name,
|
||||
})),
|
||||
[severity]
|
||||
);
|
||||
|
||||
const onFieldChange = useCallback(
|
||||
(key, value) => {
|
||||
onChange({
|
||||
...fields,
|
||||
incidentTypes,
|
||||
severityCode,
|
||||
[key]: value,
|
||||
});
|
||||
},
|
||||
[incidentTypes, severityCode, onChange, fields]
|
||||
);
|
||||
const incidentTypesComboBoxOptions: Array<EuiComboBoxOptionOption<string>> = useMemo(
|
||||
() =>
|
||||
allIncidentTypes
|
||||
? allIncidentTypes.map((type: { id: number; name: string }) => ({
|
||||
label: type.name,
|
||||
value: type.id.toString(),
|
||||
}))
|
||||
: [],
|
||||
[allIncidentTypes]
|
||||
);
|
||||
const listItems = useMemo(
|
||||
() => [
|
||||
...(incidentTypes != null && incidentTypes.length > 0
|
||||
? [
|
||||
{
|
||||
title: i18n.INCIDENT_TYPES_LABEL,
|
||||
description: allIncidentTypes
|
||||
.filter((type) => incidentTypes.includes(type.id.toString()))
|
||||
.map((type) => type.name)
|
||||
.join(', '),
|
||||
},
|
||||
]
|
||||
: []),
|
||||
...(severityCode != null && severityCode.length > 0
|
||||
? [
|
||||
{
|
||||
title: i18n.SEVERITY_LABEL,
|
||||
description:
|
||||
severity.find((severityObj) => severityObj.id.toString() === severityCode)?.name ??
|
||||
'',
|
||||
},
|
||||
]
|
||||
: []),
|
||||
],
|
||||
[incidentTypes, severityCode, allIncidentTypes, severity]
|
||||
);
|
||||
|
||||
const selectedIncidentTypesComboBoxOptionsMemo = useMemo(() => {
|
||||
const allIncidentTypesAsObject = allIncidentTypes.reduce(
|
||||
(acc, type) => ({ ...acc, [type.id.toString()]: type.name }),
|
||||
{} as Record<string, string>
|
||||
const onFieldChange = useCallback(
|
||||
(key, value) => {
|
||||
onChange({
|
||||
...fields,
|
||||
incidentTypes,
|
||||
severityCode,
|
||||
[key]: value,
|
||||
});
|
||||
},
|
||||
[incidentTypes, severityCode, onChange, fields]
|
||||
);
|
||||
|
||||
const selectedIncidentTypesComboBoxOptionsMemo = useMemo(() => {
|
||||
const allIncidentTypesAsObject = allIncidentTypes.reduce(
|
||||
(acc, type) => ({ ...acc, [type.id.toString()]: type.name }),
|
||||
{} as Record<string, string>
|
||||
);
|
||||
return incidentTypes
|
||||
? incidentTypes
|
||||
.map((type) => ({
|
||||
label: allIncidentTypesAsObject[type.toString()],
|
||||
value: type.toString(),
|
||||
}))
|
||||
.filter((type) => type.label != null)
|
||||
: [];
|
||||
}, [allIncidentTypes, incidentTypes]);
|
||||
|
||||
const onIncidentChange = useCallback(
|
||||
(selectedOptions: Array<{ label: string; value?: string }>) => {
|
||||
onFieldChange(
|
||||
'incidentTypes',
|
||||
selectedOptions.map((selectedOption) => selectedOption.value ?? selectedOption.label)
|
||||
);
|
||||
return incidentTypes
|
||||
? incidentTypes
|
||||
.map((type) => ({
|
||||
label: allIncidentTypesAsObject[type.toString()],
|
||||
value: type.toString(),
|
||||
}))
|
||||
.filter((type) => type.label != null)
|
||||
: [];
|
||||
}, [allIncidentTypes, incidentTypes]);
|
||||
},
|
||||
[onFieldChange]
|
||||
);
|
||||
|
||||
const onIncidentChange = useCallback(
|
||||
(selectedOptions: Array<{ label: string; value?: string }>) => {
|
||||
onFieldChange(
|
||||
'incidentTypes',
|
||||
selectedOptions.map((selectedOption) => selectedOption.value ?? selectedOption.label)
|
||||
);
|
||||
},
|
||||
[onFieldChange]
|
||||
);
|
||||
const onIncidentBlur = useCallback(() => {
|
||||
if (!incidentTypes) {
|
||||
onFieldChange('incidentTypes', []);
|
||||
}
|
||||
}, [incidentTypes, onFieldChange]);
|
||||
|
||||
const onIncidentBlur = useCallback(() => {
|
||||
if (!incidentTypes) {
|
||||
onFieldChange('incidentTypes', []);
|
||||
}
|
||||
}, [incidentTypes, onFieldChange]);
|
||||
// Set field at initialization
|
||||
useEffect(() => {
|
||||
if (init.current) {
|
||||
init.current = false;
|
||||
onChange({ incidentTypes, severityCode });
|
||||
}
|
||||
}, [incidentTypes, onChange, severityCode]);
|
||||
|
||||
// Set field at initialization
|
||||
useEffect(() => {
|
||||
if (init.current) {
|
||||
init.current = false;
|
||||
onChange({ incidentTypes, severityCode });
|
||||
}
|
||||
}, [incidentTypes, onChange, severityCode]);
|
||||
|
||||
return isEdit ? (
|
||||
<span data-test-subj={'connector-fields-resilient'}>
|
||||
<EuiFormRow fullWidth label={i18n.INCIDENT_TYPES_LABEL}>
|
||||
<EuiComboBox
|
||||
data-test-subj="incidentTypeComboBox"
|
||||
fullWidth
|
||||
isClearable={true}
|
||||
isDisabled={isLoadingIncidentTypes}
|
||||
isLoading={isLoadingIncidentTypes}
|
||||
onBlur={onIncidentBlur}
|
||||
onChange={onIncidentChange}
|
||||
options={incidentTypesComboBoxOptions}
|
||||
placeholder={i18n.INCIDENT_TYPES_PLACEHOLDER}
|
||||
selectedOptions={selectedIncidentTypesComboBoxOptionsMemo}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
<EuiSpacer size="m" />
|
||||
<EuiFormRow fullWidth label={i18n.SEVERITY_LABEL}>
|
||||
<EuiSelect
|
||||
data-test-subj="severitySelect"
|
||||
disabled={isLoadingSeverity}
|
||||
fullWidth
|
||||
hasNoInitialSelection
|
||||
isLoading={isLoadingSeverity}
|
||||
onChange={(e) => onFieldChange('severityCode', e.target.value)}
|
||||
options={severitySelectOptions}
|
||||
value={severityCode ?? undefined}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
<EuiSpacer size="m" />
|
||||
</span>
|
||||
) : (
|
||||
<ConnectorCard
|
||||
connectorType={ConnectorTypes.resilient}
|
||||
isLoading={isLoadingIncidentTypes || isLoadingSeverity}
|
||||
listItems={listItems}
|
||||
title={connector.name}
|
||||
/>
|
||||
);
|
||||
};
|
||||
return isEdit ? (
|
||||
<span data-test-subj={'connector-fields-resilient'}>
|
||||
<EuiFormRow fullWidth label={i18n.INCIDENT_TYPES_LABEL}>
|
||||
<EuiComboBox
|
||||
data-test-subj="incidentTypeComboBox"
|
||||
fullWidth
|
||||
isClearable={true}
|
||||
isDisabled={isLoadingIncidentTypes}
|
||||
isLoading={isLoadingIncidentTypes}
|
||||
onBlur={onIncidentBlur}
|
||||
onChange={onIncidentChange}
|
||||
options={incidentTypesComboBoxOptions}
|
||||
placeholder={i18n.INCIDENT_TYPES_PLACEHOLDER}
|
||||
selectedOptions={selectedIncidentTypesComboBoxOptionsMemo}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
<EuiSpacer size="m" />
|
||||
<EuiFormRow fullWidth label={i18n.SEVERITY_LABEL}>
|
||||
<EuiSelect
|
||||
data-test-subj="severitySelect"
|
||||
disabled={isLoadingSeverity}
|
||||
fullWidth
|
||||
hasNoInitialSelection
|
||||
isLoading={isLoadingSeverity}
|
||||
onChange={(e) => onFieldChange('severityCode', e.target.value)}
|
||||
options={severitySelectOptions}
|
||||
value={severityCode ?? undefined}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
<EuiSpacer size="m" />
|
||||
</span>
|
||||
) : (
|
||||
<ConnectorCard
|
||||
connectorType={ConnectorTypes.resilient}
|
||||
isLoading={isLoadingIncidentTypes || isLoadingSeverity}
|
||||
listItems={listItems}
|
||||
title={connector.name}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export { ResilientFieldsComponent as default };
|
||||
|
|
|
@ -25,8 +25,9 @@ export interface CasesNavigation<T = React.MouseEvent | MouseEvent | null, K = n
|
|||
: (arg: T) => Promise<void> | void;
|
||||
}
|
||||
|
||||
export const LinkButton: React.FC<PropsForButton<EuiButtonProps> | PropsForAnchor<EuiButtonProps>> =
|
||||
({ children, ...props }) => <EuiButton {...props}>{children}</EuiButton>;
|
||||
export const LinkButton: React.FC<
|
||||
PropsForButton<EuiButtonProps> | PropsForAnchor<EuiButtonProps>
|
||||
> = ({ children, ...props }) => <EuiButton {...props}>{children}</EuiButton>;
|
||||
|
||||
export const LinkAnchor: React.FC<EuiLinkProps> = ({ children, ...props }) => (
|
||||
<EuiLink {...props}>{children}</EuiLink>
|
||||
|
|
|
@ -38,143 +38,144 @@ export interface DataVisualizerUrlStateContextProviderProps {
|
|||
additionalLinks: ResultLink[];
|
||||
}
|
||||
|
||||
export const DataVisualizerUrlStateContextProvider: FC<DataVisualizerUrlStateContextProviderProps> =
|
||||
({ IndexDataVisualizerComponent, additionalLinks }) => {
|
||||
const {
|
||||
services: {
|
||||
data: { indexPatterns },
|
||||
savedObjects: { client: savedObjectsClient },
|
||||
notifications: { toasts },
|
||||
},
|
||||
} = useDataVisualizerKibana();
|
||||
const history = useHistory();
|
||||
const { search: searchString } = useLocation();
|
||||
export const DataVisualizerUrlStateContextProvider: FC<
|
||||
DataVisualizerUrlStateContextProviderProps
|
||||
> = ({ IndexDataVisualizerComponent, additionalLinks }) => {
|
||||
const {
|
||||
services: {
|
||||
data: { indexPatterns },
|
||||
savedObjects: { client: savedObjectsClient },
|
||||
notifications: { toasts },
|
||||
},
|
||||
} = useDataVisualizerKibana();
|
||||
const history = useHistory();
|
||||
const { search: searchString } = useLocation();
|
||||
|
||||
const [currentIndexPattern, setCurrentIndexPattern] = useState<DataView | undefined>(undefined);
|
||||
const [currentSavedSearch, setCurrentSavedSearch] = useState<SimpleSavedObject<unknown> | null>(
|
||||
null
|
||||
);
|
||||
const [currentIndexPattern, setCurrentIndexPattern] = useState<DataView | undefined>(undefined);
|
||||
const [currentSavedSearch, setCurrentSavedSearch] = useState<SimpleSavedObject<unknown> | null>(
|
||||
null
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
useEffect(() => {
|
||||
const prevSearchString = searchString;
|
||||
const parsedQueryString = parse(prevSearchString, { sort: false });
|
||||
|
||||
const getIndexPattern = async () => {
|
||||
if (typeof parsedQueryString?.savedSearchId === 'string') {
|
||||
const savedSearchId = parsedQueryString.savedSearchId;
|
||||
try {
|
||||
const savedSearch = await savedObjectsClient.get('search', savedSearchId);
|
||||
const indexPatternId = savedSearch.references.find(
|
||||
(ref) => ref.type === 'index-pattern'
|
||||
)?.id;
|
||||
if (indexPatternId !== undefined && savedSearch) {
|
||||
try {
|
||||
const indexPattern = await indexPatterns.get(indexPatternId);
|
||||
setCurrentSavedSearch(savedSearch);
|
||||
setCurrentIndexPattern(indexPattern);
|
||||
} catch (e) {
|
||||
toasts.addError(e, {
|
||||
title: i18n.translate('xpack.dataVisualizer.index.dataViewErrorMessage', {
|
||||
defaultMessage: 'Error finding data view',
|
||||
}),
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
toasts.addError(e, {
|
||||
title: i18n.translate('xpack.dataVisualizer.index.savedSearchErrorMessage', {
|
||||
defaultMessage: 'Error retrieving saved search {savedSearchId}',
|
||||
values: { savedSearchId },
|
||||
}),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof parsedQueryString?.index === 'string') {
|
||||
const indexPattern = await indexPatterns.get(parsedQueryString.index);
|
||||
setCurrentIndexPattern(indexPattern);
|
||||
}
|
||||
};
|
||||
getIndexPattern();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [savedObjectsClient, toasts, indexPatterns]);
|
||||
|
||||
const setUrlState: SetUrlState = useCallback(
|
||||
(
|
||||
accessor: Accessor,
|
||||
attribute: string | Dictionary<any>,
|
||||
value?: any,
|
||||
replaceState?: boolean
|
||||
) => {
|
||||
const prevSearchString = searchString;
|
||||
const urlState = parseUrlState(prevSearchString);
|
||||
const parsedQueryString = parse(prevSearchString, { sort: false });
|
||||
|
||||
const getIndexPattern = async () => {
|
||||
if (typeof parsedQueryString?.savedSearchId === 'string') {
|
||||
const savedSearchId = parsedQueryString.savedSearchId;
|
||||
try {
|
||||
const savedSearch = await savedObjectsClient.get('search', savedSearchId);
|
||||
const indexPatternId = savedSearch.references.find(
|
||||
(ref) => ref.type === 'index-pattern'
|
||||
)?.id;
|
||||
if (indexPatternId !== undefined && savedSearch) {
|
||||
try {
|
||||
const indexPattern = await indexPatterns.get(indexPatternId);
|
||||
setCurrentSavedSearch(savedSearch);
|
||||
setCurrentIndexPattern(indexPattern);
|
||||
} catch (e) {
|
||||
toasts.addError(e, {
|
||||
title: i18n.translate('xpack.dataVisualizer.index.dataViewErrorMessage', {
|
||||
defaultMessage: 'Error finding data view',
|
||||
}),
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
toasts.addError(e, {
|
||||
title: i18n.translate('xpack.dataVisualizer.index.savedSearchErrorMessage', {
|
||||
defaultMessage: 'Error retrieving saved search {savedSearchId}',
|
||||
values: { savedSearchId },
|
||||
}),
|
||||
});
|
||||
if (!Object.prototype.hasOwnProperty.call(urlState, accessor)) {
|
||||
urlState[accessor] = {};
|
||||
}
|
||||
|
||||
if (typeof attribute === 'string') {
|
||||
if (isEqual(getNestedProperty(urlState, `${accessor}.${attribute}`), value)) {
|
||||
return prevSearchString;
|
||||
}
|
||||
|
||||
urlState[accessor][attribute] = value;
|
||||
} else {
|
||||
const attributes = attribute;
|
||||
Object.keys(attributes).forEach((a) => {
|
||||
urlState[accessor][a] = attributes[a];
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
const oldLocationSearchString = stringify(parsedQueryString, {
|
||||
sort: false,
|
||||
encode: false,
|
||||
});
|
||||
|
||||
Object.keys(urlState).forEach((a) => {
|
||||
if (isRisonSerializationRequired(a)) {
|
||||
parsedQueryString[a] = encode(urlState[a]);
|
||||
} else {
|
||||
parsedQueryString[a] = urlState[a];
|
||||
}
|
||||
});
|
||||
const newLocationSearchString = stringify(parsedQueryString, {
|
||||
sort: false,
|
||||
encode: false,
|
||||
});
|
||||
|
||||
if (oldLocationSearchString !== newLocationSearchString) {
|
||||
const newSearchString = stringify(parsedQueryString, { sort: false });
|
||||
if (replaceState) {
|
||||
history.replace({ search: newSearchString });
|
||||
} else {
|
||||
history.push({ search: newSearchString });
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error('Could not save url state', error);
|
||||
}
|
||||
},
|
||||
[history, searchString]
|
||||
);
|
||||
|
||||
if (typeof parsedQueryString?.index === 'string') {
|
||||
const indexPattern = await indexPatterns.get(parsedQueryString.index);
|
||||
setCurrentIndexPattern(indexPattern);
|
||||
}
|
||||
};
|
||||
getIndexPattern();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [savedObjectsClient, toasts, indexPatterns]);
|
||||
|
||||
const setUrlState: SetUrlState = useCallback(
|
||||
(
|
||||
accessor: Accessor,
|
||||
attribute: string | Dictionary<any>,
|
||||
value?: any,
|
||||
replaceState?: boolean
|
||||
) => {
|
||||
const prevSearchString = searchString;
|
||||
const urlState = parseUrlState(prevSearchString);
|
||||
const parsedQueryString = parse(prevSearchString, { sort: false });
|
||||
|
||||
if (!Object.prototype.hasOwnProperty.call(urlState, accessor)) {
|
||||
urlState[accessor] = {};
|
||||
}
|
||||
|
||||
if (typeof attribute === 'string') {
|
||||
if (isEqual(getNestedProperty(urlState, `${accessor}.${attribute}`), value)) {
|
||||
return prevSearchString;
|
||||
}
|
||||
|
||||
urlState[accessor][attribute] = value;
|
||||
} else {
|
||||
const attributes = attribute;
|
||||
Object.keys(attributes).forEach((a) => {
|
||||
urlState[accessor][a] = attributes[a];
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
const oldLocationSearchString = stringify(parsedQueryString, {
|
||||
sort: false,
|
||||
encode: false,
|
||||
});
|
||||
|
||||
Object.keys(urlState).forEach((a) => {
|
||||
if (isRisonSerializationRequired(a)) {
|
||||
parsedQueryString[a] = encode(urlState[a]);
|
||||
} else {
|
||||
parsedQueryString[a] = urlState[a];
|
||||
}
|
||||
});
|
||||
const newLocationSearchString = stringify(parsedQueryString, {
|
||||
sort: false,
|
||||
encode: false,
|
||||
});
|
||||
|
||||
if (oldLocationSearchString !== newLocationSearchString) {
|
||||
const newSearchString = stringify(parsedQueryString, { sort: false });
|
||||
if (replaceState) {
|
||||
history.replace({ search: newSearchString });
|
||||
} else {
|
||||
history.push({ search: newSearchString });
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error('Could not save url state', error);
|
||||
}
|
||||
},
|
||||
[history, searchString]
|
||||
);
|
||||
|
||||
return (
|
||||
<UrlStateContextProvider value={{ searchString, setUrlState }}>
|
||||
{currentIndexPattern ? (
|
||||
<IndexDataVisualizerComponent
|
||||
currentIndexPattern={currentIndexPattern}
|
||||
currentSavedSearch={currentSavedSearch}
|
||||
additionalLinks={additionalLinks}
|
||||
/>
|
||||
) : (
|
||||
<div />
|
||||
)}
|
||||
</UrlStateContextProvider>
|
||||
);
|
||||
};
|
||||
return (
|
||||
<UrlStateContextProvider value={{ searchString, setUrlState }}>
|
||||
{currentIndexPattern ? (
|
||||
<IndexDataVisualizerComponent
|
||||
currentIndexPattern={currentIndexPattern}
|
||||
currentSavedSearch={currentSavedSearch}
|
||||
additionalLinks={additionalLinks}
|
||||
/>
|
||||
) : (
|
||||
<div />
|
||||
)}
|
||||
</UrlStateContextProvider>
|
||||
);
|
||||
};
|
||||
|
||||
export const IndexDataVisualizer: FC<{ additionalLinks: ResultLink[] }> = ({ additionalLinks }) => {
|
||||
const coreStart = getCoreStart();
|
||||
|
|
|
@ -15,53 +15,50 @@ export interface FleetServerCloudUnhealthyCalloutProps {
|
|||
deploymentUrl: string;
|
||||
}
|
||||
|
||||
export const FleetServerCloudUnhealthyCallout: React.FunctionComponent<FleetServerCloudUnhealthyCalloutProps> =
|
||||
({ deploymentUrl }) => {
|
||||
const { docLinks } = useStartServices();
|
||||
return (
|
||||
<EuiCallOut
|
||||
iconType="alert"
|
||||
color="warning"
|
||||
title={
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.fleetServerCloudUnhealthyCallout.calloutTitle"
|
||||
defaultMessage="Fleet Server is not Healthy"
|
||||
/>
|
||||
}
|
||||
>
|
||||
export const FleetServerCloudUnhealthyCallout: React.FunctionComponent<
|
||||
FleetServerCloudUnhealthyCalloutProps
|
||||
> = ({ deploymentUrl }) => {
|
||||
const { docLinks } = useStartServices();
|
||||
return (
|
||||
<EuiCallOut
|
||||
iconType="alert"
|
||||
color="warning"
|
||||
title={
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.fleetServerCloudRequiredCallout.calloutDescription"
|
||||
defaultMessage="A healthy Fleet server is required to enroll agents with Fleet. Enable Fleet Server in your {cloudDeploymentLink}. For more information see the {guideLink}."
|
||||
values={{
|
||||
cloudDeploymentLink: (
|
||||
<EuiLink href={deploymentUrl} target="_blank" external>
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.fleetServerCloudRequiredCallout.cloudDeploymentLink"
|
||||
defaultMessage="cloud deployment"
|
||||
/>
|
||||
</EuiLink>
|
||||
),
|
||||
guideLink: (
|
||||
<EuiLink
|
||||
href={docLinks.links.fleet.fleetServerAddFleetServer}
|
||||
target="_blank"
|
||||
external
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.fleetServerCloudRequiredCallout.guideLink"
|
||||
defaultMessage="Fleet and Elastic Agent Guide"
|
||||
/>
|
||||
</EuiLink>
|
||||
),
|
||||
}}
|
||||
id="xpack.fleet.fleetServerCloudUnhealthyCallout.calloutTitle"
|
||||
defaultMessage="Fleet Server is not Healthy"
|
||||
/>
|
||||
<EuiSpacer size="m" />
|
||||
<EuiButton href={deploymentUrl} target="_blank" color="warning" fill>
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.fleetServerCloudRequiredCallout.editDeploymentButtonLabel"
|
||||
defaultMessage="Edit deployment"
|
||||
/>
|
||||
</EuiButton>
|
||||
</EuiCallOut>
|
||||
);
|
||||
};
|
||||
}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.fleetServerCloudRequiredCallout.calloutDescription"
|
||||
defaultMessage="A healthy Fleet server is required to enroll agents with Fleet. Enable Fleet Server in your {cloudDeploymentLink}. For more information see the {guideLink}."
|
||||
values={{
|
||||
cloudDeploymentLink: (
|
||||
<EuiLink href={deploymentUrl} target="_blank" external>
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.fleetServerCloudRequiredCallout.cloudDeploymentLink"
|
||||
defaultMessage="cloud deployment"
|
||||
/>
|
||||
</EuiLink>
|
||||
),
|
||||
guideLink: (
|
||||
<EuiLink href={docLinks.links.fleet.fleetServerAddFleetServer} target="_blank" external>
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.fleetServerCloudRequiredCallout.guideLink"
|
||||
defaultMessage="Fleet and Elastic Agent Guide"
|
||||
/>
|
||||
</EuiLink>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
<EuiSpacer size="m" />
|
||||
<EuiButton href={deploymentUrl} target="_blank" color="warning" fill>
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.fleetServerCloudRequiredCallout.editDeploymentButtonLabel"
|
||||
defaultMessage="Edit deployment"
|
||||
/>
|
||||
</EuiButton>
|
||||
</EuiCallOut>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -14,45 +14,42 @@ import { useStartServices } from '../../../../hooks';
|
|||
export interface FleetServerOnPremUnhealthyCalloutProps {
|
||||
onClickAddFleetServer: () => void;
|
||||
}
|
||||
export const FleetServerOnPremUnhealthyCallout: React.FunctionComponent<FleetServerOnPremUnhealthyCalloutProps> =
|
||||
({ onClickAddFleetServer }) => {
|
||||
const { docLinks } = useStartServices();
|
||||
return (
|
||||
<EuiCallOut
|
||||
iconType="alert"
|
||||
color="warning"
|
||||
title={
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.fleetServerOnPremUnhealthyCallout.calloutTitle"
|
||||
defaultMessage="Fleet Server is not Healthy"
|
||||
/>
|
||||
}
|
||||
>
|
||||
export const FleetServerOnPremUnhealthyCallout: React.FunctionComponent<
|
||||
FleetServerOnPremUnhealthyCalloutProps
|
||||
> = ({ onClickAddFleetServer }) => {
|
||||
const { docLinks } = useStartServices();
|
||||
return (
|
||||
<EuiCallOut
|
||||
iconType="alert"
|
||||
color="warning"
|
||||
title={
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.fleetServerOnPremUnhealthyCallout.calloutDescription"
|
||||
defaultMessage="A healthy Fleet server is required before you can enroll agents with Fleet. For more information see the {guideLink}."
|
||||
values={{
|
||||
guideLink: (
|
||||
<EuiLink
|
||||
href={docLinks.links.fleet.fleetServerAddFleetServer}
|
||||
target="_blank"
|
||||
external
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.fleetServerOnPremUnhealthyCallout.guideLink"
|
||||
defaultMessage="Fleet and Elastic Agent Guide"
|
||||
/>
|
||||
</EuiLink>
|
||||
),
|
||||
}}
|
||||
id="xpack.fleet.fleetServerOnPremUnhealthyCallout.calloutTitle"
|
||||
defaultMessage="Fleet Server is not Healthy"
|
||||
/>
|
||||
<EuiSpacer size="m" />
|
||||
<EuiButton onClick={onClickAddFleetServer} color="warning" fill>
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.fleetServerOnPremUnhealthyCallout.addFleetServerButtonLabel"
|
||||
defaultMessage="Add Fleet Server"
|
||||
/>
|
||||
</EuiButton>
|
||||
</EuiCallOut>
|
||||
);
|
||||
};
|
||||
}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.fleetServerOnPremUnhealthyCallout.calloutDescription"
|
||||
defaultMessage="A healthy Fleet server is required before you can enroll agents with Fleet. For more information see the {guideLink}."
|
||||
values={{
|
||||
guideLink: (
|
||||
<EuiLink href={docLinks.links.fleet.fleetServerAddFleetServer} target="_blank" external>
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.fleetServerOnPremUnhealthyCallout.guideLink"
|
||||
defaultMessage="Fleet and Elastic Agent Guide"
|
||||
/>
|
||||
</EuiLink>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
<EuiSpacer size="m" />
|
||||
<EuiButton onClick={onClickAddFleetServer} color="warning" fill>
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.fleetServerOnPremUnhealthyCallout.addFleetServerButtonLabel"
|
||||
defaultMessage="Add Fleet Server"
|
||||
/>
|
||||
</EuiButton>
|
||||
</EuiCallOut>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -60,35 +60,31 @@ export type YamlCodeEditorWithPlaceholderProps = Pick<CodeEditorProps, 'value' |
|
|||
disabled?: boolean;
|
||||
};
|
||||
|
||||
export const YamlCodeEditorWithPlaceholder: React.FunctionComponent<YamlCodeEditorWithPlaceholderProps> =
|
||||
(props) => {
|
||||
const { placeholder, disabled, ...editorProps } = props;
|
||||
|
||||
if (disabled) {
|
||||
return (
|
||||
<EuiCodeBlock
|
||||
style={{ height: '116px' }}
|
||||
language="yaml"
|
||||
isCopyable={false}
|
||||
paddingSize="s"
|
||||
>
|
||||
<pre>{editorProps.value}</pre>
|
||||
</EuiCodeBlock>
|
||||
);
|
||||
}
|
||||
export const YamlCodeEditorWithPlaceholder: React.FunctionComponent<
|
||||
YamlCodeEditorWithPlaceholderProps
|
||||
> = (props) => {
|
||||
const { placeholder, disabled, ...editorProps } = props;
|
||||
|
||||
if (disabled) {
|
||||
return (
|
||||
<CodeEditorContainer>
|
||||
<CodeEditor
|
||||
languageId="yaml"
|
||||
width="100%"
|
||||
height="116px"
|
||||
options={CODE_EDITOR_OPTIONS}
|
||||
{...editorProps}
|
||||
/>
|
||||
{(!editorProps.value || editorProps.value === '') && (
|
||||
<CodeEditorPlaceholder>{placeholder}</CodeEditorPlaceholder>
|
||||
)}
|
||||
</CodeEditorContainer>
|
||||
<EuiCodeBlock style={{ height: '116px' }} language="yaml" isCopyable={false} paddingSize="s">
|
||||
<pre>{editorProps.value}</pre>
|
||||
</EuiCodeBlock>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
return (
|
||||
<CodeEditorContainer>
|
||||
<CodeEditor
|
||||
languageId="yaml"
|
||||
width="100%"
|
||||
height="116px"
|
||||
options={CODE_EDITOR_OPTIONS}
|
||||
{...editorProps}
|
||||
/>
|
||||
{(!editorProps.value || editorProps.value === '') && (
|
||||
<CodeEditorPlaceholder>{placeholder}</CodeEditorPlaceholder>
|
||||
)}
|
||||
</CodeEditorContainer>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -616,13 +616,14 @@ export function Detail() {
|
|||
|
||||
type EuiButtonPropsFull = Parameters<typeof EuiButton>[0];
|
||||
|
||||
const EuiButtonWithTooltip: React.FC<EuiButtonPropsFull & { tooltip?: Partial<EuiToolTipProps> }> =
|
||||
({ tooltip: tooltipProps, ...buttonProps }) => {
|
||||
return tooltipProps ? (
|
||||
<EuiToolTip {...tooltipProps}>
|
||||
<EuiButton {...buttonProps} />
|
||||
</EuiToolTip>
|
||||
) : (
|
||||
const EuiButtonWithTooltip: React.FC<
|
||||
EuiButtonPropsFull & { tooltip?: Partial<EuiToolTipProps> }
|
||||
> = ({ tooltip: tooltipProps, ...buttonProps }) => {
|
||||
return tooltipProps ? (
|
||||
<EuiToolTip {...tooltipProps}>
|
||||
<EuiButton {...buttonProps} />
|
||||
);
|
||||
};
|
||||
</EuiToolTip>
|
||||
) : (
|
||||
<EuiButton {...buttonProps} />
|
||||
);
|
||||
};
|
||||
|
|
|
@ -12,11 +12,12 @@ import { EuiIcon } from '@elastic/eui';
|
|||
import type { UsePackageIconType } from '../hooks';
|
||||
import { usePackageIconType } from '../hooks';
|
||||
|
||||
export const PackageIcon: React.FunctionComponent<UsePackageIconType & Omit<EuiIconProps, 'type'>> =
|
||||
({ packageName, integrationName, version, icons, tryApi, ...euiIconProps }) => {
|
||||
const iconType = usePackageIconType({ packageName, integrationName, version, icons, tryApi });
|
||||
return <EuiIcon size="s" type={iconType} {...euiIconProps} />;
|
||||
};
|
||||
export const PackageIcon: React.FunctionComponent<
|
||||
UsePackageIconType & Omit<EuiIconProps, 'type'>
|
||||
> = ({ packageName, integrationName, version, icons, tryApi, ...euiIconProps }) => {
|
||||
const iconType = usePackageIconType({ packageName, integrationName, version, icons, tryApi });
|
||||
return <EuiIcon size="s" type={iconType} {...euiIconProps} />;
|
||||
};
|
||||
|
||||
export const CardIcon: React.FunctionComponent<UsePackageIconType & Omit<EuiIconProps, 'type'>> = (
|
||||
props
|
||||
|
|
|
@ -28,51 +28,53 @@ import { defaultIngestErrorHandler } from '../../errors';
|
|||
import { licenseService } from '../../services';
|
||||
import * as AgentService from '../../services/agents';
|
||||
|
||||
export const getAgentHandler: RequestHandler<TypeOf<typeof GetOneAgentRequestSchema.params>> =
|
||||
async (context, request, response) => {
|
||||
const soClient = context.core.savedObjects.client;
|
||||
const esClient = context.core.elasticsearch.client.asInternalUser;
|
||||
export const getAgentHandler: RequestHandler<
|
||||
TypeOf<typeof GetOneAgentRequestSchema.params>
|
||||
> = async (context, request, response) => {
|
||||
const soClient = context.core.savedObjects.client;
|
||||
const esClient = context.core.elasticsearch.client.asInternalUser;
|
||||
|
||||
try {
|
||||
const body: GetOneAgentResponse = {
|
||||
item: await AgentService.getAgentById(esClient, request.params.agentId),
|
||||
};
|
||||
try {
|
||||
const body: GetOneAgentResponse = {
|
||||
item: await AgentService.getAgentById(esClient, request.params.agentId),
|
||||
};
|
||||
|
||||
return response.ok({ body });
|
||||
} catch (error) {
|
||||
if (soClient.errors.isNotFoundError(error)) {
|
||||
return response.notFound({
|
||||
body: { message: `Agent ${request.params.agentId} not found` },
|
||||
});
|
||||
}
|
||||
|
||||
return defaultIngestErrorHandler({ error, response });
|
||||
return response.ok({ body });
|
||||
} catch (error) {
|
||||
if (soClient.errors.isNotFoundError(error)) {
|
||||
return response.notFound({
|
||||
body: { message: `Agent ${request.params.agentId} not found` },
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export const deleteAgentHandler: RequestHandler<TypeOf<typeof DeleteAgentRequestSchema.params>> =
|
||||
async (context, request, response) => {
|
||||
const esClient = context.core.elasticsearch.client.asInternalUser;
|
||||
return defaultIngestErrorHandler({ error, response });
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
await AgentService.deleteAgent(esClient, request.params.agentId);
|
||||
export const deleteAgentHandler: RequestHandler<
|
||||
TypeOf<typeof DeleteAgentRequestSchema.params>
|
||||
> = async (context, request, response) => {
|
||||
const esClient = context.core.elasticsearch.client.asInternalUser;
|
||||
|
||||
const body = {
|
||||
action: 'deleted',
|
||||
};
|
||||
try {
|
||||
await AgentService.deleteAgent(esClient, request.params.agentId);
|
||||
|
||||
return response.ok({ body });
|
||||
} catch (error) {
|
||||
if (error.isBoom) {
|
||||
return response.customError({
|
||||
statusCode: error.output.statusCode,
|
||||
body: { message: `Agent ${request.params.agentId} not found` },
|
||||
});
|
||||
}
|
||||
const body = {
|
||||
action: 'deleted',
|
||||
};
|
||||
|
||||
return defaultIngestErrorHandler({ error, response });
|
||||
return response.ok({ body });
|
||||
} catch (error) {
|
||||
if (error.isBoom) {
|
||||
return response.customError({
|
||||
statusCode: error.output.statusCode,
|
||||
body: { message: `Agent ${request.params.agentId} not found` },
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return defaultIngestErrorHandler({ error, response });
|
||||
}
|
||||
};
|
||||
|
||||
export const updateAgentHandler: RequestHandler<
|
||||
TypeOf<typeof UpdateAgentRequestSchema.params>,
|
||||
|
|
|
@ -116,101 +116,102 @@ export const getLimitedListHandler: FleetRequestHandler = async (context, reques
|
|||
}
|
||||
};
|
||||
|
||||
export const getFileHandler: FleetRequestHandler<TypeOf<typeof GetFileRequestSchema.params>> =
|
||||
async (context, request, response) => {
|
||||
try {
|
||||
const { pkgName, pkgVersion, filePath } = request.params;
|
||||
const savedObjectsClient = context.fleet.epm.internalSoClient;
|
||||
const installation = await getInstallation({ savedObjectsClient, pkgName });
|
||||
const useLocalFile = pkgVersion === installation?.version;
|
||||
export const getFileHandler: FleetRequestHandler<
|
||||
TypeOf<typeof GetFileRequestSchema.params>
|
||||
> = async (context, request, response) => {
|
||||
try {
|
||||
const { pkgName, pkgVersion, filePath } = request.params;
|
||||
const savedObjectsClient = context.fleet.epm.internalSoClient;
|
||||
const installation = await getInstallation({ savedObjectsClient, pkgName });
|
||||
const useLocalFile = pkgVersion === installation?.version;
|
||||
|
||||
if (useLocalFile) {
|
||||
const assetPath = `${pkgName}-${pkgVersion}/${filePath}`;
|
||||
const fileBuffer = getArchiveEntry(assetPath);
|
||||
// only pull local installation if we don't have it cached
|
||||
const storedAsset =
|
||||
!fileBuffer && (await getAsset({ savedObjectsClient, path: assetPath }));
|
||||
|
||||
// error, if neither is available
|
||||
if (!fileBuffer && !storedAsset) {
|
||||
return response.custom({
|
||||
body: `installed package file not found: ${filePath}`,
|
||||
statusCode: 404,
|
||||
});
|
||||
}
|
||||
|
||||
// if storedAsset is not available, fileBuffer *must* be
|
||||
// b/c we error if we don't have at least one, and storedAsset is the least likely
|
||||
const { buffer, contentType } = storedAsset
|
||||
? {
|
||||
contentType: storedAsset.media_type,
|
||||
buffer: storedAsset.data_utf8
|
||||
? Buffer.from(storedAsset.data_utf8, 'utf8')
|
||||
: Buffer.from(storedAsset.data_base64, 'base64'),
|
||||
}
|
||||
: {
|
||||
contentType: mime.contentType(path.extname(assetPath)),
|
||||
buffer: fileBuffer,
|
||||
};
|
||||
|
||||
if (!contentType) {
|
||||
return response.custom({
|
||||
body: `unknown content type for file: ${filePath}`,
|
||||
statusCode: 400,
|
||||
});
|
||||
}
|
||||
if (useLocalFile) {
|
||||
const assetPath = `${pkgName}-${pkgVersion}/${filePath}`;
|
||||
const fileBuffer = getArchiveEntry(assetPath);
|
||||
// only pull local installation if we don't have it cached
|
||||
const storedAsset = !fileBuffer && (await getAsset({ savedObjectsClient, path: assetPath }));
|
||||
|
||||
// error, if neither is available
|
||||
if (!fileBuffer && !storedAsset) {
|
||||
return response.custom({
|
||||
body: buffer,
|
||||
statusCode: 200,
|
||||
headers: {
|
||||
'cache-control': 'max-age=10, public',
|
||||
'content-type': contentType,
|
||||
},
|
||||
body: `installed package file not found: ${filePath}`,
|
||||
statusCode: 404,
|
||||
});
|
||||
} else {
|
||||
const registryResponse = await getFile(pkgName, pkgVersion, filePath);
|
||||
const headersToProxy: KnownHeaders[] = ['content-type', 'cache-control'];
|
||||
const proxiedHeaders = headersToProxy.reduce((headers, knownHeader) => {
|
||||
const value = registryResponse.headers.get(knownHeader);
|
||||
if (value !== null) {
|
||||
headers[knownHeader] = value;
|
||||
}
|
||||
|
||||
// if storedAsset is not available, fileBuffer *must* be
|
||||
// b/c we error if we don't have at least one, and storedAsset is the least likely
|
||||
const { buffer, contentType } = storedAsset
|
||||
? {
|
||||
contentType: storedAsset.media_type,
|
||||
buffer: storedAsset.data_utf8
|
||||
? Buffer.from(storedAsset.data_utf8, 'utf8')
|
||||
: Buffer.from(storedAsset.data_base64, 'base64'),
|
||||
}
|
||||
return headers;
|
||||
}, {} as ResponseHeaders);
|
||||
: {
|
||||
contentType: mime.contentType(path.extname(assetPath)),
|
||||
buffer: fileBuffer,
|
||||
};
|
||||
|
||||
if (!contentType) {
|
||||
return response.custom({
|
||||
body: registryResponse.body,
|
||||
statusCode: registryResponse.status,
|
||||
headers: proxiedHeaders,
|
||||
body: `unknown content type for file: ${filePath}`,
|
||||
statusCode: 400,
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
return defaultIngestErrorHandler({ error, response });
|
||||
}
|
||||
};
|
||||
|
||||
export const getInfoHandler: FleetRequestHandler<TypeOf<typeof GetInfoRequestSchema.params>> =
|
||||
async (context, request, response) => {
|
||||
try {
|
||||
const savedObjectsClient = context.fleet.epm.internalSoClient;
|
||||
const { pkgName, pkgVersion } = request.params;
|
||||
if (pkgVersion && !semverValid(pkgVersion)) {
|
||||
throw new IngestManagerError('Package version is not a valid semver');
|
||||
}
|
||||
const res = await getPackageInfo({
|
||||
savedObjectsClient,
|
||||
pkgName,
|
||||
pkgVersion: pkgVersion || '',
|
||||
return response.custom({
|
||||
body: buffer,
|
||||
statusCode: 200,
|
||||
headers: {
|
||||
'cache-control': 'max-age=10, public',
|
||||
'content-type': contentType,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
const registryResponse = await getFile(pkgName, pkgVersion, filePath);
|
||||
const headersToProxy: KnownHeaders[] = ['content-type', 'cache-control'];
|
||||
const proxiedHeaders = headersToProxy.reduce((headers, knownHeader) => {
|
||||
const value = registryResponse.headers.get(knownHeader);
|
||||
if (value !== null) {
|
||||
headers[knownHeader] = value;
|
||||
}
|
||||
return headers;
|
||||
}, {} as ResponseHeaders);
|
||||
|
||||
return response.custom({
|
||||
body: registryResponse.body,
|
||||
statusCode: registryResponse.status,
|
||||
headers: proxiedHeaders,
|
||||
});
|
||||
const body: GetInfoResponse = {
|
||||
item: res,
|
||||
};
|
||||
return response.ok({ body });
|
||||
} catch (error) {
|
||||
return defaultIngestErrorHandler({ error, response });
|
||||
}
|
||||
};
|
||||
} catch (error) {
|
||||
return defaultIngestErrorHandler({ error, response });
|
||||
}
|
||||
};
|
||||
|
||||
export const getInfoHandler: FleetRequestHandler<
|
||||
TypeOf<typeof GetInfoRequestSchema.params>
|
||||
> = async (context, request, response) => {
|
||||
try {
|
||||
const savedObjectsClient = context.fleet.epm.internalSoClient;
|
||||
const { pkgName, pkgVersion } = request.params;
|
||||
if (pkgVersion && !semverValid(pkgVersion)) {
|
||||
throw new IngestManagerError('Package version is not a valid semver');
|
||||
}
|
||||
const res = await getPackageInfo({
|
||||
savedObjectsClient,
|
||||
pkgName,
|
||||
pkgVersion: pkgVersion || '',
|
||||
});
|
||||
const body: GetInfoResponse = {
|
||||
item: res,
|
||||
};
|
||||
return response.ok({ body });
|
||||
} catch (error) {
|
||||
return defaultIngestErrorHandler({ error, response });
|
||||
}
|
||||
};
|
||||
|
||||
export const updatePackageHandler: FleetRequestHandler<
|
||||
TypeOf<typeof UpdatePackageRequestSchema.params>,
|
||||
|
@ -232,19 +233,20 @@ export const updatePackageHandler: FleetRequestHandler<
|
|||
}
|
||||
};
|
||||
|
||||
export const getStatsHandler: FleetRequestHandler<TypeOf<typeof GetStatsRequestSchema.params>> =
|
||||
async (context, request, response) => {
|
||||
try {
|
||||
const { pkgName } = request.params;
|
||||
const savedObjectsClient = context.fleet.epm.internalSoClient;
|
||||
const body: GetStatsResponse = {
|
||||
response: await getPackageUsageStats({ savedObjectsClient, pkgName }),
|
||||
};
|
||||
return response.ok({ body });
|
||||
} catch (error) {
|
||||
return defaultIngestErrorHandler({ error, response });
|
||||
}
|
||||
};
|
||||
export const getStatsHandler: FleetRequestHandler<
|
||||
TypeOf<typeof GetStatsRequestSchema.params>
|
||||
> = async (context, request, response) => {
|
||||
try {
|
||||
const { pkgName } = request.params;
|
||||
const savedObjectsClient = context.fleet.epm.internalSoClient;
|
||||
const body: GetStatsResponse = {
|
||||
response: await getPackageUsageStats({ savedObjectsClient, pkgName }),
|
||||
};
|
||||
return response.ok({ body });
|
||||
} catch (error) {
|
||||
return defaultIngestErrorHandler({ error, response });
|
||||
}
|
||||
};
|
||||
|
||||
export const installPackageFromRegistryHandler: FleetRequestHandler<
|
||||
TypeOf<typeof InstallPackageFromRegistryRequestSchema.params>,
|
||||
|
|
|
@ -41,27 +41,28 @@ export const getOutputsHandler: RequestHandler = async (context, request, respon
|
|||
}
|
||||
};
|
||||
|
||||
export const getOneOuputHandler: RequestHandler<TypeOf<typeof GetOneOutputRequestSchema.params>> =
|
||||
async (context, request, response) => {
|
||||
const soClient = context.core.savedObjects.client;
|
||||
try {
|
||||
const output = await outputService.get(soClient, request.params.outputId);
|
||||
export const getOneOuputHandler: RequestHandler<
|
||||
TypeOf<typeof GetOneOutputRequestSchema.params>
|
||||
> = async (context, request, response) => {
|
||||
const soClient = context.core.savedObjects.client;
|
||||
try {
|
||||
const output = await outputService.get(soClient, request.params.outputId);
|
||||
|
||||
const body: GetOneOutputResponse = {
|
||||
item: output,
|
||||
};
|
||||
const body: GetOneOutputResponse = {
|
||||
item: output,
|
||||
};
|
||||
|
||||
return response.ok({ body });
|
||||
} catch (error) {
|
||||
if (error.isBoom && error.output.statusCode === 404) {
|
||||
return response.notFound({
|
||||
body: { message: `Output ${request.params.outputId} not found` },
|
||||
});
|
||||
}
|
||||
|
||||
return defaultIngestErrorHandler({ error, response });
|
||||
return response.ok({ body });
|
||||
} catch (error) {
|
||||
if (error.isBoom && error.output.statusCode === 404) {
|
||||
return response.notFound({
|
||||
body: { message: `Output ${request.params.outputId} not found` },
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return defaultIngestErrorHandler({ error, response });
|
||||
}
|
||||
};
|
||||
|
||||
export const putOuputHandler: RequestHandler<
|
||||
TypeOf<typeof PutOutputRequestSchema.params>,
|
||||
|
@ -119,24 +120,25 @@ export const postOuputHandler: RequestHandler<
|
|||
}
|
||||
};
|
||||
|
||||
export const deleteOutputHandler: RequestHandler<TypeOf<typeof DeleteOutputRequestSchema.params>> =
|
||||
async (context, request, response) => {
|
||||
const soClient = context.core.savedObjects.client;
|
||||
try {
|
||||
await outputService.delete(soClient, request.params.outputId);
|
||||
export const deleteOutputHandler: RequestHandler<
|
||||
TypeOf<typeof DeleteOutputRequestSchema.params>
|
||||
> = async (context, request, response) => {
|
||||
const soClient = context.core.savedObjects.client;
|
||||
try {
|
||||
await outputService.delete(soClient, request.params.outputId);
|
||||
|
||||
const body: DeleteOutputResponse = {
|
||||
id: request.params.outputId,
|
||||
};
|
||||
const body: DeleteOutputResponse = {
|
||||
id: request.params.outputId,
|
||||
};
|
||||
|
||||
return response.ok({ body });
|
||||
} catch (error) {
|
||||
if (error.isBoom && error.output.statusCode === 404) {
|
||||
return response.notFound({
|
||||
body: { message: `Output ${request.params.outputId} not found` },
|
||||
});
|
||||
}
|
||||
|
||||
return defaultIngestErrorHandler({ error, response });
|
||||
return response.ok({ body });
|
||||
} catch (error) {
|
||||
if (error.isBoom && error.output.statusCode === 404) {
|
||||
return response.notFound({
|
||||
body: { message: `Output ${request.params.outputId} not found` },
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return defaultIngestErrorHandler({ error, response });
|
||||
}
|
||||
};
|
||||
|
|
|
@ -159,177 +159,176 @@ export const SourceStatusWrapper: React.FC = ({ children }) => {
|
|||
);
|
||||
};
|
||||
|
||||
export const Editor: React.FC<AlertTypeParamsExpressionProps<PartialRuleParams, LogsContextMeta>> =
|
||||
(props) => {
|
||||
const { setAlertParams, alertParams, errors } = props;
|
||||
const [hasSetDefaults, setHasSetDefaults] = useState<boolean>(false);
|
||||
const { sourceId, resolvedSourceConfiguration } = useLogSourceContext();
|
||||
export const Editor: React.FC<
|
||||
AlertTypeParamsExpressionProps<PartialRuleParams, LogsContextMeta>
|
||||
> = (props) => {
|
||||
const { setAlertParams, alertParams, errors } = props;
|
||||
const [hasSetDefaults, setHasSetDefaults] = useState<boolean>(false);
|
||||
const { sourceId, resolvedSourceConfiguration } = useLogSourceContext();
|
||||
|
||||
const {
|
||||
criteria: criteriaErrors,
|
||||
threshold: thresholdErrors,
|
||||
timeSizeUnit: timeSizeUnitErrors,
|
||||
timeWindowSize: timeWindowSizeErrors,
|
||||
} = useMemo(() => decodeOrThrow(errorsRT)(errors), [errors]);
|
||||
const {
|
||||
criteria: criteriaErrors,
|
||||
threshold: thresholdErrors,
|
||||
timeSizeUnit: timeSizeUnitErrors,
|
||||
timeWindowSize: timeWindowSizeErrors,
|
||||
} = useMemo(() => decodeOrThrow(errorsRT)(errors), [errors]);
|
||||
|
||||
const supportedFields = useMemo(() => {
|
||||
if (resolvedSourceConfiguration?.fields) {
|
||||
return resolvedSourceConfiguration.fields.filter((field) => {
|
||||
return (field.type === 'string' || field.type === 'number') && field.searchable;
|
||||
});
|
||||
} else {
|
||||
return [];
|
||||
const supportedFields = useMemo(() => {
|
||||
if (resolvedSourceConfiguration?.fields) {
|
||||
return resolvedSourceConfiguration.fields.filter((field) => {
|
||||
return (field.type === 'string' || field.type === 'number') && field.searchable;
|
||||
});
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
}, [resolvedSourceConfiguration]);
|
||||
|
||||
const groupByFields = useMemo(() => {
|
||||
if (resolvedSourceConfiguration?.fields) {
|
||||
return resolvedSourceConfiguration.fields.filter((field) => {
|
||||
return field.type === 'string' && field.aggregatable;
|
||||
});
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
}, [resolvedSourceConfiguration]);
|
||||
|
||||
const updateThreshold = useCallback(
|
||||
(thresholdParams) => {
|
||||
const nextThresholdParams = { ...alertParams.count, ...thresholdParams };
|
||||
setAlertParams('count', nextThresholdParams);
|
||||
},
|
||||
[alertParams.count, setAlertParams]
|
||||
);
|
||||
|
||||
const updateCriteria = useCallback(
|
||||
(criteria: PartialCriteriaType) => {
|
||||
setAlertParams('criteria', criteria);
|
||||
},
|
||||
[setAlertParams]
|
||||
);
|
||||
|
||||
const updateTimeSize = useCallback(
|
||||
(ts: number | undefined) => {
|
||||
setAlertParams('timeSize', ts);
|
||||
},
|
||||
[setAlertParams]
|
||||
);
|
||||
|
||||
const updateTimeUnit = useCallback(
|
||||
(tu: string) => {
|
||||
if (timeUnitRT.is(tu)) {
|
||||
setAlertParams('timeUnit', tu);
|
||||
}
|
||||
}, [resolvedSourceConfiguration]);
|
||||
},
|
||||
[setAlertParams]
|
||||
);
|
||||
|
||||
const groupByFields = useMemo(() => {
|
||||
if (resolvedSourceConfiguration?.fields) {
|
||||
return resolvedSourceConfiguration.fields.filter((field) => {
|
||||
return field.type === 'string' && field.aggregatable;
|
||||
});
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
}, [resolvedSourceConfiguration]);
|
||||
const updateGroupBy = useCallback(
|
||||
(groups: string[]) => {
|
||||
setAlertParams('groupBy', groups);
|
||||
},
|
||||
[setAlertParams]
|
||||
);
|
||||
|
||||
const updateThreshold = useCallback(
|
||||
(thresholdParams) => {
|
||||
const nextThresholdParams = { ...alertParams.count, ...thresholdParams };
|
||||
setAlertParams('count', nextThresholdParams);
|
||||
},
|
||||
[alertParams.count, setAlertParams]
|
||||
);
|
||||
const defaultCountAlertParams = useMemo(
|
||||
() => createDefaultCountRuleParams(supportedFields),
|
||||
[supportedFields]
|
||||
);
|
||||
|
||||
const updateCriteria = useCallback(
|
||||
(criteria: PartialCriteriaType) => {
|
||||
setAlertParams('criteria', criteria);
|
||||
},
|
||||
[setAlertParams]
|
||||
);
|
||||
const updateType = useCallback(
|
||||
(type: ThresholdType) => {
|
||||
const defaults =
|
||||
type === 'count' ? defaultCountAlertParams : createDefaultRatioRuleParams(supportedFields);
|
||||
// Reset properties that don't make sense switching from one context to the other
|
||||
setAlertParams('count', defaults.count);
|
||||
setAlertParams('criteria', defaults.criteria);
|
||||
},
|
||||
[defaultCountAlertParams, setAlertParams, supportedFields]
|
||||
);
|
||||
|
||||
const updateTimeSize = useCallback(
|
||||
(ts: number | undefined) => {
|
||||
setAlertParams('timeSize', ts);
|
||||
},
|
||||
[setAlertParams]
|
||||
);
|
||||
|
||||
const updateTimeUnit = useCallback(
|
||||
(tu: string) => {
|
||||
if (timeUnitRT.is(tu)) {
|
||||
setAlertParams('timeUnit', tu);
|
||||
}
|
||||
},
|
||||
[setAlertParams]
|
||||
);
|
||||
|
||||
const updateGroupBy = useCallback(
|
||||
(groups: string[]) => {
|
||||
setAlertParams('groupBy', groups);
|
||||
},
|
||||
[setAlertParams]
|
||||
);
|
||||
|
||||
const defaultCountAlertParams = useMemo(
|
||||
() => createDefaultCountRuleParams(supportedFields),
|
||||
[supportedFields]
|
||||
);
|
||||
|
||||
const updateType = useCallback(
|
||||
(type: ThresholdType) => {
|
||||
const defaults =
|
||||
type === 'count'
|
||||
? defaultCountAlertParams
|
||||
: createDefaultRatioRuleParams(supportedFields);
|
||||
// Reset properties that don't make sense switching from one context to the other
|
||||
setAlertParams('count', defaults.count);
|
||||
setAlertParams('criteria', defaults.criteria);
|
||||
},
|
||||
[defaultCountAlertParams, setAlertParams, supportedFields]
|
||||
);
|
||||
|
||||
useMount(() => {
|
||||
const newAlertParams = { ...defaultCountAlertParams, ...alertParams };
|
||||
for (const [key, value] of Object.entries(newAlertParams) as ObjectEntries<
|
||||
typeof newAlertParams
|
||||
>) {
|
||||
setAlertParams(key, value);
|
||||
}
|
||||
setHasSetDefaults(true);
|
||||
});
|
||||
|
||||
const shouldShowGroupByOptimizationWarning = useMemo(() => {
|
||||
const hasSetGroupBy = alertParams.groupBy && alertParams.groupBy.length > 0;
|
||||
return (
|
||||
hasSetGroupBy &&
|
||||
alertParams.count &&
|
||||
!isOptimizableGroupedThreshold(alertParams.count.comparator, alertParams.count.value)
|
||||
);
|
||||
}, [alertParams]);
|
||||
|
||||
// Wait until the alert param defaults have been set
|
||||
if (!hasSetDefaults) return null;
|
||||
|
||||
const criteriaComponent = alertParams.criteria ? (
|
||||
<Criteria
|
||||
fields={supportedFields}
|
||||
criteria={alertParams.criteria}
|
||||
defaultCriterion={defaultCountAlertParams.criteria[0]}
|
||||
errors={criteriaErrors}
|
||||
ruleParams={alertParams}
|
||||
sourceId={sourceId}
|
||||
updateCriteria={updateCriteria}
|
||||
/>
|
||||
) : null;
|
||||
useMount(() => {
|
||||
const newAlertParams = { ...defaultCountAlertParams, ...alertParams };
|
||||
for (const [key, value] of Object.entries(newAlertParams) as ObjectEntries<
|
||||
typeof newAlertParams
|
||||
>) {
|
||||
setAlertParams(key, value);
|
||||
}
|
||||
setHasSetDefaults(true);
|
||||
});
|
||||
|
||||
const shouldShowGroupByOptimizationWarning = useMemo(() => {
|
||||
const hasSetGroupBy = alertParams.groupBy && alertParams.groupBy.length > 0;
|
||||
return (
|
||||
<>
|
||||
<TypeSwitcher criteria={alertParams.criteria || []} updateType={updateType} />
|
||||
|
||||
{alertParams.criteria && !isRatioRule(alertParams.criteria) && criteriaComponent}
|
||||
|
||||
<Threshold
|
||||
comparator={alertParams.count?.comparator}
|
||||
value={alertParams.count?.value}
|
||||
updateThreshold={updateThreshold}
|
||||
errors={thresholdErrors}
|
||||
/>
|
||||
|
||||
<ForLastExpression
|
||||
timeWindowSize={alertParams.timeSize}
|
||||
timeWindowUnit={alertParams.timeUnit}
|
||||
onChangeWindowSize={updateTimeSize}
|
||||
onChangeWindowUnit={updateTimeUnit}
|
||||
errors={{ timeWindowSize: timeWindowSizeErrors, timeSizeUnit: timeSizeUnitErrors }}
|
||||
/>
|
||||
|
||||
<GroupByExpression
|
||||
selectedGroups={alertParams.groupBy}
|
||||
onChange={updateGroupBy}
|
||||
fields={groupByFields}
|
||||
/>
|
||||
|
||||
{alertParams.criteria && isRatioRule(alertParams.criteria) && criteriaComponent}
|
||||
|
||||
{shouldShowGroupByOptimizationWarning && (
|
||||
<>
|
||||
<EuiSpacer size="l" />
|
||||
<EuiCallOut color="warning">
|
||||
{i18n.translate('xpack.infra.logs.alertFlyout.groupByOptimizationWarning', {
|
||||
defaultMessage:
|
||||
'When setting a "group by" we highly recommend using the "{comparator}" comparator for your threshold. This can lead to significant performance improvements.',
|
||||
values: {
|
||||
comparator: Comparator.GT,
|
||||
},
|
||||
})}
|
||||
</EuiCallOut>
|
||||
</>
|
||||
)}
|
||||
|
||||
<EuiSpacer size="l" />
|
||||
</>
|
||||
hasSetGroupBy &&
|
||||
alertParams.count &&
|
||||
!isOptimizableGroupedThreshold(alertParams.count.comparator, alertParams.count.value)
|
||||
);
|
||||
};
|
||||
}, [alertParams]);
|
||||
|
||||
// Wait until the alert param defaults have been set
|
||||
if (!hasSetDefaults) return null;
|
||||
|
||||
const criteriaComponent = alertParams.criteria ? (
|
||||
<Criteria
|
||||
fields={supportedFields}
|
||||
criteria={alertParams.criteria}
|
||||
defaultCriterion={defaultCountAlertParams.criteria[0]}
|
||||
errors={criteriaErrors}
|
||||
ruleParams={alertParams}
|
||||
sourceId={sourceId}
|
||||
updateCriteria={updateCriteria}
|
||||
/>
|
||||
) : null;
|
||||
|
||||
return (
|
||||
<>
|
||||
<TypeSwitcher criteria={alertParams.criteria || []} updateType={updateType} />
|
||||
|
||||
{alertParams.criteria && !isRatioRule(alertParams.criteria) && criteriaComponent}
|
||||
|
||||
<Threshold
|
||||
comparator={alertParams.count?.comparator}
|
||||
value={alertParams.count?.value}
|
||||
updateThreshold={updateThreshold}
|
||||
errors={thresholdErrors}
|
||||
/>
|
||||
|
||||
<ForLastExpression
|
||||
timeWindowSize={alertParams.timeSize}
|
||||
timeWindowUnit={alertParams.timeUnit}
|
||||
onChangeWindowSize={updateTimeSize}
|
||||
onChangeWindowUnit={updateTimeUnit}
|
||||
errors={{ timeWindowSize: timeWindowSizeErrors, timeSizeUnit: timeSizeUnitErrors }}
|
||||
/>
|
||||
|
||||
<GroupByExpression
|
||||
selectedGroups={alertParams.groupBy}
|
||||
onChange={updateGroupBy}
|
||||
fields={groupByFields}
|
||||
/>
|
||||
|
||||
{alertParams.criteria && isRatioRule(alertParams.criteria) && criteriaComponent}
|
||||
|
||||
{shouldShowGroupByOptimizationWarning && (
|
||||
<>
|
||||
<EuiSpacer size="l" />
|
||||
<EuiCallOut color="warning">
|
||||
{i18n.translate('xpack.infra.logs.alertFlyout.groupByOptimizationWarning', {
|
||||
defaultMessage:
|
||||
'When setting a "group by" we highly recommend using the "{comparator}" comparator for your threshold. This can lead to significant performance improvements.',
|
||||
values: {
|
||||
comparator: Comparator.GT,
|
||||
},
|
||||
})}
|
||||
</EuiCallOut>
|
||||
</>
|
||||
)}
|
||||
|
||||
<EuiSpacer size="l" />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
// required for dynamic import
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
|
|
|
@ -42,244 +42,243 @@ interface LogEntryCategoriesResultsContentProps {
|
|||
pageTitle: string;
|
||||
}
|
||||
|
||||
export const LogEntryCategoriesResultsContent: React.FunctionComponent<LogEntryCategoriesResultsContentProps> =
|
||||
({ onOpenSetup, pageTitle }) => {
|
||||
useTrackPageview({ app: 'infra_logs', path: 'log_entry_categories_results' });
|
||||
useTrackPageview({ app: 'infra_logs', path: 'log_entry_categories_results', delay: 15000 });
|
||||
export const LogEntryCategoriesResultsContent: React.FunctionComponent<
|
||||
LogEntryCategoriesResultsContentProps
|
||||
> = ({ onOpenSetup, pageTitle }) => {
|
||||
useTrackPageview({ app: 'infra_logs', path: 'log_entry_categories_results' });
|
||||
useTrackPageview({ app: 'infra_logs', path: 'log_entry_categories_results', delay: 15000 });
|
||||
|
||||
const {
|
||||
services: { ml, http },
|
||||
} = useKibanaContextForPlugin();
|
||||
const {
|
||||
services: { ml, http },
|
||||
} = useKibanaContextForPlugin();
|
||||
|
||||
const { sourceStatus } = useLogSourceContext();
|
||||
const { hasLogAnalysisSetupCapabilities } = useLogAnalysisCapabilitiesContext();
|
||||
const { sourceStatus } = useLogSourceContext();
|
||||
const { hasLogAnalysisSetupCapabilities } = useLogAnalysisCapabilitiesContext();
|
||||
|
||||
const {
|
||||
fetchJobStatus,
|
||||
fetchModuleDefinition,
|
||||
moduleDescriptor,
|
||||
setupStatus,
|
||||
hasOutdatedJobConfigurations,
|
||||
hasOutdatedJobDefinitions,
|
||||
hasStoppedJobs,
|
||||
jobIds,
|
||||
categoryQualityWarnings,
|
||||
sourceConfiguration: { sourceId },
|
||||
} = useLogEntryCategoriesModuleContext();
|
||||
const {
|
||||
fetchJobStatus,
|
||||
fetchModuleDefinition,
|
||||
moduleDescriptor,
|
||||
setupStatus,
|
||||
hasOutdatedJobConfigurations,
|
||||
hasOutdatedJobDefinitions,
|
||||
hasStoppedJobs,
|
||||
jobIds,
|
||||
categoryQualityWarnings,
|
||||
sourceConfiguration: { sourceId },
|
||||
} = useLogEntryCategoriesModuleContext();
|
||||
|
||||
const {
|
||||
timeRange: selectedTimeRange,
|
||||
setTimeRange: setSelectedTimeRange,
|
||||
autoRefresh,
|
||||
setAutoRefresh,
|
||||
} = useLogEntryCategoriesResultsUrlState();
|
||||
const {
|
||||
timeRange: selectedTimeRange,
|
||||
setTimeRange: setSelectedTimeRange,
|
||||
autoRefresh,
|
||||
setAutoRefresh,
|
||||
} = useLogEntryCategoriesResultsUrlState();
|
||||
|
||||
const [categoryQueryTimeRange, setCategoryQueryTimeRange] = useState<{
|
||||
lastChangedTime: number;
|
||||
timeRange: TimeRange;
|
||||
}>(() => ({
|
||||
lastChangedTime: Date.now(),
|
||||
timeRange: stringToNumericTimeRange(selectedTimeRange),
|
||||
}));
|
||||
const [categoryQueryTimeRange, setCategoryQueryTimeRange] = useState<{
|
||||
lastChangedTime: number;
|
||||
timeRange: TimeRange;
|
||||
}>(() => ({
|
||||
lastChangedTime: Date.now(),
|
||||
timeRange: stringToNumericTimeRange(selectedTimeRange),
|
||||
}));
|
||||
|
||||
const [categoryQueryDatasets, setCategoryQueryDatasets] = useState<string[]>([]);
|
||||
const [categoryQueryDatasets, setCategoryQueryDatasets] = useState<string[]>([]);
|
||||
|
||||
const { services } = useKibana<{}>();
|
||||
const { services } = useKibana<{}>();
|
||||
|
||||
const showLoadDataErrorNotification = useCallback(
|
||||
(error: Error) => {
|
||||
services.notifications?.toasts.addError(error, {
|
||||
title: loadDataErrorTitle,
|
||||
});
|
||||
const showLoadDataErrorNotification = useCallback(
|
||||
(error: Error) => {
|
||||
services.notifications?.toasts.addError(error, {
|
||||
title: loadDataErrorTitle,
|
||||
});
|
||||
},
|
||||
[services.notifications]
|
||||
);
|
||||
|
||||
const {
|
||||
getLogEntryCategoryDatasets,
|
||||
getTopLogEntryCategories,
|
||||
isLoadingLogEntryCategoryDatasets,
|
||||
isLoadingTopLogEntryCategories,
|
||||
logEntryCategoryDatasets,
|
||||
topLogEntryCategories,
|
||||
sortOptions,
|
||||
changeSortOptions,
|
||||
} = useLogEntryCategoriesResults({
|
||||
categoriesCount: 25,
|
||||
endTime: categoryQueryTimeRange.timeRange.endTime,
|
||||
filteredDatasets: categoryQueryDatasets,
|
||||
onGetTopLogEntryCategoriesError: showLoadDataErrorNotification,
|
||||
sourceId,
|
||||
startTime: categoryQueryTimeRange.timeRange.startTime,
|
||||
});
|
||||
|
||||
const handleQueryTimeRangeChange = useCallback(
|
||||
({ start: startTime, end: endTime }: { start: string; end: string }) => {
|
||||
setCategoryQueryTimeRange((previousQueryParameters) => ({
|
||||
...previousQueryParameters,
|
||||
timeRange: stringToNumericTimeRange({ startTime, endTime }),
|
||||
lastChangedTime: Date.now(),
|
||||
}));
|
||||
},
|
||||
[setCategoryQueryTimeRange]
|
||||
);
|
||||
|
||||
const handleSelectedTimeRangeChange = useCallback(
|
||||
(selectedTime: { start: string; end: string; isInvalid: boolean }) => {
|
||||
if (selectedTime.isInvalid) {
|
||||
return;
|
||||
}
|
||||
setSelectedTimeRange({
|
||||
startTime: selectedTime.start,
|
||||
endTime: selectedTime.end,
|
||||
});
|
||||
handleQueryTimeRangeChange(selectedTime);
|
||||
},
|
||||
[setSelectedTimeRange, handleQueryTimeRangeChange]
|
||||
);
|
||||
|
||||
const handleAutoRefreshChange = useCallback(
|
||||
({ isPaused, refreshInterval: interval }: { isPaused: boolean; refreshInterval: number }) => {
|
||||
setAutoRefresh({
|
||||
isPaused,
|
||||
interval,
|
||||
});
|
||||
},
|
||||
[setAutoRefresh]
|
||||
);
|
||||
|
||||
const hasResults = useMemo(
|
||||
() => topLogEntryCategories.length > 0,
|
||||
[topLogEntryCategories.length]
|
||||
);
|
||||
|
||||
const isFirstUse = useMemo(
|
||||
() =>
|
||||
((setupStatus.type === 'skipped' && !!setupStatus.newlyCreated) ||
|
||||
setupStatus.type === 'succeeded') &&
|
||||
!hasResults,
|
||||
[hasResults, setupStatus]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
getTopLogEntryCategories();
|
||||
}, [
|
||||
getTopLogEntryCategories,
|
||||
categoryQueryDatasets,
|
||||
categoryQueryTimeRange.lastChangedTime,
|
||||
sortOptions,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
getLogEntryCategoryDatasets();
|
||||
}, [getLogEntryCategoryDatasets, categoryQueryTimeRange.lastChangedTime]);
|
||||
|
||||
useEffect(() => {
|
||||
fetchModuleDefinition();
|
||||
}, [fetchModuleDefinition]);
|
||||
|
||||
useInterval(() => {
|
||||
fetchJobStatus();
|
||||
}, JOB_STATUS_POLLING_INTERVAL);
|
||||
|
||||
useInterval(
|
||||
() => {
|
||||
handleQueryTimeRangeChange({
|
||||
start: selectedTimeRange.startTime,
|
||||
end: selectedTimeRange.endTime,
|
||||
});
|
||||
},
|
||||
autoRefresh.isPaused ? null : autoRefresh.interval
|
||||
);
|
||||
|
||||
const analyzeInMlLink = useMlHref(ml, http.basePath.get(), {
|
||||
page: ML_PAGES.ANOMALY_EXPLORER,
|
||||
pageState: {
|
||||
jobIds: [jobIds['log-entry-categories-count']],
|
||||
timeRange: {
|
||||
from: moment(categoryQueryTimeRange.timeRange.startTime).format('YYYY-MM-DDTHH:mm:ss.SSSZ'),
|
||||
to: moment(categoryQueryTimeRange.timeRange.endTime).format('YYYY-MM-DDTHH:mm:ss.SSSZ'),
|
||||
mode: 'absolute',
|
||||
},
|
||||
[services.notifications]
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
const {
|
||||
getLogEntryCategoryDatasets,
|
||||
getTopLogEntryCategories,
|
||||
isLoadingLogEntryCategoryDatasets,
|
||||
isLoadingTopLogEntryCategories,
|
||||
logEntryCategoryDatasets,
|
||||
topLogEntryCategories,
|
||||
sortOptions,
|
||||
changeSortOptions,
|
||||
} = useLogEntryCategoriesResults({
|
||||
categoriesCount: 25,
|
||||
endTime: categoryQueryTimeRange.timeRange.endTime,
|
||||
filteredDatasets: categoryQueryDatasets,
|
||||
onGetTopLogEntryCategoriesError: showLoadDataErrorNotification,
|
||||
sourceId,
|
||||
startTime: categoryQueryTimeRange.timeRange.startTime,
|
||||
});
|
||||
|
||||
const handleQueryTimeRangeChange = useCallback(
|
||||
({ start: startTime, end: endTime }: { start: string; end: string }) => {
|
||||
setCategoryQueryTimeRange((previousQueryParameters) => ({
|
||||
...previousQueryParameters,
|
||||
timeRange: stringToNumericTimeRange({ startTime, endTime }),
|
||||
lastChangedTime: Date.now(),
|
||||
}));
|
||||
},
|
||||
[setCategoryQueryTimeRange]
|
||||
);
|
||||
|
||||
const handleSelectedTimeRangeChange = useCallback(
|
||||
(selectedTime: { start: string; end: string; isInvalid: boolean }) => {
|
||||
if (selectedTime.isInvalid) {
|
||||
return;
|
||||
}
|
||||
setSelectedTimeRange({
|
||||
startTime: selectedTime.start,
|
||||
endTime: selectedTime.end,
|
||||
});
|
||||
handleQueryTimeRangeChange(selectedTime);
|
||||
},
|
||||
[setSelectedTimeRange, handleQueryTimeRangeChange]
|
||||
);
|
||||
|
||||
const handleAutoRefreshChange = useCallback(
|
||||
({ isPaused, refreshInterval: interval }: { isPaused: boolean; refreshInterval: number }) => {
|
||||
setAutoRefresh({
|
||||
isPaused,
|
||||
interval,
|
||||
});
|
||||
},
|
||||
[setAutoRefresh]
|
||||
);
|
||||
|
||||
const hasResults = useMemo(
|
||||
() => topLogEntryCategories.length > 0,
|
||||
[topLogEntryCategories.length]
|
||||
);
|
||||
|
||||
const isFirstUse = useMemo(
|
||||
() =>
|
||||
((setupStatus.type === 'skipped' && !!setupStatus.newlyCreated) ||
|
||||
setupStatus.type === 'succeeded') &&
|
||||
!hasResults,
|
||||
[hasResults, setupStatus]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
getTopLogEntryCategories();
|
||||
}, [
|
||||
getTopLogEntryCategories,
|
||||
categoryQueryDatasets,
|
||||
categoryQueryTimeRange.lastChangedTime,
|
||||
sortOptions,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
getLogEntryCategoryDatasets();
|
||||
}, [getLogEntryCategoryDatasets, categoryQueryTimeRange.lastChangedTime]);
|
||||
|
||||
useEffect(() => {
|
||||
fetchModuleDefinition();
|
||||
}, [fetchModuleDefinition]);
|
||||
|
||||
useInterval(() => {
|
||||
fetchJobStatus();
|
||||
}, JOB_STATUS_POLLING_INTERVAL);
|
||||
|
||||
useInterval(
|
||||
() => {
|
||||
handleQueryTimeRangeChange({
|
||||
start: selectedTimeRange.startTime,
|
||||
end: selectedTimeRange.endTime,
|
||||
});
|
||||
},
|
||||
autoRefresh.isPaused ? null : autoRefresh.interval
|
||||
);
|
||||
|
||||
const analyzeInMlLink = useMlHref(ml, http.basePath.get(), {
|
||||
page: ML_PAGES.ANOMALY_EXPLORER,
|
||||
pageState: {
|
||||
jobIds: [jobIds['log-entry-categories-count']],
|
||||
timeRange: {
|
||||
from: moment(categoryQueryTimeRange.timeRange.startTime).format(
|
||||
'YYYY-MM-DDTHH:mm:ss.SSSZ'
|
||||
),
|
||||
to: moment(categoryQueryTimeRange.timeRange.endTime).format('YYYY-MM-DDTHH:mm:ss.SSSZ'),
|
||||
mode: 'absolute',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<ViewLogInContext.Provider
|
||||
sourceId={sourceId}
|
||||
startTimestamp={categoryQueryTimeRange.timeRange.startTime}
|
||||
endTimestamp={categoryQueryTimeRange.timeRange.endTime}
|
||||
return (
|
||||
<ViewLogInContext.Provider
|
||||
sourceId={sourceId}
|
||||
startTimestamp={categoryQueryTimeRange.timeRange.startTime}
|
||||
endTimestamp={categoryQueryTimeRange.timeRange.endTime}
|
||||
>
|
||||
<LogsPageTemplate
|
||||
hasData={sourceStatus?.logIndexStatus !== 'missing'}
|
||||
pageHeader={{
|
||||
pageTitle,
|
||||
rightSideItems: [
|
||||
<RecreateJobButton
|
||||
hasSetupCapabilities={hasLogAnalysisSetupCapabilities}
|
||||
onClick={onOpenSetup}
|
||||
size="s"
|
||||
/>,
|
||||
<AnalyzeInMlButton href={analyzeInMlLink} />,
|
||||
],
|
||||
}}
|
||||
>
|
||||
<LogsPageTemplate
|
||||
hasData={sourceStatus?.logIndexStatus !== 'missing'}
|
||||
pageHeader={{
|
||||
pageTitle,
|
||||
rightSideItems: [
|
||||
<RecreateJobButton
|
||||
hasSetupCapabilities={hasLogAnalysisSetupCapabilities}
|
||||
onClick={onOpenSetup}
|
||||
size="s"
|
||||
/>,
|
||||
<AnalyzeInMlButton href={analyzeInMlLink} />,
|
||||
],
|
||||
}}
|
||||
>
|
||||
<EuiFlexGroup direction="column">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiFlexGroup justifyContent="spaceBetween" alignItems="center">
|
||||
<EuiFlexItem>
|
||||
<DatasetsSelector
|
||||
availableDatasets={logEntryCategoryDatasets}
|
||||
isLoading={isLoadingLogEntryCategoryDatasets}
|
||||
onChangeDatasetSelection={setCategoryQueryDatasets}
|
||||
selectedDatasets={categoryQueryDatasets}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiSuperDatePicker
|
||||
start={selectedTimeRange.startTime}
|
||||
end={selectedTimeRange.endTime}
|
||||
onTimeChange={handleSelectedTimeRangeChange}
|
||||
isPaused={autoRefresh.isPaused}
|
||||
refreshInterval={autoRefresh.interval}
|
||||
onRefreshChange={handleAutoRefreshChange}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<MLJobsAwaitingNodeWarning jobIds={Object.values(jobIds)} />
|
||||
<CategoryJobNoticesSection
|
||||
hasOutdatedJobConfigurations={hasOutdatedJobConfigurations}
|
||||
hasOutdatedJobDefinitions={hasOutdatedJobDefinitions}
|
||||
hasSetupCapabilities={hasLogAnalysisSetupCapabilities}
|
||||
hasStoppedJobs={hasStoppedJobs}
|
||||
isFirstUse={isFirstUse}
|
||||
moduleName={moduleDescriptor.moduleName}
|
||||
onRecreateMlJobForReconfiguration={onOpenSetup}
|
||||
onRecreateMlJobForUpdate={onOpenSetup}
|
||||
qualityWarnings={categoryQualityWarnings}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<TopCategoriesSection
|
||||
isLoadingTopCategories={isLoadingTopLogEntryCategories}
|
||||
jobId={jobIds['log-entry-categories-count']}
|
||||
sourceId={sourceId}
|
||||
timeRange={categoryQueryTimeRange.timeRange}
|
||||
topCategories={topLogEntryCategories}
|
||||
sortOptions={sortOptions}
|
||||
changeSortOptions={changeSortOptions}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</LogsPageTemplate>
|
||||
<PageViewLogInContext />
|
||||
</ViewLogInContext.Provider>
|
||||
);
|
||||
};
|
||||
<EuiFlexGroup direction="column">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiFlexGroup justifyContent="spaceBetween" alignItems="center">
|
||||
<EuiFlexItem>
|
||||
<DatasetsSelector
|
||||
availableDatasets={logEntryCategoryDatasets}
|
||||
isLoading={isLoadingLogEntryCategoryDatasets}
|
||||
onChangeDatasetSelection={setCategoryQueryDatasets}
|
||||
selectedDatasets={categoryQueryDatasets}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiSuperDatePicker
|
||||
start={selectedTimeRange.startTime}
|
||||
end={selectedTimeRange.endTime}
|
||||
onTimeChange={handleSelectedTimeRangeChange}
|
||||
isPaused={autoRefresh.isPaused}
|
||||
refreshInterval={autoRefresh.interval}
|
||||
onRefreshChange={handleAutoRefreshChange}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<MLJobsAwaitingNodeWarning jobIds={Object.values(jobIds)} />
|
||||
<CategoryJobNoticesSection
|
||||
hasOutdatedJobConfigurations={hasOutdatedJobConfigurations}
|
||||
hasOutdatedJobDefinitions={hasOutdatedJobDefinitions}
|
||||
hasSetupCapabilities={hasLogAnalysisSetupCapabilities}
|
||||
hasStoppedJobs={hasStoppedJobs}
|
||||
isFirstUse={isFirstUse}
|
||||
moduleName={moduleDescriptor.moduleName}
|
||||
onRecreateMlJobForReconfiguration={onOpenSetup}
|
||||
onRecreateMlJobForUpdate={onOpenSetup}
|
||||
qualityWarnings={categoryQualityWarnings}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<TopCategoriesSection
|
||||
isLoadingTopCategories={isLoadingTopLogEntryCategories}
|
||||
jobId={jobIds['log-entry-categories-count']}
|
||||
sourceId={sourceId}
|
||||
timeRange={categoryQueryTimeRange.timeRange}
|
||||
topCategories={topLogEntryCategories}
|
||||
sortOptions={sortOptions}
|
||||
changeSortOptions={changeSortOptions}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</LogsPageTemplate>
|
||||
<PageViewLogInContext />
|
||||
</ViewLogInContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
const stringToNumericTimeRange = (timeRange: StringTimeRange): TimeRange => ({
|
||||
startTime: moment(
|
||||
|
|
|
@ -14,40 +14,41 @@ interface LogEntryCategoriesSetupContentProps {
|
|||
onOpenSetup: () => void;
|
||||
}
|
||||
|
||||
export const LogEntryCategoriesSetupContent: React.FunctionComponent<LogEntryCategoriesSetupContentProps> =
|
||||
({ onOpenSetup }) => {
|
||||
useTrackPageview({ app: 'infra_logs', path: 'log_entry_categories_setup' });
|
||||
useTrackPageview({ app: 'infra_logs', path: 'log_entry_categories_setup', delay: 15000 });
|
||||
export const LogEntryCategoriesSetupContent: React.FunctionComponent<
|
||||
LogEntryCategoriesSetupContentProps
|
||||
> = ({ onOpenSetup }) => {
|
||||
useTrackPageview({ app: 'infra_logs', path: 'log_entry_categories_setup' });
|
||||
useTrackPageview({ app: 'infra_logs', path: 'log_entry_categories_setup', delay: 15000 });
|
||||
|
||||
return (
|
||||
<EuiEmptyPrompt
|
||||
data-test-subj="logEntryCategoriesSetupPage"
|
||||
title={
|
||||
<h2>
|
||||
return (
|
||||
<EuiEmptyPrompt
|
||||
data-test-subj="logEntryCategoriesSetupPage"
|
||||
title={
|
||||
<h2>
|
||||
<FormattedMessage
|
||||
id="xpack.infra.logs.logEntryCategories.setupTitle"
|
||||
defaultMessage="Set up log category analysis"
|
||||
/>
|
||||
</h2>
|
||||
}
|
||||
body={
|
||||
<EuiText size="s">
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="xpack.infra.logs.logEntryCategories.setupTitle"
|
||||
defaultMessage="Set up log category analysis"
|
||||
id="xpack.infra.logs.logEntryCategories.setupDescription"
|
||||
defaultMessage="To enable log categories, set up a machine learning job."
|
||||
/>
|
||||
</h2>
|
||||
}
|
||||
body={
|
||||
<EuiText size="s">
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="xpack.infra.logs.logEntryCategories.setupDescription"
|
||||
defaultMessage="To enable log categories, set up a machine learning job."
|
||||
/>
|
||||
</p>
|
||||
</EuiText>
|
||||
}
|
||||
actions={
|
||||
<EuiButton fill onClick={onOpenSetup}>
|
||||
<FormattedMessage
|
||||
id="xpack.infra.logs.logEntryCategories.showAnalysisSetupButtonLabel"
|
||||
defaultMessage="ML setup"
|
||||
/>
|
||||
</EuiButton>
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
||||
</p>
|
||||
</EuiText>
|
||||
}
|
||||
actions={
|
||||
<EuiButton fill onClick={onOpenSetup}>
|
||||
<FormattedMessage
|
||||
id="xpack.infra.logs.logEntryCategories.showAnalysisSetupButtonLabel"
|
||||
defaultMessage="ML setup"
|
||||
/>
|
||||
</EuiButton>
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -32,39 +32,40 @@ export type TagEnhancedSavedObjectSaveModalDashboardProps = Omit<
|
|||
|
||||
const SavedObjectSaveModalDashboard = withSuspense(LazySavedObjectSaveModalDashboard);
|
||||
|
||||
export const TagEnhancedSavedObjectSaveModalDashboard: FC<TagEnhancedSavedObjectSaveModalDashboardProps> =
|
||||
({ initialTags, onSave, savedObjectsTagging, ...otherProps }) => {
|
||||
const [selectedTags, setSelectedTags] = useState(initialTags);
|
||||
export const TagEnhancedSavedObjectSaveModalDashboard: FC<
|
||||
TagEnhancedSavedObjectSaveModalDashboardProps
|
||||
> = ({ initialTags, onSave, savedObjectsTagging, ...otherProps }) => {
|
||||
const [selectedTags, setSelectedTags] = useState(initialTags);
|
||||
|
||||
const tagSelectorOption = useMemo(
|
||||
() =>
|
||||
savedObjectsTagging ? (
|
||||
<savedObjectsTagging.ui.components.SavedObjectSaveModalTagSelector
|
||||
initialSelection={initialTags}
|
||||
onTagsSelected={setSelectedTags}
|
||||
/>
|
||||
) : undefined,
|
||||
[savedObjectsTagging, initialTags]
|
||||
);
|
||||
const tagSelectorOption = useMemo(
|
||||
() =>
|
||||
savedObjectsTagging ? (
|
||||
<savedObjectsTagging.ui.components.SavedObjectSaveModalTagSelector
|
||||
initialSelection={initialTags}
|
||||
onTagsSelected={setSelectedTags}
|
||||
/>
|
||||
) : undefined,
|
||||
[savedObjectsTagging, initialTags]
|
||||
);
|
||||
|
||||
const tagEnhancedOptions = <>{tagSelectorOption}</>;
|
||||
const tagEnhancedOptions = <>{tagSelectorOption}</>;
|
||||
|
||||
const tagEnhancedOnSave: SaveModalDashboardProps['onSave'] = useCallback(
|
||||
(saveOptions) => {
|
||||
onSave({
|
||||
...saveOptions,
|
||||
returnToOrigin: false,
|
||||
newTags: selectedTags,
|
||||
});
|
||||
},
|
||||
[onSave, selectedTags]
|
||||
);
|
||||
const tagEnhancedOnSave: SaveModalDashboardProps['onSave'] = useCallback(
|
||||
(saveOptions) => {
|
||||
onSave({
|
||||
...saveOptions,
|
||||
returnToOrigin: false,
|
||||
newTags: selectedTags,
|
||||
});
|
||||
},
|
||||
[onSave, selectedTags]
|
||||
);
|
||||
|
||||
return (
|
||||
<SavedObjectSaveModalDashboard
|
||||
{...otherProps}
|
||||
onSave={tagEnhancedOnSave}
|
||||
tagOptions={tagEnhancedOptions}
|
||||
/>
|
||||
);
|
||||
};
|
||||
return (
|
||||
<SavedObjectSaveModalDashboard
|
||||
{...otherProps}
|
||||
onSave={tagEnhancedOnSave}
|
||||
tagOptions={tagEnhancedOptions}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -22,53 +22,54 @@ export type TagEnhancedSavedObjectSaveModalOriginProps = Omit<OriginSaveModalPro
|
|||
onSave: (props: OriginSaveProps) => void;
|
||||
};
|
||||
|
||||
export const TagEnhancedSavedObjectSaveModalOrigin: FC<TagEnhancedSavedObjectSaveModalOriginProps> =
|
||||
({ initialTags, onSave, savedObjectsTagging, options, ...otherProps }) => {
|
||||
const [selectedTags, setSelectedTags] = useState(initialTags);
|
||||
export const TagEnhancedSavedObjectSaveModalOrigin: FC<
|
||||
TagEnhancedSavedObjectSaveModalOriginProps
|
||||
> = ({ initialTags, onSave, savedObjectsTagging, options, ...otherProps }) => {
|
||||
const [selectedTags, setSelectedTags] = useState(initialTags);
|
||||
|
||||
const tagSelectorOption = useMemo(
|
||||
() =>
|
||||
savedObjectsTagging ? (
|
||||
<savedObjectsTagging.ui.components.SavedObjectSaveModalTagSelector
|
||||
initialSelection={initialTags}
|
||||
onTagsSelected={setSelectedTags}
|
||||
/>
|
||||
) : undefined,
|
||||
[savedObjectsTagging, initialTags]
|
||||
const tagSelectorOption = useMemo(
|
||||
() =>
|
||||
savedObjectsTagging ? (
|
||||
<savedObjectsTagging.ui.components.SavedObjectSaveModalTagSelector
|
||||
initialSelection={initialTags}
|
||||
onTagsSelected={setSelectedTags}
|
||||
/>
|
||||
) : undefined,
|
||||
[savedObjectsTagging, initialTags]
|
||||
);
|
||||
|
||||
const tagEnhancedOptions =
|
||||
typeof options === 'function' ? (
|
||||
(state: SaveModalState) => {
|
||||
return (
|
||||
<>
|
||||
{tagSelectorOption}
|
||||
{options(state)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
) : (
|
||||
<>
|
||||
{tagSelectorOption}
|
||||
{options}
|
||||
</>
|
||||
);
|
||||
|
||||
const tagEnhancedOptions =
|
||||
typeof options === 'function' ? (
|
||||
(state: SaveModalState) => {
|
||||
return (
|
||||
<>
|
||||
{tagSelectorOption}
|
||||
{options(state)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
) : (
|
||||
<>
|
||||
{tagSelectorOption}
|
||||
{options}
|
||||
</>
|
||||
);
|
||||
const tagEnhancedOnSave: OriginSaveModalProps['onSave'] = useCallback(
|
||||
(saveOptions) => {
|
||||
onSave({
|
||||
...saveOptions,
|
||||
newTags: selectedTags,
|
||||
});
|
||||
},
|
||||
[onSave, selectedTags]
|
||||
);
|
||||
|
||||
const tagEnhancedOnSave: OriginSaveModalProps['onSave'] = useCallback(
|
||||
(saveOptions) => {
|
||||
onSave({
|
||||
...saveOptions,
|
||||
newTags: selectedTags,
|
||||
});
|
||||
},
|
||||
[onSave, selectedTags]
|
||||
);
|
||||
|
||||
return (
|
||||
<SavedObjectSaveModalOrigin
|
||||
{...otherProps}
|
||||
onSave={tagEnhancedOnSave}
|
||||
options={tagEnhancedOptions}
|
||||
/>
|
||||
);
|
||||
};
|
||||
return (
|
||||
<SavedObjectSaveModalOrigin
|
||||
{...otherProps}
|
||||
onSave={tagEnhancedOnSave}
|
||||
options={tagEnhancedOptions}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -403,14 +403,16 @@ const transformTableState: SavedObjectMigrationFn<
|
|||
return newDoc;
|
||||
};
|
||||
|
||||
const renameOperationsForFormula: SavedObjectMigrationFn<LensDocShapePre712, LensDocShapePost712> =
|
||||
(doc) => {
|
||||
const newDoc = cloneDeep(doc);
|
||||
return {
|
||||
...newDoc,
|
||||
attributes: commonRenameOperationsForFormula(newDoc.attributes),
|
||||
};
|
||||
const renameOperationsForFormula: SavedObjectMigrationFn<
|
||||
LensDocShapePre712,
|
||||
LensDocShapePost712
|
||||
> = (doc) => {
|
||||
const newDoc = cloneDeep(doc);
|
||||
return {
|
||||
...newDoc,
|
||||
attributes: commonRenameOperationsForFormula(newDoc.attributes),
|
||||
};
|
||||
};
|
||||
|
||||
const removeTimezoneDateHistogramParam: SavedObjectMigrationFn<LensDocShape713, LensDocShape714> = (
|
||||
doc
|
||||
|
|
|
@ -17,8 +17,9 @@ import {
|
|||
} from '@elastic/eui';
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const LinkButton: React.FC<PropsForButton<EuiButtonProps> | PropsForAnchor<EuiButtonProps>> =
|
||||
({ children, ...props }) => <EuiButton {...props}>{children}</EuiButton>;
|
||||
export const LinkButton: React.FC<
|
||||
PropsForButton<EuiButtonProps> | PropsForAnchor<EuiButtonProps>
|
||||
> = ({ children, ...props }) => <EuiButton {...props}>{children}</EuiButton>;
|
||||
|
||||
export const LinkAnchor: React.FC<EuiLinkProps> = ({ children, ...props }) => (
|
||||
<EuiLink {...props}>{children}</EuiLink>
|
||||
|
|
|
@ -31,63 +31,64 @@ const reason =
|
|||
* accepts `EuiDataGridCellValueElementProps`, plus `data`
|
||||
* from the TGrid
|
||||
*/
|
||||
export const RenderCellValue: React.FC<EuiDataGridCellValueElementProps & CellValueElementProps> =
|
||||
({
|
||||
columnId,
|
||||
data,
|
||||
eventId,
|
||||
header,
|
||||
isDetails,
|
||||
isDraggable,
|
||||
isExpandable,
|
||||
isExpanded,
|
||||
linkValues,
|
||||
rowIndex,
|
||||
setCellProps,
|
||||
timelineId,
|
||||
}) => {
|
||||
const value =
|
||||
getMappedNonEcsValue({
|
||||
data,
|
||||
fieldName: columnId,
|
||||
})?.reduce((x) => x[0]) ?? '';
|
||||
export const RenderCellValue: React.FC<
|
||||
EuiDataGridCellValueElementProps & CellValueElementProps
|
||||
> = ({
|
||||
columnId,
|
||||
data,
|
||||
eventId,
|
||||
header,
|
||||
isDetails,
|
||||
isDraggable,
|
||||
isExpandable,
|
||||
isExpanded,
|
||||
linkValues,
|
||||
rowIndex,
|
||||
setCellProps,
|
||||
timelineId,
|
||||
}) => {
|
||||
const value =
|
||||
getMappedNonEcsValue({
|
||||
data,
|
||||
fieldName: columnId,
|
||||
})?.reduce((x) => x[0]) ?? '';
|
||||
|
||||
switch (columnId) {
|
||||
case ALERT_STATUS:
|
||||
return (
|
||||
<Status data-test-subj="alert-status" status={random(0, 1) ? 'recovered' : 'active'} />
|
||||
);
|
||||
case ALERT_DURATION:
|
||||
case 'signal.duration.us':
|
||||
return <span data-test-subj="alert-duration">{moment().fromNow(true)}</span>;
|
||||
case ALERT_RULE_SEVERITY:
|
||||
case 'signal.rule.severity':
|
||||
return <Severity data-test-subj="rule-severity" severity={value} />;
|
||||
case ALERT_REASON:
|
||||
case 'signal.reason':
|
||||
return (
|
||||
<EuiLink data-test-subj="reason">
|
||||
<TruncatableText>{reason}</TruncatableText>
|
||||
</EuiLink>
|
||||
);
|
||||
default:
|
||||
// NOTE: we're using `DefaultCellRenderer` in this example configuration as a fallback, but
|
||||
// using `DefaultCellRenderer` here is entirely optional
|
||||
return (
|
||||
<DefaultCellRenderer
|
||||
columnId={columnId}
|
||||
data={data}
|
||||
eventId={eventId}
|
||||
header={header}
|
||||
isDetails={isDetails}
|
||||
isDraggable={isDraggable}
|
||||
isExpandable={isExpandable}
|
||||
isExpanded={isExpanded}
|
||||
linkValues={linkValues}
|
||||
rowIndex={rowIndex}
|
||||
setCellProps={setCellProps}
|
||||
timelineId={timelineId}
|
||||
/>
|
||||
);
|
||||
}
|
||||
};
|
||||
switch (columnId) {
|
||||
case ALERT_STATUS:
|
||||
return (
|
||||
<Status data-test-subj="alert-status" status={random(0, 1) ? 'recovered' : 'active'} />
|
||||
);
|
||||
case ALERT_DURATION:
|
||||
case 'signal.duration.us':
|
||||
return <span data-test-subj="alert-duration">{moment().fromNow(true)}</span>;
|
||||
case ALERT_RULE_SEVERITY:
|
||||
case 'signal.rule.severity':
|
||||
return <Severity data-test-subj="rule-severity" severity={value} />;
|
||||
case ALERT_REASON:
|
||||
case 'signal.reason':
|
||||
return (
|
||||
<EuiLink data-test-subj="reason">
|
||||
<TruncatableText>{reason}</TruncatableText>
|
||||
</EuiLink>
|
||||
);
|
||||
default:
|
||||
// NOTE: we're using `DefaultCellRenderer` in this example configuration as a fallback, but
|
||||
// using `DefaultCellRenderer` here is entirely optional
|
||||
return (
|
||||
<DefaultCellRenderer
|
||||
columnId={columnId}
|
||||
data={data}
|
||||
eventId={eventId}
|
||||
header={header}
|
||||
isDetails={isDetails}
|
||||
isDraggable={isDraggable}
|
||||
isExpandable={isExpandable}
|
||||
isExpanded={isExpanded}
|
||||
linkValues={linkValues}
|
||||
rowIndex={rowIndex}
|
||||
setCellProps={setCellProps}
|
||||
timelineId={timelineId}
|
||||
/>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -24,59 +24,60 @@ const reason =
|
|||
* accepts `EuiDataGridCellValueElementProps`, plus `data`
|
||||
* from the TGrid
|
||||
*/
|
||||
export const RenderCellValue: React.FC<EuiDataGridCellValueElementProps & CellValueElementProps> =
|
||||
({
|
||||
columnId,
|
||||
data,
|
||||
eventId,
|
||||
header,
|
||||
isDetails,
|
||||
isExpandable,
|
||||
isExpanded,
|
||||
linkValues,
|
||||
rowIndex,
|
||||
setCellProps,
|
||||
timelineId,
|
||||
}) => {
|
||||
const value =
|
||||
getMappedNonEcsValue({
|
||||
data,
|
||||
fieldName: columnId,
|
||||
})?.reduce((x) => x[0]) ?? '';
|
||||
const draggableId = `${timelineId}-${eventId}-${columnId}-${value}`;
|
||||
export const RenderCellValue: React.FC<
|
||||
EuiDataGridCellValueElementProps & CellValueElementProps
|
||||
> = ({
|
||||
columnId,
|
||||
data,
|
||||
eventId,
|
||||
header,
|
||||
isDetails,
|
||||
isExpandable,
|
||||
isExpanded,
|
||||
linkValues,
|
||||
rowIndex,
|
||||
setCellProps,
|
||||
timelineId,
|
||||
}) => {
|
||||
const value =
|
||||
getMappedNonEcsValue({
|
||||
data,
|
||||
fieldName: columnId,
|
||||
})?.reduce((x) => x[0]) ?? '';
|
||||
const draggableId = `${timelineId}-${eventId}-${columnId}-${value}`;
|
||||
|
||||
switch (columnId) {
|
||||
case 'signal.rule.severity':
|
||||
case ALERT_RULE_SEVERITY:
|
||||
return (
|
||||
<DefaultDraggable
|
||||
data-test-subj="custom-severity"
|
||||
field={columnId}
|
||||
id={draggableId}
|
||||
value={value}
|
||||
>
|
||||
<Severity severity={value} />
|
||||
</DefaultDraggable>
|
||||
);
|
||||
case 'signal.reason':
|
||||
case ALERT_REASON:
|
||||
return <TruncatableText data-test-subj="custom-reason">{reason}</TruncatableText>;
|
||||
default:
|
||||
return (
|
||||
<DefaultCellRenderer
|
||||
columnId={columnId}
|
||||
data={data}
|
||||
eventId={eventId}
|
||||
header={header}
|
||||
isDetails={isDetails}
|
||||
isDraggable={false}
|
||||
isExpandable={isExpandable}
|
||||
isExpanded={isExpanded}
|
||||
linkValues={linkValues}
|
||||
rowIndex={rowIndex}
|
||||
setCellProps={setCellProps}
|
||||
timelineId={timelineId}
|
||||
/>
|
||||
);
|
||||
}
|
||||
};
|
||||
switch (columnId) {
|
||||
case 'signal.rule.severity':
|
||||
case ALERT_RULE_SEVERITY:
|
||||
return (
|
||||
<DefaultDraggable
|
||||
data-test-subj="custom-severity"
|
||||
field={columnId}
|
||||
id={draggableId}
|
||||
value={value}
|
||||
>
|
||||
<Severity severity={value} />
|
||||
</DefaultDraggable>
|
||||
);
|
||||
case 'signal.reason':
|
||||
case ALERT_REASON:
|
||||
return <TruncatableText data-test-subj="custom-reason">{reason}</TruncatableText>;
|
||||
default:
|
||||
return (
|
||||
<DefaultCellRenderer
|
||||
columnId={columnId}
|
||||
data={data}
|
||||
eventId={eventId}
|
||||
header={header}
|
||||
isDetails={isDetails}
|
||||
isDraggable={false}
|
||||
isExpandable={isExpandable}
|
||||
isExpanded={isExpanded}
|
||||
linkValues={linkValues}
|
||||
rowIndex={rowIndex}
|
||||
setCellProps={setCellProps}
|
||||
timelineId={timelineId}
|
||||
/>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -16,43 +16,44 @@ import { DefaultCellRenderer } from '../../../timelines/components/timeline/cell
|
|||
* accepts `EuiDataGridCellValueElementProps`, plus `data`
|
||||
* from the TGrid
|
||||
*/
|
||||
export const RenderCellValue: React.FC<EuiDataGridCellValueElementProps & CellValueElementProps> =
|
||||
({
|
||||
browserFields,
|
||||
columnId,
|
||||
data,
|
||||
ecsData,
|
||||
eventId,
|
||||
globalFilters,
|
||||
header,
|
||||
isDetails,
|
||||
isDraggable,
|
||||
isExpandable,
|
||||
isExpanded,
|
||||
linkValues,
|
||||
rowIndex,
|
||||
rowRenderers,
|
||||
setCellProps,
|
||||
timelineId,
|
||||
truncate,
|
||||
}) => (
|
||||
<DefaultCellRenderer
|
||||
browserFields={browserFields}
|
||||
columnId={columnId}
|
||||
data={data}
|
||||
ecsData={ecsData}
|
||||
eventId={eventId}
|
||||
globalFilters={globalFilters}
|
||||
header={header}
|
||||
isDetails={isDetails}
|
||||
isDraggable={isDraggable}
|
||||
isExpandable={isExpandable}
|
||||
isExpanded={isExpanded}
|
||||
linkValues={linkValues}
|
||||
rowIndex={rowIndex}
|
||||
rowRenderers={rowRenderers}
|
||||
setCellProps={setCellProps}
|
||||
timelineId={timelineId}
|
||||
truncate={truncate}
|
||||
/>
|
||||
);
|
||||
export const RenderCellValue: React.FC<
|
||||
EuiDataGridCellValueElementProps & CellValueElementProps
|
||||
> = ({
|
||||
browserFields,
|
||||
columnId,
|
||||
data,
|
||||
ecsData,
|
||||
eventId,
|
||||
globalFilters,
|
||||
header,
|
||||
isDetails,
|
||||
isDraggable,
|
||||
isExpandable,
|
||||
isExpanded,
|
||||
linkValues,
|
||||
rowIndex,
|
||||
rowRenderers,
|
||||
setCellProps,
|
||||
timelineId,
|
||||
truncate,
|
||||
}) => (
|
||||
<DefaultCellRenderer
|
||||
browserFields={browserFields}
|
||||
columnId={columnId}
|
||||
data={data}
|
||||
ecsData={ecsData}
|
||||
eventId={eventId}
|
||||
globalFilters={globalFilters}
|
||||
header={header}
|
||||
isDetails={isDetails}
|
||||
isDraggable={isDraggable}
|
||||
isExpandable={isExpandable}
|
||||
isExpanded={isExpanded}
|
||||
linkValues={linkValues}
|
||||
rowIndex={rowIndex}
|
||||
rowRenderers={rowRenderers}
|
||||
setCellProps={setCellProps}
|
||||
timelineId={timelineId}
|
||||
truncate={truncate}
|
||||
/>
|
||||
);
|
||||
|
|
|
@ -346,5 +346,6 @@ export const createEventFiltersPageMiddleware = (
|
|||
};
|
||||
};
|
||||
|
||||
export const eventFiltersPageMiddlewareFactory: ImmutableMiddlewareFactory<EventFiltersListPageState> =
|
||||
(coreStart) => createEventFiltersPageMiddleware(new EventFiltersHttpService(coreStart.http));
|
||||
export const eventFiltersPageMiddlewareFactory: ImmutableMiddlewareFactory<
|
||||
EventFiltersListPageState
|
||||
> = (coreStart) => createEventFiltersPageMiddleware(new EventFiltersHttpService(coreStart.http));
|
||||
|
|
|
@ -66,16 +66,17 @@ const handleEventFiltersListPageDataChanges: CaseReducer<EventFiltersListPageDat
|
|||
};
|
||||
};
|
||||
|
||||
const handleEventFiltersListPageDataExistChanges: CaseReducer<EventFiltersListPageDataExistsChanged> =
|
||||
(state, action) => {
|
||||
return {
|
||||
...state,
|
||||
listPage: {
|
||||
...state.listPage,
|
||||
dataExist: action.payload,
|
||||
},
|
||||
};
|
||||
const handleEventFiltersListPageDataExistChanges: CaseReducer<
|
||||
EventFiltersListPageDataExistsChanged
|
||||
> = (state, action) => {
|
||||
return {
|
||||
...state,
|
||||
listPage: {
|
||||
...state.listPage,
|
||||
dataExist: action.payload,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const eventFiltersInitForm: CaseReducer<EventFiltersInitForm> = (state, action) => {
|
||||
return {
|
||||
|
|
|
@ -93,8 +93,9 @@ export const getListFetchError: EventFiltersSelector<Immutable<ServerApiError> |
|
|||
return (isFailedResourceState(listPageDataState) && listPageDataState.error) || undefined;
|
||||
});
|
||||
|
||||
export const getListPageDataExistsState: EventFiltersSelector<StoreState['listPage']['dataExist']> =
|
||||
({ listPage: { dataExist } }) => dataExist;
|
||||
export const getListPageDataExistsState: EventFiltersSelector<
|
||||
StoreState['listPage']['dataExist']
|
||||
> = ({ listPage: { dataExist } }) => dataExist;
|
||||
|
||||
export const getListIsLoading: EventFiltersSelector<boolean> = createSelector(
|
||||
getCurrentListPageDataState,
|
||||
|
|
|
@ -24,18 +24,19 @@ const getStatValue = (el: reactTestingLibrary.RenderResult, stat: string) => {
|
|||
};
|
||||
|
||||
describe('Fleet event filters card', () => {
|
||||
const renderComponent: (stats: GetExceptionSummaryResponse) => reactTestingLibrary.RenderResult =
|
||||
(stats) => {
|
||||
const Wrapper: React.FC = ({ children }) => (
|
||||
<I18nProvider>
|
||||
<ThemeProvider theme={mockTheme}>{children}</ThemeProvider>
|
||||
</I18nProvider>
|
||||
);
|
||||
const component = reactTestingLibrary.render(<ExceptionItemsSummary stats={stats} />, {
|
||||
wrapper: Wrapper,
|
||||
});
|
||||
return component;
|
||||
};
|
||||
const renderComponent: (
|
||||
stats: GetExceptionSummaryResponse
|
||||
) => reactTestingLibrary.RenderResult = (stats) => {
|
||||
const Wrapper: React.FC = ({ children }) => (
|
||||
<I18nProvider>
|
||||
<ThemeProvider theme={mockTheme}>{children}</ThemeProvider>
|
||||
</I18nProvider>
|
||||
);
|
||||
const component = reactTestingLibrary.render(<ExceptionItemsSummary stats={stats} />, {
|
||||
wrapper: Wrapper,
|
||||
});
|
||||
return component;
|
||||
};
|
||||
it('should renders correctly', () => {
|
||||
const summary: GetExceptionSummaryResponse = {
|
||||
windows: 3,
|
||||
|
|
|
@ -474,5 +474,6 @@ export const createTrustedAppsPageMiddleware = (
|
|||
};
|
||||
};
|
||||
|
||||
export const trustedAppsPageMiddlewareFactory: ImmutableMiddlewareFactory<TrustedAppsListPageState> =
|
||||
(coreStart) => createTrustedAppsPageMiddleware(new TrustedAppsHttpService(coreStart.http));
|
||||
export const trustedAppsPageMiddlewareFactory: ImmutableMiddlewareFactory<
|
||||
TrustedAppsListPageState
|
||||
> = (coreStart) => createTrustedAppsPageMiddleware(new TrustedAppsHttpService(coreStart.http));
|
||||
|
|
|
@ -68,13 +68,14 @@ const trustedAppsListResourceStateChanged: CaseReducer<TrustedAppsListResourceSt
|
|||
return { ...state, listView: { ...state.listView, listResourceState: action.payload.newState } };
|
||||
};
|
||||
|
||||
const trustedAppDeletionSubmissionResourceStateChanged: CaseReducer<TrustedAppDeletionSubmissionResourceStateChanged> =
|
||||
(state, action) => {
|
||||
return {
|
||||
...state,
|
||||
deletionDialog: { ...state.deletionDialog, submissionResourceState: action.payload.newState },
|
||||
};
|
||||
const trustedAppDeletionSubmissionResourceStateChanged: CaseReducer<
|
||||
TrustedAppDeletionSubmissionResourceStateChanged
|
||||
> = (state, action) => {
|
||||
return {
|
||||
...state,
|
||||
deletionDialog: { ...state.deletionDialog, submissionResourceState: action.payload.newState },
|
||||
};
|
||||
};
|
||||
|
||||
const trustedAppDeletionDialogStarted: CaseReducer<TrustedAppDeletionDialogStarted> = (
|
||||
state,
|
||||
|
@ -93,13 +94,14 @@ const trustedAppDeletionDialogClosed: CaseReducer<TrustedAppDeletionDialogClosed
|
|||
return { ...state, deletionDialog: initialDeletionDialogState() };
|
||||
};
|
||||
|
||||
const trustedAppCreationSubmissionResourceStateChanged: CaseReducer<TrustedAppCreationSubmissionResourceStateChanged> =
|
||||
(state, action) => {
|
||||
return {
|
||||
...state,
|
||||
creationDialog: { ...state.creationDialog, submissionResourceState: action.payload.newState },
|
||||
};
|
||||
const trustedAppCreationSubmissionResourceStateChanged: CaseReducer<
|
||||
TrustedAppCreationSubmissionResourceStateChanged
|
||||
> = (state, action) => {
|
||||
return {
|
||||
...state,
|
||||
creationDialog: { ...state.creationDialog, submissionResourceState: action.payload.newState },
|
||||
};
|
||||
};
|
||||
|
||||
const trustedAppCreationDialogStarted: CaseReducer<TrustedAppCreationDialogStarted> = (
|
||||
state,
|
||||
|
@ -114,13 +116,14 @@ const trustedAppCreationDialogStarted: CaseReducer<TrustedAppCreationDialogStart
|
|||
};
|
||||
};
|
||||
|
||||
const trustedAppCreationDialogFormStateUpdated: CaseReducer<TrustedAppCreationDialogFormStateUpdated> =
|
||||
(state, action) => {
|
||||
return {
|
||||
...state,
|
||||
creationDialog: { ...state.creationDialog, formState: { ...action.payload } },
|
||||
};
|
||||
const trustedAppCreationDialogFormStateUpdated: CaseReducer<
|
||||
TrustedAppCreationDialogFormStateUpdated
|
||||
> = (state, action) => {
|
||||
return {
|
||||
...state,
|
||||
creationDialog: { ...state.creationDialog, formState: { ...action.payload } },
|
||||
};
|
||||
};
|
||||
|
||||
const handleUpdateToEditItemState: CaseReducer<TrustedAppCreationEditItemStateChanged> = (
|
||||
state,
|
||||
|
|
|
@ -590,22 +590,22 @@ const previewTransformHandler: RequestHandler<
|
|||
}
|
||||
};
|
||||
|
||||
const startTransformsHandler: RequestHandler<undefined, undefined, StartTransformsRequestSchema> =
|
||||
async (ctx, req, res) => {
|
||||
const transformsInfo = req.body;
|
||||
const startTransformsHandler: RequestHandler<
|
||||
undefined,
|
||||
undefined,
|
||||
StartTransformsRequestSchema
|
||||
> = async (ctx, req, res) => {
|
||||
const transformsInfo = req.body;
|
||||
|
||||
try {
|
||||
const body = await startTransforms(
|
||||
transformsInfo,
|
||||
ctx.core.elasticsearch.client.asCurrentUser
|
||||
);
|
||||
return res.ok({
|
||||
body,
|
||||
});
|
||||
} catch (e) {
|
||||
return res.customError(wrapError(wrapEsError(e)));
|
||||
}
|
||||
};
|
||||
try {
|
||||
const body = await startTransforms(transformsInfo, ctx.core.elasticsearch.client.asCurrentUser);
|
||||
return res.ok({
|
||||
body,
|
||||
});
|
||||
} catch (e) {
|
||||
return res.customError(wrapError(wrapEsError(e)));
|
||||
}
|
||||
};
|
||||
|
||||
async function startTransforms(
|
||||
transformsInfo: StartTransformsRequestSchema,
|
||||
|
@ -635,18 +635,21 @@ async function startTransforms(
|
|||
return results;
|
||||
}
|
||||
|
||||
const stopTransformsHandler: RequestHandler<undefined, undefined, StopTransformsRequestSchema> =
|
||||
async (ctx, req, res) => {
|
||||
const transformsInfo = req.body;
|
||||
const stopTransformsHandler: RequestHandler<
|
||||
undefined,
|
||||
undefined,
|
||||
StopTransformsRequestSchema
|
||||
> = async (ctx, req, res) => {
|
||||
const transformsInfo = req.body;
|
||||
|
||||
try {
|
||||
return res.ok({
|
||||
body: await stopTransforms(transformsInfo, ctx.core.elasticsearch.client.asCurrentUser),
|
||||
});
|
||||
} catch (e) {
|
||||
return res.customError(wrapError(wrapEsError(e)));
|
||||
}
|
||||
};
|
||||
try {
|
||||
return res.ok({
|
||||
body: await stopTransforms(transformsInfo, ctx.core.elasticsearch.client.asCurrentUser),
|
||||
});
|
||||
} catch (e) {
|
||||
return res.customError(wrapError(wrapEsError(e)));
|
||||
}
|
||||
};
|
||||
|
||||
async function stopTransforms(
|
||||
transformsInfo: StopTransformsRequestSchema,
|
||||
|
|
|
@ -29,157 +29,158 @@ export { ServiceNowConnectorFields as default };
|
|||
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
|
||||
import { snExternalServiceConfig } from '../../../../../../actions/server/builtin_action_types/servicenow/config';
|
||||
|
||||
const ServiceNowConnectorFields: React.FC<ActionConnectorFieldsProps<ServiceNowActionConnector>> =
|
||||
({
|
||||
action,
|
||||
editActionSecrets,
|
||||
const ServiceNowConnectorFields: React.FC<
|
||||
ActionConnectorFieldsProps<ServiceNowActionConnector>
|
||||
> = ({
|
||||
action,
|
||||
editActionSecrets,
|
||||
editActionConfig,
|
||||
errors,
|
||||
consumer,
|
||||
readOnly,
|
||||
setCallbacks,
|
||||
isEdit,
|
||||
}) => {
|
||||
const {
|
||||
http,
|
||||
notifications: { toasts },
|
||||
} = useKibana().services;
|
||||
const { apiUrl, usesTableApi } = action.config;
|
||||
const { username, password } = action.secrets;
|
||||
const requiresNewApplication = !checkConnectorIsDeprecated(action);
|
||||
|
||||
const [showUpdateConnector, setShowUpdateConnector] = useState(false);
|
||||
|
||||
const { fetchAppInfo, isLoading } = useGetAppInfo({
|
||||
actionTypeId: action.actionTypeId,
|
||||
});
|
||||
|
||||
const [showApplicationRequiredCallout, setShowApplicationRequiredCallout] =
|
||||
useState<boolean>(false);
|
||||
const [applicationInfoErrorMsg, setApplicationInfoErrorMsg] = useState<string | null>(null);
|
||||
|
||||
const getApplicationInfo = useCallback(async () => {
|
||||
setShowApplicationRequiredCallout(false);
|
||||
setApplicationInfoErrorMsg(null);
|
||||
|
||||
try {
|
||||
const res = await fetchAppInfo(action);
|
||||
if (isRESTApiError(res)) {
|
||||
throw new Error(res.error?.message ?? i18n.UNKNOWN);
|
||||
}
|
||||
|
||||
return res;
|
||||
} catch (e) {
|
||||
setShowApplicationRequiredCallout(true);
|
||||
setApplicationInfoErrorMsg(e.message);
|
||||
// We need to throw here so the connector will be not be saved.
|
||||
throw e;
|
||||
}
|
||||
}, [action, fetchAppInfo]);
|
||||
|
||||
const beforeActionConnectorSave = useCallback(async () => {
|
||||
if (requiresNewApplication) {
|
||||
await getApplicationInfo();
|
||||
}
|
||||
}, [getApplicationInfo, requiresNewApplication]);
|
||||
|
||||
useEffect(
|
||||
() => setCallbacks({ beforeActionConnectorSave }),
|
||||
[beforeActionConnectorSave, setCallbacks]
|
||||
);
|
||||
|
||||
const onMigrateClick = useCallback(() => setShowUpdateConnector(true), []);
|
||||
const onModalCancel = useCallback(() => setShowUpdateConnector(false), []);
|
||||
|
||||
const onUpdateConnectorConfirm = useCallback(async () => {
|
||||
try {
|
||||
await getApplicationInfo();
|
||||
|
||||
await updateActionConnector({
|
||||
http,
|
||||
connector: {
|
||||
name: action.name,
|
||||
config: { apiUrl, usesTableApi: false },
|
||||
secrets: { username, password },
|
||||
},
|
||||
id: action.id,
|
||||
});
|
||||
|
||||
editActionConfig('usesTableApi', false);
|
||||
setShowUpdateConnector(false);
|
||||
|
||||
toasts.addSuccess({
|
||||
title: i18n.UPDATE_SUCCESS_TOAST_TITLE(action.name),
|
||||
text: i18n.UPDATE_SUCCESS_TOAST_TEXT,
|
||||
});
|
||||
} catch (err) {
|
||||
/**
|
||||
* getApplicationInfo may throw an error if the request
|
||||
* fails or if there is a REST api error.
|
||||
*
|
||||
* We silent the errors as a callout will show and inform the user
|
||||
*/
|
||||
}
|
||||
}, [
|
||||
getApplicationInfo,
|
||||
http,
|
||||
action.name,
|
||||
action.id,
|
||||
apiUrl,
|
||||
username,
|
||||
password,
|
||||
editActionConfig,
|
||||
errors,
|
||||
consumer,
|
||||
readOnly,
|
||||
setCallbacks,
|
||||
isEdit,
|
||||
}) => {
|
||||
const {
|
||||
http,
|
||||
notifications: { toasts },
|
||||
} = useKibana().services;
|
||||
const { apiUrl, usesTableApi } = action.config;
|
||||
const { username, password } = action.secrets;
|
||||
const requiresNewApplication = !checkConnectorIsDeprecated(action);
|
||||
toasts,
|
||||
]);
|
||||
|
||||
const [showUpdateConnector, setShowUpdateConnector] = useState(false);
|
||||
/**
|
||||
* Defaults the usesTableApi attribute to false
|
||||
* if it is not defined. The usesTableApi attribute
|
||||
* will be undefined only at the creation of
|
||||
* the connector.
|
||||
*/
|
||||
useEffect(() => {
|
||||
if (usesTableApi == null) {
|
||||
editActionConfig('usesTableApi', false);
|
||||
}
|
||||
});
|
||||
|
||||
const { fetchAppInfo, isLoading } = useGetAppInfo({
|
||||
actionTypeId: action.actionTypeId,
|
||||
});
|
||||
|
||||
const [showApplicationRequiredCallout, setShowApplicationRequiredCallout] =
|
||||
useState<boolean>(false);
|
||||
const [applicationInfoErrorMsg, setApplicationInfoErrorMsg] = useState<string | null>(null);
|
||||
|
||||
const getApplicationInfo = useCallback(async () => {
|
||||
setShowApplicationRequiredCallout(false);
|
||||
setApplicationInfoErrorMsg(null);
|
||||
|
||||
try {
|
||||
const res = await fetchAppInfo(action);
|
||||
if (isRESTApiError(res)) {
|
||||
throw new Error(res.error?.message ?? i18n.UNKNOWN);
|
||||
}
|
||||
|
||||
return res;
|
||||
} catch (e) {
|
||||
setShowApplicationRequiredCallout(true);
|
||||
setApplicationInfoErrorMsg(e.message);
|
||||
// We need to throw here so the connector will be not be saved.
|
||||
throw e;
|
||||
}
|
||||
}, [action, fetchAppInfo]);
|
||||
|
||||
const beforeActionConnectorSave = useCallback(async () => {
|
||||
if (requiresNewApplication) {
|
||||
await getApplicationInfo();
|
||||
}
|
||||
}, [getApplicationInfo, requiresNewApplication]);
|
||||
|
||||
useEffect(
|
||||
() => setCallbacks({ beforeActionConnectorSave }),
|
||||
[beforeActionConnectorSave, setCallbacks]
|
||||
);
|
||||
|
||||
const onMigrateClick = useCallback(() => setShowUpdateConnector(true), []);
|
||||
const onModalCancel = useCallback(() => setShowUpdateConnector(false), []);
|
||||
|
||||
const onUpdateConnectorConfirm = useCallback(async () => {
|
||||
try {
|
||||
await getApplicationInfo();
|
||||
|
||||
await updateActionConnector({
|
||||
http,
|
||||
connector: {
|
||||
name: action.name,
|
||||
config: { apiUrl, usesTableApi: false },
|
||||
secrets: { username, password },
|
||||
},
|
||||
id: action.id,
|
||||
});
|
||||
|
||||
editActionConfig('usesTableApi', false);
|
||||
setShowUpdateConnector(false);
|
||||
|
||||
toasts.addSuccess({
|
||||
title: i18n.UPDATE_SUCCESS_TOAST_TITLE(action.name),
|
||||
text: i18n.UPDATE_SUCCESS_TOAST_TEXT,
|
||||
});
|
||||
} catch (err) {
|
||||
/**
|
||||
* getApplicationInfo may throw an error if the request
|
||||
* fails or if there is a REST api error.
|
||||
*
|
||||
* We silent the errors as a callout will show and inform the user
|
||||
*/
|
||||
}
|
||||
}, [
|
||||
getApplicationInfo,
|
||||
http,
|
||||
action.name,
|
||||
action.id,
|
||||
apiUrl,
|
||||
username,
|
||||
password,
|
||||
editActionConfig,
|
||||
toasts,
|
||||
]);
|
||||
|
||||
/**
|
||||
* Defaults the usesTableApi attribute to false
|
||||
* if it is not defined. The usesTableApi attribute
|
||||
* will be undefined only at the creation of
|
||||
* the connector.
|
||||
*/
|
||||
useEffect(() => {
|
||||
if (usesTableApi == null) {
|
||||
editActionConfig('usesTableApi', false);
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
{showUpdateConnector && (
|
||||
<UpdateConnector
|
||||
action={action}
|
||||
applicationInfoErrorMsg={applicationInfoErrorMsg}
|
||||
errors={errors}
|
||||
readOnly={readOnly}
|
||||
isLoading={isLoading}
|
||||
editActionSecrets={editActionSecrets}
|
||||
editActionConfig={editActionConfig}
|
||||
onConfirm={onUpdateConnectorConfirm}
|
||||
onCancel={onModalCancel}
|
||||
/>
|
||||
)}
|
||||
{requiresNewApplication && (
|
||||
<InstallationCallout appId={snExternalServiceConfig[action.actionTypeId].appId ?? ''} />
|
||||
)}
|
||||
{!requiresNewApplication && <SpacedDeprecatedCallout onMigrate={onMigrateClick} />}
|
||||
<Credentials
|
||||
return (
|
||||
<>
|
||||
{showUpdateConnector && (
|
||||
<UpdateConnector
|
||||
action={action}
|
||||
applicationInfoErrorMsg={applicationInfoErrorMsg}
|
||||
errors={errors}
|
||||
readOnly={readOnly}
|
||||
isLoading={isLoading}
|
||||
editActionSecrets={editActionSecrets}
|
||||
editActionConfig={editActionConfig}
|
||||
onConfirm={onUpdateConnectorConfirm}
|
||||
onCancel={onModalCancel}
|
||||
/>
|
||||
{showApplicationRequiredCallout && requiresNewApplication && (
|
||||
<ApplicationRequiredCallout
|
||||
message={applicationInfoErrorMsg}
|
||||
appId={snExternalServiceConfig[action.actionTypeId].appId ?? ''}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
)}
|
||||
{requiresNewApplication && (
|
||||
<InstallationCallout appId={snExternalServiceConfig[action.actionTypeId].appId ?? ''} />
|
||||
)}
|
||||
{!requiresNewApplication && <SpacedDeprecatedCallout onMigrate={onMigrateClick} />}
|
||||
<Credentials
|
||||
action={action}
|
||||
errors={errors}
|
||||
readOnly={readOnly}
|
||||
isLoading={isLoading}
|
||||
editActionSecrets={editActionSecrets}
|
||||
editActionConfig={editActionConfig}
|
||||
/>
|
||||
{showApplicationRequiredCallout && requiresNewApplication && (
|
||||
<ApplicationRequiredCallout
|
||||
message={applicationInfoErrorMsg}
|
||||
appId={snExternalServiceConfig[action.actionTypeId].appId ?? ''}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const SpacedDeprecatedCallout = ({ onMigrate }: { onMigrate: () => void }) => (
|
||||
<>
|
||||
|
|
|
@ -14,66 +14,67 @@ import { SlackActionConnector } from '../types';
|
|||
import { useKibana } from '../../../../common/lib/kibana';
|
||||
import { getEncryptedFieldNotifyLabel } from '../../get_encrypted_field_notify_label';
|
||||
|
||||
const SlackActionFields: React.FunctionComponent<ActionConnectorFieldsProps<SlackActionConnector>> =
|
||||
({ action, editActionSecrets, errors, readOnly }) => {
|
||||
const { docLinks } = useKibana().services;
|
||||
const { webhookUrl } = action.secrets;
|
||||
const isWebhookUrlInvalid: boolean =
|
||||
errors.webhookUrl !== undefined && errors.webhookUrl.length > 0 && webhookUrl !== undefined;
|
||||
const SlackActionFields: React.FunctionComponent<
|
||||
ActionConnectorFieldsProps<SlackActionConnector>
|
||||
> = ({ action, editActionSecrets, errors, readOnly }) => {
|
||||
const { docLinks } = useKibana().services;
|
||||
const { webhookUrl } = action.secrets;
|
||||
const isWebhookUrlInvalid: boolean =
|
||||
errors.webhookUrl !== undefined && errors.webhookUrl.length > 0 && webhookUrl !== undefined;
|
||||
|
||||
return (
|
||||
<>
|
||||
<EuiFormRow
|
||||
id="webhookUrl"
|
||||
fullWidth
|
||||
helpText={
|
||||
<EuiLink href={docLinks.links.alerting.slackAction} target="_blank">
|
||||
<FormattedMessage
|
||||
id="xpack.triggersActionsUI.components.builtinActionTypes.slackAction.webhookUrlHelpLabel"
|
||||
defaultMessage="Create a Slack Webhook URL"
|
||||
/>
|
||||
</EuiLink>
|
||||
}
|
||||
error={errors.webhookUrl}
|
||||
isInvalid={isWebhookUrlInvalid}
|
||||
label={i18n.translate(
|
||||
'xpack.triggersActionsUI.components.builtinActionTypes.slackAction.webhookUrlTextFieldLabel',
|
||||
{
|
||||
defaultMessage: 'Webhook URL',
|
||||
}
|
||||
)}
|
||||
>
|
||||
<>
|
||||
{getEncryptedFieldNotifyLabel(
|
||||
!action.id,
|
||||
1,
|
||||
action.isMissingSecrets ?? false,
|
||||
i18n.translate(
|
||||
'xpack.triggersActionsUI.components.builtinActionTypes.slackAction.reenterValueLabel',
|
||||
{ defaultMessage: 'This URL is encrypted. Please reenter a value for this field.' }
|
||||
)
|
||||
)}
|
||||
<EuiFieldText
|
||||
fullWidth
|
||||
isInvalid={isWebhookUrlInvalid}
|
||||
name="webhookUrl"
|
||||
readOnly={readOnly}
|
||||
value={webhookUrl || ''}
|
||||
data-test-subj="slackWebhookUrlInput"
|
||||
onChange={(e) => {
|
||||
editActionSecrets('webhookUrl', e.target.value);
|
||||
}}
|
||||
onBlur={() => {
|
||||
if (!webhookUrl) {
|
||||
editActionSecrets('webhookUrl', '');
|
||||
}
|
||||
}}
|
||||
return (
|
||||
<>
|
||||
<EuiFormRow
|
||||
id="webhookUrl"
|
||||
fullWidth
|
||||
helpText={
|
||||
<EuiLink href={docLinks.links.alerting.slackAction} target="_blank">
|
||||
<FormattedMessage
|
||||
id="xpack.triggersActionsUI.components.builtinActionTypes.slackAction.webhookUrlHelpLabel"
|
||||
defaultMessage="Create a Slack Webhook URL"
|
||||
/>
|
||||
</>
|
||||
</EuiFormRow>
|
||||
</>
|
||||
);
|
||||
};
|
||||
</EuiLink>
|
||||
}
|
||||
error={errors.webhookUrl}
|
||||
isInvalid={isWebhookUrlInvalid}
|
||||
label={i18n.translate(
|
||||
'xpack.triggersActionsUI.components.builtinActionTypes.slackAction.webhookUrlTextFieldLabel',
|
||||
{
|
||||
defaultMessage: 'Webhook URL',
|
||||
}
|
||||
)}
|
||||
>
|
||||
<>
|
||||
{getEncryptedFieldNotifyLabel(
|
||||
!action.id,
|
||||
1,
|
||||
action.isMissingSecrets ?? false,
|
||||
i18n.translate(
|
||||
'xpack.triggersActionsUI.components.builtinActionTypes.slackAction.reenterValueLabel',
|
||||
{ defaultMessage: 'This URL is encrypted. Please reenter a value for this field.' }
|
||||
)
|
||||
)}
|
||||
<EuiFieldText
|
||||
fullWidth
|
||||
isInvalid={isWebhookUrlInvalid}
|
||||
name="webhookUrl"
|
||||
readOnly={readOnly}
|
||||
value={webhookUrl || ''}
|
||||
data-test-subj="slackWebhookUrlInput"
|
||||
onChange={(e) => {
|
||||
editActionSecrets('webhookUrl', e.target.value);
|
||||
}}
|
||||
onBlur={() => {
|
||||
if (!webhookUrl) {
|
||||
editActionSecrets('webhookUrl', '');
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
</EuiFormRow>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export { SlackActionFields as default };
|
||||
|
|
|
@ -14,67 +14,68 @@ import { TeamsActionConnector } from '../types';
|
|||
import { useKibana } from '../../../../common/lib/kibana';
|
||||
import { getEncryptedFieldNotifyLabel } from '../../get_encrypted_field_notify_label';
|
||||
|
||||
const TeamsActionFields: React.FunctionComponent<ActionConnectorFieldsProps<TeamsActionConnector>> =
|
||||
({ action, editActionSecrets, errors, readOnly }) => {
|
||||
const { webhookUrl } = action.secrets;
|
||||
const { docLinks } = useKibana().services;
|
||||
const TeamsActionFields: React.FunctionComponent<
|
||||
ActionConnectorFieldsProps<TeamsActionConnector>
|
||||
> = ({ action, editActionSecrets, errors, readOnly }) => {
|
||||
const { webhookUrl } = action.secrets;
|
||||
const { docLinks } = useKibana().services;
|
||||
|
||||
const isWebhookUrlInvalid: boolean =
|
||||
errors.webhookUrl !== undefined && errors.webhookUrl.length > 0 && webhookUrl !== undefined;
|
||||
const isWebhookUrlInvalid: boolean =
|
||||
errors.webhookUrl !== undefined && errors.webhookUrl.length > 0 && webhookUrl !== undefined;
|
||||
|
||||
return (
|
||||
<>
|
||||
<EuiFormRow
|
||||
id="webhookUrl"
|
||||
fullWidth
|
||||
helpText={
|
||||
<EuiLink href={docLinks.links.alerting.teamsAction} target="_blank">
|
||||
<FormattedMessage
|
||||
id="xpack.triggersActionsUI.components.builtinActionTypes.teamsAction.webhookUrlHelpLabel"
|
||||
defaultMessage="Create a Microsoft Teams Webhook URL"
|
||||
/>
|
||||
</EuiLink>
|
||||
}
|
||||
error={errors.webhookUrl}
|
||||
isInvalid={isWebhookUrlInvalid}
|
||||
label={i18n.translate(
|
||||
'xpack.triggersActionsUI.components.builtinActionTypes.teamsAction.webhookUrlTextFieldLabel',
|
||||
{
|
||||
defaultMessage: 'Webhook URL',
|
||||
}
|
||||
)}
|
||||
>
|
||||
<>
|
||||
{getEncryptedFieldNotifyLabel(
|
||||
!action.id,
|
||||
1,
|
||||
action.isMissingSecrets ?? false,
|
||||
i18n.translate(
|
||||
'xpack.triggersActionsUI.components.builtinActionTypes.teamsAction.reenterValueLabel',
|
||||
{ defaultMessage: 'This URL is encrypted. Please reenter a value for this field.' }
|
||||
)
|
||||
)}
|
||||
<EuiFieldText
|
||||
fullWidth
|
||||
isInvalid={isWebhookUrlInvalid}
|
||||
name="webhookUrl"
|
||||
readOnly={readOnly}
|
||||
value={webhookUrl || ''}
|
||||
data-test-subj="teamsWebhookUrlInput"
|
||||
onChange={(e) => {
|
||||
editActionSecrets('webhookUrl', e.target.value);
|
||||
}}
|
||||
onBlur={() => {
|
||||
if (!webhookUrl) {
|
||||
editActionSecrets('webhookUrl', '');
|
||||
}
|
||||
}}
|
||||
return (
|
||||
<>
|
||||
<EuiFormRow
|
||||
id="webhookUrl"
|
||||
fullWidth
|
||||
helpText={
|
||||
<EuiLink href={docLinks.links.alerting.teamsAction} target="_blank">
|
||||
<FormattedMessage
|
||||
id="xpack.triggersActionsUI.components.builtinActionTypes.teamsAction.webhookUrlHelpLabel"
|
||||
defaultMessage="Create a Microsoft Teams Webhook URL"
|
||||
/>
|
||||
</>
|
||||
</EuiFormRow>
|
||||
</>
|
||||
);
|
||||
};
|
||||
</EuiLink>
|
||||
}
|
||||
error={errors.webhookUrl}
|
||||
isInvalid={isWebhookUrlInvalid}
|
||||
label={i18n.translate(
|
||||
'xpack.triggersActionsUI.components.builtinActionTypes.teamsAction.webhookUrlTextFieldLabel',
|
||||
{
|
||||
defaultMessage: 'Webhook URL',
|
||||
}
|
||||
)}
|
||||
>
|
||||
<>
|
||||
{getEncryptedFieldNotifyLabel(
|
||||
!action.id,
|
||||
1,
|
||||
action.isMissingSecrets ?? false,
|
||||
i18n.translate(
|
||||
'xpack.triggersActionsUI.components.builtinActionTypes.teamsAction.reenterValueLabel',
|
||||
{ defaultMessage: 'This URL is encrypted. Please reenter a value for this field.' }
|
||||
)
|
||||
)}
|
||||
<EuiFieldText
|
||||
fullWidth
|
||||
isInvalid={isWebhookUrlInvalid}
|
||||
name="webhookUrl"
|
||||
readOnly={readOnly}
|
||||
value={webhookUrl || ''}
|
||||
data-test-subj="teamsWebhookUrlInput"
|
||||
onChange={(e) => {
|
||||
editActionSecrets('webhookUrl', e.target.value);
|
||||
}}
|
||||
onBlur={() => {
|
||||
if (!webhookUrl) {
|
||||
editActionSecrets('webhookUrl', '');
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
</EuiFormRow>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export { TeamsActionFields as default };
|
||||
|
|
|
@ -14,47 +14,49 @@ export interface GetJourneyStepsParams {
|
|||
checkGroups: string[];
|
||||
}
|
||||
|
||||
export const getJourneyFailedSteps: UMElasticsearchQueryFn<GetJourneyStepsParams, JourneyStep[]> =
|
||||
async ({ uptimeEsClient, checkGroups }) => {
|
||||
const params = {
|
||||
query: {
|
||||
bool: {
|
||||
filter: [
|
||||
{
|
||||
terms: {
|
||||
'synthetics.type': ['step/end'],
|
||||
},
|
||||
export const getJourneyFailedSteps: UMElasticsearchQueryFn<
|
||||
GetJourneyStepsParams,
|
||||
JourneyStep[]
|
||||
> = async ({ uptimeEsClient, checkGroups }) => {
|
||||
const params = {
|
||||
query: {
|
||||
bool: {
|
||||
filter: [
|
||||
{
|
||||
terms: {
|
||||
'synthetics.type': ['step/end'],
|
||||
},
|
||||
{
|
||||
exists: {
|
||||
field: 'synthetics.error',
|
||||
},
|
||||
},
|
||||
{
|
||||
exists: {
|
||||
field: 'synthetics.error',
|
||||
},
|
||||
{
|
||||
terms: {
|
||||
'monitor.check_group': checkGroups,
|
||||
},
|
||||
},
|
||||
{
|
||||
terms: {
|
||||
'monitor.check_group': checkGroups,
|
||||
},
|
||||
] as QueryDslQueryContainer[],
|
||||
},
|
||||
},
|
||||
] as QueryDslQueryContainer[],
|
||||
},
|
||||
sort: asMutableArray([
|
||||
{ 'synthetics.step.index': { order: 'asc' } },
|
||||
{ '@timestamp': { order: 'asc' } },
|
||||
] as const),
|
||||
_source: {
|
||||
excludes: ['synthetics.blob'],
|
||||
},
|
||||
size: 500,
|
||||
};
|
||||
|
||||
const { body: result } = await uptimeEsClient.search({ body: params });
|
||||
|
||||
return result.hits.hits.map(({ _id, _source }) => {
|
||||
const step = Object.assign({ _id }, _source) as JourneyStep;
|
||||
return {
|
||||
...step,
|
||||
timestamp: step['@timestamp'],
|
||||
};
|
||||
});
|
||||
},
|
||||
sort: asMutableArray([
|
||||
{ 'synthetics.step.index': { order: 'asc' } },
|
||||
{ '@timestamp': { order: 'asc' } },
|
||||
] as const),
|
||||
_source: {
|
||||
excludes: ['synthetics.blob'],
|
||||
},
|
||||
size: 500,
|
||||
};
|
||||
|
||||
const { body: result } = await uptimeEsClient.search({ body: params });
|
||||
|
||||
return result.hits.hits.map(({ _id, _source }) => {
|
||||
const step = Object.assign({ _id }, _source) as JourneyStep;
|
||||
return {
|
||||
...step,
|
||||
timestamp: step['@timestamp'],
|
||||
};
|
||||
});
|
||||
};
|
||||
|
|
|
@ -33,67 +33,69 @@ export const formatSyntheticEvents = (eventTypes?: string | string[]) => {
|
|||
|
||||
type ResultType = JourneyStep & { '@timestamp': string };
|
||||
|
||||
export const getJourneySteps: UMElasticsearchQueryFn<GetJourneyStepsParams, JourneyStep[]> =
|
||||
async ({ uptimeEsClient, checkGroup, syntheticEventTypes }) => {
|
||||
const params = {
|
||||
query: {
|
||||
bool: {
|
||||
filter: [
|
||||
{
|
||||
terms: {
|
||||
'synthetics.type': formatSyntheticEvents(syntheticEventTypes),
|
||||
},
|
||||
export const getJourneySteps: UMElasticsearchQueryFn<
|
||||
GetJourneyStepsParams,
|
||||
JourneyStep[]
|
||||
> = async ({ uptimeEsClient, checkGroup, syntheticEventTypes }) => {
|
||||
const params = {
|
||||
query: {
|
||||
bool: {
|
||||
filter: [
|
||||
{
|
||||
terms: {
|
||||
'synthetics.type': formatSyntheticEvents(syntheticEventTypes),
|
||||
},
|
||||
{
|
||||
term: {
|
||||
'monitor.check_group': checkGroup,
|
||||
},
|
||||
},
|
||||
{
|
||||
term: {
|
||||
'monitor.check_group': checkGroup,
|
||||
},
|
||||
] as QueryDslQueryContainer,
|
||||
},
|
||||
},
|
||||
] as QueryDslQueryContainer,
|
||||
},
|
||||
sort: asMutableArray([
|
||||
{ 'synthetics.step.index': { order: 'asc' } },
|
||||
{ '@timestamp': { order: 'asc' } },
|
||||
] as const),
|
||||
_source: {
|
||||
excludes: ['synthetics.blob', 'screenshot_ref'],
|
||||
},
|
||||
size: 500,
|
||||
};
|
||||
const { body: result } = await uptimeEsClient.search({ body: params });
|
||||
|
||||
const steps = result.hits.hits.map(
|
||||
({ _id, _source }) => Object.assign({ _id }, _source) as ResultType
|
||||
);
|
||||
|
||||
const screenshotIndexList: number[] = [];
|
||||
const refIndexList: number[] = [];
|
||||
const stepsWithoutImages: ResultType[] = [];
|
||||
|
||||
/**
|
||||
* Store screenshot indexes, we use these to determine if a step has a screenshot below.
|
||||
* Store steps that are not screenshots, we return these to the client.
|
||||
*/
|
||||
for (const step of steps) {
|
||||
const { synthetics } = step;
|
||||
if (synthetics.type === 'step/screenshot' && synthetics?.step?.index) {
|
||||
screenshotIndexList.push(synthetics.step.index);
|
||||
} else if (synthetics.type === 'step/screenshot_ref' && synthetics?.step?.index) {
|
||||
refIndexList.push(synthetics.step.index);
|
||||
} else {
|
||||
stepsWithoutImages.push(step);
|
||||
}
|
||||
}
|
||||
|
||||
return stepsWithoutImages.map(({ _id, ...rest }) => ({
|
||||
_id,
|
||||
...rest,
|
||||
timestamp: rest['@timestamp'],
|
||||
synthetics: {
|
||||
...rest.synthetics,
|
||||
isFullScreenshot: screenshotIndexList.some((i) => i === rest?.synthetics?.step?.index),
|
||||
isScreenshotRef: refIndexList.some((i) => i === rest?.synthetics?.step?.index),
|
||||
},
|
||||
}));
|
||||
},
|
||||
sort: asMutableArray([
|
||||
{ 'synthetics.step.index': { order: 'asc' } },
|
||||
{ '@timestamp': { order: 'asc' } },
|
||||
] as const),
|
||||
_source: {
|
||||
excludes: ['synthetics.blob', 'screenshot_ref'],
|
||||
},
|
||||
size: 500,
|
||||
};
|
||||
const { body: result } = await uptimeEsClient.search({ body: params });
|
||||
|
||||
const steps = result.hits.hits.map(
|
||||
({ _id, _source }) => Object.assign({ _id }, _source) as ResultType
|
||||
);
|
||||
|
||||
const screenshotIndexList: number[] = [];
|
||||
const refIndexList: number[] = [];
|
||||
const stepsWithoutImages: ResultType[] = [];
|
||||
|
||||
/**
|
||||
* Store screenshot indexes, we use these to determine if a step has a screenshot below.
|
||||
* Store steps that are not screenshots, we return these to the client.
|
||||
*/
|
||||
for (const step of steps) {
|
||||
const { synthetics } = step;
|
||||
if (synthetics.type === 'step/screenshot' && synthetics?.step?.index) {
|
||||
screenshotIndexList.push(synthetics.step.index);
|
||||
} else if (synthetics.type === 'step/screenshot_ref' && synthetics?.step?.index) {
|
||||
refIndexList.push(synthetics.step.index);
|
||||
} else {
|
||||
stepsWithoutImages.push(step);
|
||||
}
|
||||
}
|
||||
|
||||
return stepsWithoutImages.map(({ _id, ...rest }) => ({
|
||||
_id,
|
||||
...rest,
|
||||
timestamp: rest['@timestamp'],
|
||||
synthetics: {
|
||||
...rest.synthetics,
|
||||
isFullScreenshot: screenshotIndexList.some((i) => i === rest?.synthetics?.step?.index),
|
||||
isScreenshotRef: refIndexList.some((i) => i === rest?.synthetics?.step?.index),
|
||||
},
|
||||
}));
|
||||
};
|
||||
|
|
|
@ -80,25 +80,27 @@ export const getLastSuccessfulStepParams = ({
|
|||
};
|
||||
};
|
||||
|
||||
export const getLastSuccessfulCheck: UMElasticsearchQueryFn<GetStepScreenshotParams, Ping | null> =
|
||||
async ({ uptimeEsClient, monitorId, timestamp, location }) => {
|
||||
const lastSuccessCheckParams = getLastSuccessfulStepParams({
|
||||
monitorId,
|
||||
timestamp,
|
||||
location,
|
||||
});
|
||||
export const getLastSuccessfulCheck: UMElasticsearchQueryFn<
|
||||
GetStepScreenshotParams,
|
||||
Ping | null
|
||||
> = async ({ uptimeEsClient, monitorId, timestamp, location }) => {
|
||||
const lastSuccessCheckParams = getLastSuccessfulStepParams({
|
||||
monitorId,
|
||||
timestamp,
|
||||
location,
|
||||
});
|
||||
|
||||
const { body: result } = await uptimeEsClient.search({ body: lastSuccessCheckParams });
|
||||
const { body: result } = await uptimeEsClient.search({ body: lastSuccessCheckParams });
|
||||
|
||||
if (result.hits.total.value < 1) {
|
||||
return null;
|
||||
}
|
||||
if (result.hits.total.value < 1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const check = result.hits.hits[0]._source as Ping & { '@timestamp': string };
|
||||
const check = result.hits.hits[0]._source as Ping & { '@timestamp': string };
|
||||
|
||||
return {
|
||||
...check,
|
||||
timestamp: check['@timestamp'],
|
||||
docId: result.hits.hits[0]._id,
|
||||
};
|
||||
return {
|
||||
...check,
|
||||
timestamp: check['@timestamp'],
|
||||
docId: result.hits.hits[0]._id,
|
||||
};
|
||||
};
|
||||
|
|
|
@ -93,63 +93,65 @@ export const getMonitorAlerts = async ({
|
|||
return monitorAlerts;
|
||||
};
|
||||
|
||||
export const getMonitorDetails: UMElasticsearchQueryFn<GetMonitorDetailsParams, MonitorDetails> =
|
||||
async ({ uptimeEsClient, monitorId, dateStart, dateEnd, rulesClient }) => {
|
||||
const queryFilters: any = [
|
||||
{
|
||||
range: {
|
||||
'@timestamp': {
|
||||
gte: dateStart,
|
||||
lte: dateEnd,
|
||||
},
|
||||
export const getMonitorDetails: UMElasticsearchQueryFn<
|
||||
GetMonitorDetailsParams,
|
||||
MonitorDetails
|
||||
> = async ({ uptimeEsClient, monitorId, dateStart, dateEnd, rulesClient }) => {
|
||||
const queryFilters: any = [
|
||||
{
|
||||
range: {
|
||||
'@timestamp': {
|
||||
gte: dateStart,
|
||||
lte: dateEnd,
|
||||
},
|
||||
},
|
||||
{
|
||||
term: {
|
||||
'monitor.id': monitorId,
|
||||
},
|
||||
},
|
||||
{
|
||||
term: {
|
||||
'monitor.id': monitorId,
|
||||
},
|
||||
];
|
||||
},
|
||||
];
|
||||
|
||||
const params = {
|
||||
size: 1,
|
||||
_source: ['error', '@timestamp'],
|
||||
query: {
|
||||
bool: {
|
||||
must: [
|
||||
{
|
||||
exists: {
|
||||
field: 'error',
|
||||
},
|
||||
const params = {
|
||||
size: 1,
|
||||
_source: ['error', '@timestamp'],
|
||||
query: {
|
||||
bool: {
|
||||
must: [
|
||||
{
|
||||
exists: {
|
||||
field: 'error',
|
||||
},
|
||||
],
|
||||
filter: queryFilters,
|
||||
},
|
||||
],
|
||||
filter: queryFilters,
|
||||
},
|
||||
},
|
||||
sort: [
|
||||
{
|
||||
'@timestamp': {
|
||||
order: 'desc' as const,
|
||||
},
|
||||
},
|
||||
sort: [
|
||||
{
|
||||
'@timestamp': {
|
||||
order: 'desc' as const,
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const { body: result } = await uptimeEsClient.search({ body: params }, 'getMonitorDetails');
|
||||
|
||||
const data = result.hits.hits[0]?._source as Ping & { '@timestamp': string };
|
||||
|
||||
const errorTimestamp: string | undefined = data?.['@timestamp'];
|
||||
const monAlerts = await getMonitorAlerts({
|
||||
uptimeEsClient,
|
||||
rulesClient,
|
||||
monitorId,
|
||||
});
|
||||
|
||||
return {
|
||||
monitorId,
|
||||
error: data?.error,
|
||||
timestamp: errorTimestamp,
|
||||
alerts: monAlerts,
|
||||
};
|
||||
],
|
||||
};
|
||||
|
||||
const { body: result } = await uptimeEsClient.search({ body: params }, 'getMonitorDetails');
|
||||
|
||||
const data = result.hits.hits[0]?._source as Ping & { '@timestamp': string };
|
||||
|
||||
const errorTimestamp: string | undefined = data?.['@timestamp'];
|
||||
const monAlerts = await getMonitorAlerts({
|
||||
uptimeEsClient,
|
||||
rulesClient,
|
||||
monitorId,
|
||||
});
|
||||
|
||||
return {
|
||||
monitorId,
|
||||
error: data?.error,
|
||||
timestamp: errorTimestamp,
|
||||
alerts: monAlerts,
|
||||
};
|
||||
};
|
||||
|
|
|
@ -12,97 +12,99 @@ import { UMElasticsearchQueryFn } from '../adapters/framework';
|
|||
import { createEsQuery } from '../../../common/utils/es_search';
|
||||
import { getHistogramInterval } from '../../../common/lib/get_histogram_interval';
|
||||
|
||||
export const getPingHistogram: UMElasticsearchQueryFn<GetPingHistogramParams, HistogramResult> =
|
||||
async ({
|
||||
uptimeEsClient,
|
||||
dateStart: from,
|
||||
dateEnd: to,
|
||||
filters,
|
||||
monitorId,
|
||||
bucketSize,
|
||||
query,
|
||||
}) => {
|
||||
const boolFilters = filters ? JSON.parse(filters) : null;
|
||||
const additionalFilters = [];
|
||||
if (monitorId) {
|
||||
additionalFilters.push({ match: { 'monitor.id': monitorId } });
|
||||
}
|
||||
if (boolFilters) {
|
||||
additionalFilters.push(boolFilters);
|
||||
}
|
||||
const filter = getFilterClause(from, to, additionalFilters);
|
||||
export const getPingHistogram: UMElasticsearchQueryFn<
|
||||
GetPingHistogramParams,
|
||||
HistogramResult
|
||||
> = async ({
|
||||
uptimeEsClient,
|
||||
dateStart: from,
|
||||
dateEnd: to,
|
||||
filters,
|
||||
monitorId,
|
||||
bucketSize,
|
||||
query,
|
||||
}) => {
|
||||
const boolFilters = filters ? JSON.parse(filters) : null;
|
||||
const additionalFilters = [];
|
||||
if (monitorId) {
|
||||
additionalFilters.push({ match: { 'monitor.id': monitorId } });
|
||||
}
|
||||
if (boolFilters) {
|
||||
additionalFilters.push(boolFilters);
|
||||
}
|
||||
const filter = getFilterClause(from, to, additionalFilters);
|
||||
|
||||
const minInterval = getHistogramInterval(from, to, QUERY.DEFAULT_BUCKET_COUNT);
|
||||
const minInterval = getHistogramInterval(from, to, QUERY.DEFAULT_BUCKET_COUNT);
|
||||
|
||||
const params = createEsQuery({
|
||||
body: {
|
||||
query: {
|
||||
bool: {
|
||||
filter: [
|
||||
...filter,
|
||||
{
|
||||
exists: {
|
||||
field: 'summary',
|
||||
},
|
||||
const params = createEsQuery({
|
||||
body: {
|
||||
query: {
|
||||
bool: {
|
||||
filter: [
|
||||
...filter,
|
||||
{
|
||||
exists: {
|
||||
field: 'summary',
|
||||
},
|
||||
],
|
||||
...(query
|
||||
? {
|
||||
minimum_should_match: 1,
|
||||
should: [
|
||||
{
|
||||
multi_match: {
|
||||
query: escape(query),
|
||||
type: 'phrase_prefix' as const,
|
||||
fields: ['monitor.id.text', 'monitor.name.text', 'url.full.text'],
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
: {}),
|
||||
},
|
||||
},
|
||||
size: 0,
|
||||
aggs: {
|
||||
timeseries: {
|
||||
date_histogram: {
|
||||
field: '@timestamp',
|
||||
fixed_interval: bucketSize || minInterval + 'ms',
|
||||
missing: '0',
|
||||
},
|
||||
aggs: {
|
||||
down: {
|
||||
sum: {
|
||||
field: 'summary.down',
|
||||
},
|
||||
],
|
||||
...(query
|
||||
? {
|
||||
minimum_should_match: 1,
|
||||
should: [
|
||||
{
|
||||
multi_match: {
|
||||
query: escape(query),
|
||||
type: 'phrase_prefix' as const,
|
||||
fields: ['monitor.id.text', 'monitor.name.text', 'url.full.text'],
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
: {}),
|
||||
},
|
||||
},
|
||||
size: 0,
|
||||
aggs: {
|
||||
timeseries: {
|
||||
date_histogram: {
|
||||
field: '@timestamp',
|
||||
fixed_interval: bucketSize || minInterval + 'ms',
|
||||
missing: '0',
|
||||
},
|
||||
aggs: {
|
||||
down: {
|
||||
sum: {
|
||||
field: 'summary.down',
|
||||
},
|
||||
up: {
|
||||
sum: {
|
||||
field: 'summary.up',
|
||||
},
|
||||
},
|
||||
up: {
|
||||
sum: {
|
||||
field: 'summary.up',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
const { body: result } = await uptimeEsClient.search(params, 'getPingsOverTime');
|
||||
const buckets = result?.aggregations?.timeseries?.buckets ?? [];
|
||||
const { body: result } = await uptimeEsClient.search(params, 'getPingsOverTime');
|
||||
const buckets = result?.aggregations?.timeseries?.buckets ?? [];
|
||||
|
||||
const histogram = buckets.map((bucket: Pick<typeof buckets[0], 'key' | 'down' | 'up'>) => {
|
||||
const x: number = bucket.key;
|
||||
const downCount = bucket.down.value || 0;
|
||||
const upCount = bucket.up.value || 0;
|
||||
return {
|
||||
x,
|
||||
downCount,
|
||||
upCount,
|
||||
y: 1,
|
||||
};
|
||||
});
|
||||
const histogram = buckets.map((bucket: Pick<typeof buckets[0], 'key' | 'down' | 'up'>) => {
|
||||
const x: number = bucket.key;
|
||||
const downCount = bucket.down.value || 0;
|
||||
const upCount = bucket.up.value || 0;
|
||||
return {
|
||||
histogram,
|
||||
minInterval,
|
||||
x,
|
||||
downCount,
|
||||
upCount,
|
||||
y: 1,
|
||||
};
|
||||
});
|
||||
return {
|
||||
histogram,
|
||||
minInterval,
|
||||
};
|
||||
};
|
||||
|
|
|
@ -12,5 +12,7 @@ import {
|
|||
FeatureUsageTestPluginStart,
|
||||
} from './plugin';
|
||||
|
||||
export const plugin: PluginInitializer<FeatureUsageTestPluginSetup, FeatureUsageTestPluginStart> =
|
||||
() => new FeatureUsageTestPlugin();
|
||||
export const plugin: PluginInitializer<
|
||||
FeatureUsageTestPluginSetup,
|
||||
FeatureUsageTestPluginStart
|
||||
> = () => new FeatureUsageTestPlugin();
|
||||
|
|
90
yarn.lock
90
yarn.lock
|
@ -6793,13 +6793,13 @@
|
|||
resolved "https://registry.yarnpkg.com/@types/zen-observable/-/zen-observable-0.8.0.tgz#8b63ab7f1aa5321248aad5ac890a485656dcea4d"
|
||||
integrity sha512-te5lMAWii1uEJ4FwLjzdlbw3+n0FZNOvFXHxQDKeT0dilh7HOzdMzV2TrJVUzq8ep7J4Na8OUYPRLSQkJHAlrg==
|
||||
|
||||
"@typescript-eslint/eslint-plugin@^5.2.0":
|
||||
version "5.2.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.2.0.tgz#2bdb247cc2e2afce7efbce09afb9a6f0a8a08434"
|
||||
integrity sha512-qQwg7sqYkBF4CIQSyRQyqsYvP+g/J0To9ZPVNJpfxfekl5RmdvQnFFTVVwpRtaUDFNvjfe/34TgY/dpc3MgNTw==
|
||||
"@typescript-eslint/eslint-plugin@^5.6.0":
|
||||
version "5.7.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.7.0.tgz#12d54709f8ea1da99a01d8a992cd0474ad0f0aa9"
|
||||
integrity sha512-8RTGBpNn5a9M628wBPrCbJ+v3YTEOE2qeZb7TDkGKTDXSj36KGRg92SpFFaR/0S3rSXQxM0Og/kV9EyadsYSBg==
|
||||
dependencies:
|
||||
"@typescript-eslint/experimental-utils" "5.2.0"
|
||||
"@typescript-eslint/scope-manager" "5.2.0"
|
||||
"@typescript-eslint/experimental-utils" "5.7.0"
|
||||
"@typescript-eslint/scope-manager" "5.7.0"
|
||||
debug "^4.3.2"
|
||||
functional-red-black-tree "^1.0.1"
|
||||
ignore "^5.1.8"
|
||||
|
@ -6807,15 +6807,15 @@
|
|||
semver "^7.3.5"
|
||||
tsutils "^3.21.0"
|
||||
|
||||
"@typescript-eslint/experimental-utils@5.2.0":
|
||||
version "5.2.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-5.2.0.tgz#e3b2cb9cd0aff9b50f68d9a414c299fd26b067e6"
|
||||
integrity sha512-fWyT3Agf7n7HuZZRpvUYdFYbPk3iDCq6fgu3ulia4c7yxmPnwVBovdSOX7RL+k8u6hLbrXcdAehlWUVpGh6IEw==
|
||||
"@typescript-eslint/experimental-utils@5.7.0":
|
||||
version "5.7.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-5.7.0.tgz#2b1633e6613c3238036156f70c32634843ad034f"
|
||||
integrity sha512-u57eZ5FbEpzN5kSjmVrSesovWslH2ZyNPnaXQMXWgH57d5+EVHEt76W75vVuI9qKZ5BMDKNfRN+pxcPEjQjb2A==
|
||||
dependencies:
|
||||
"@types/json-schema" "^7.0.9"
|
||||
"@typescript-eslint/scope-manager" "5.2.0"
|
||||
"@typescript-eslint/types" "5.2.0"
|
||||
"@typescript-eslint/typescript-estree" "5.2.0"
|
||||
"@typescript-eslint/scope-manager" "5.7.0"
|
||||
"@typescript-eslint/types" "5.7.0"
|
||||
"@typescript-eslint/typescript-estree" "5.7.0"
|
||||
eslint-scope "^5.1.1"
|
||||
eslint-utils "^3.0.0"
|
||||
|
||||
|
@ -6831,14 +6831,14 @@
|
|||
eslint-scope "^5.1.1"
|
||||
eslint-utils "^3.0.0"
|
||||
|
||||
"@typescript-eslint/parser@^5.2.0":
|
||||
version "5.2.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.2.0.tgz#dc081aa89de16b5676b10215519af3aa7b58fb72"
|
||||
integrity sha512-Uyy4TjJBlh3NuA8/4yIQptyJb95Qz5PX//6p8n7zG0QnN4o3NF9Je3JHbVU7fxf5ncSXTmnvMtd/LDQWDk0YqA==
|
||||
"@typescript-eslint/parser@^5.6.0":
|
||||
version "5.7.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.7.0.tgz#4dca6de463d86f02d252e681136a67888ea3b181"
|
||||
integrity sha512-m/gWCCcS4jXw6vkrPQ1BjZ1vomP01PArgzvauBqzsoZ3urLbsRChexB8/YV8z9HwE3qlJM35FxfKZ1nfP/4x8g==
|
||||
dependencies:
|
||||
"@typescript-eslint/scope-manager" "5.2.0"
|
||||
"@typescript-eslint/types" "5.2.0"
|
||||
"@typescript-eslint/typescript-estree" "5.2.0"
|
||||
"@typescript-eslint/scope-manager" "5.7.0"
|
||||
"@typescript-eslint/types" "5.7.0"
|
||||
"@typescript-eslint/typescript-estree" "5.7.0"
|
||||
debug "^4.3.2"
|
||||
|
||||
"@typescript-eslint/scope-manager@4.31.2":
|
||||
|
@ -6849,23 +6849,23 @@
|
|||
"@typescript-eslint/types" "4.31.2"
|
||||
"@typescript-eslint/visitor-keys" "4.31.2"
|
||||
|
||||
"@typescript-eslint/scope-manager@5.2.0":
|
||||
version "5.2.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.2.0.tgz#7ce8e4ab2baaa0ad5282913ea8e13ce03ec6a12a"
|
||||
integrity sha512-RW+wowZqPzQw8MUFltfKYZfKXqA2qgyi6oi/31J1zfXJRpOn6tCaZtd9b5u9ubnDG2n/EMvQLeZrsLNPpaUiFQ==
|
||||
"@typescript-eslint/scope-manager@5.7.0":
|
||||
version "5.7.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.7.0.tgz#70adf960e5a58994ad50438ba60d98ecadd79452"
|
||||
integrity sha512-7mxR520DGq5F7sSSgM0HSSMJ+TFUymOeFRMfUfGFAVBv8BR+Jv1vHgAouYUvWRZeszVBJlLcc9fDdktxb5kmxA==
|
||||
dependencies:
|
||||
"@typescript-eslint/types" "5.2.0"
|
||||
"@typescript-eslint/visitor-keys" "5.2.0"
|
||||
"@typescript-eslint/types" "5.7.0"
|
||||
"@typescript-eslint/visitor-keys" "5.7.0"
|
||||
|
||||
"@typescript-eslint/types@4.31.2":
|
||||
version "4.31.2"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-4.31.2.tgz#2aea7177d6d744521a168ed4668eddbd912dfadf"
|
||||
integrity sha512-kWiTTBCTKEdBGrZKwFvOlGNcAsKGJSBc8xLvSjSppFO88AqGxGNYtF36EuEYG6XZ9vT0xX8RNiHbQUKglbSi1w==
|
||||
|
||||
"@typescript-eslint/types@5.2.0":
|
||||
version "5.2.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.2.0.tgz#7ad32d15abddb0ee968a330f0ea182ea544ef7cf"
|
||||
integrity sha512-cTk6x08qqosps6sPyP2j7NxyFPlCNsJwSDasqPNjEQ8JMD5xxj2NHxcLin5AJQ8pAVwpQ8BMI3bTxR0zxmK9qQ==
|
||||
"@typescript-eslint/types@5.7.0":
|
||||
version "5.7.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.7.0.tgz#2d4cae0105ba7d08bffa69698197a762483ebcbe"
|
||||
integrity sha512-5AeYIF5p2kAneIpnLFve8g50VyAjq7udM7ApZZ9JYjdPjkz0LvODfuSHIDUVnIuUoxafoWzpFyU7Sqbxgi79mA==
|
||||
|
||||
"@typescript-eslint/typescript-estree@4.31.2":
|
||||
version "4.31.2"
|
||||
|
@ -6880,13 +6880,13 @@
|
|||
semver "^7.3.5"
|
||||
tsutils "^3.21.0"
|
||||
|
||||
"@typescript-eslint/typescript-estree@5.2.0", "@typescript-eslint/typescript-estree@^5.2.0":
|
||||
version "5.2.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.2.0.tgz#c22e0ff6f8a4a3f78504a80ebd686fe2870a68ae"
|
||||
integrity sha512-RsdXq2XmVgKbm9nLsE3mjNUM7BTr/K4DYR9WfFVMUuozHWtH5gMpiNZmtrMG8GR385EOSQ3kC9HiEMJWimxd/g==
|
||||
"@typescript-eslint/typescript-estree@5.7.0", "@typescript-eslint/typescript-estree@^5.6.0":
|
||||
version "5.7.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.7.0.tgz#968fad899050ccce4f08a40cd5fabc0798525006"
|
||||
integrity sha512-aO1Ql+izMrTnPj5aFFlEJkpD4jRqC4Gwhygu2oHK2wfVQpmOPbyDSveJ+r/NQo+PWV43M6uEAeLVbTi09dFLhg==
|
||||
dependencies:
|
||||
"@typescript-eslint/types" "5.2.0"
|
||||
"@typescript-eslint/visitor-keys" "5.2.0"
|
||||
"@typescript-eslint/types" "5.7.0"
|
||||
"@typescript-eslint/visitor-keys" "5.7.0"
|
||||
debug "^4.3.2"
|
||||
globby "^11.0.4"
|
||||
is-glob "^4.0.3"
|
||||
|
@ -6901,12 +6901,12 @@
|
|||
"@typescript-eslint/types" "4.31.2"
|
||||
eslint-visitor-keys "^2.0.0"
|
||||
|
||||
"@typescript-eslint/visitor-keys@5.2.0":
|
||||
version "5.2.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.2.0.tgz#03522d35df98474f08e0357171a7d1b259a88f55"
|
||||
integrity sha512-Nk7HizaXWWCUBfLA/rPNKMzXzWS8Wg9qHMuGtT+v2/YpPij4nVXrVJc24N/r5WrrmqK31jCrZxeHqIgqRzs0Xg==
|
||||
"@typescript-eslint/visitor-keys@5.7.0":
|
||||
version "5.7.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.7.0.tgz#e05164239eb7cb8aa9fa06c516ede480ce260178"
|
||||
integrity sha512-hdohahZ4lTFcglZSJ3DGdzxQHBSxsLVqHzkiOmKi7xVAWC4y2c1bIMKmPJSrA4aOEoRUPOKQ87Y/taC7yVHpFg==
|
||||
dependencies:
|
||||
"@typescript-eslint/types" "5.2.0"
|
||||
"@typescript-eslint/types" "5.7.0"
|
||||
eslint-visitor-keys "^3.0.0"
|
||||
|
||||
"@ungap/promise-all-settled@1.1.2":
|
||||
|
@ -22415,10 +22415,10 @@ prettier-linter-helpers@^1.0.0:
|
|||
dependencies:
|
||||
fast-diff "^1.1.2"
|
||||
|
||||
prettier@^2.4.0:
|
||||
version "2.4.0"
|
||||
resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.4.0.tgz#85bdfe0f70c3e777cf13a4ffff39713ca6f64cba"
|
||||
integrity sha512-DsEPLY1dE5HF3BxCRBmD4uYZ+5DCbvatnolqTqcxEgKVZnL2kUfyu7b8pPQ5+hTBkdhU9SLUmK0/pHb07RE4WQ==
|
||||
prettier@^2.5.1:
|
||||
version "2.5.1"
|
||||
resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.5.1.tgz#fff75fa9d519c54cf0fce328c1017d94546bc56a"
|
||||
integrity sha512-vBZcPRUR5MZJwoyi3ZoyQlc1rXeEck8KgeC9AwwOn+exuxLxq5toTRDTSaVrXHxelDMHy9zlicw8u66yxoSUFg==
|
||||
|
||||
prettier@~2.2.1:
|
||||
version "2.2.1"
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue