Bump prettier/eslint packages related to Typescript after TS upgrade (#121119)

This commit is contained in:
Brian Seeders 2021-12-14 13:04:14 -05:00 committed by GitHub
parent 4af89667c2
commit af960b61ff
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
47 changed files with 2168 additions and 2123 deletions

View file

@ -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",

View file

@ -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,

View file

@ -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,

View file

@ -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>
);
};

View file

@ -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}
/>
);
};

View file

@ -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}
/>
);
};

View file

@ -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,
};
};

View file

@ -13,5 +13,7 @@ import {
IndexPatternsTestPluginStart,
} from './plugin';
export const plugin: PluginInitializer<IndexPatternsTestPluginSetup, IndexPatternsTestPluginStart> =
() => new IndexPatternsTestPlugin();
export const plugin: PluginInitializer<
IndexPatternsTestPluginSetup,
IndexPatternsTestPluginStart
> = () => new IndexPatternsTestPlugin();

View file

@ -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>
);
};

View file

@ -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 };

View file

@ -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>

View file

@ -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();

View file

@ -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>
);
};

View file

@ -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>
);
};

View file

@ -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>
);
};

View file

@ -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} />
);
};

View file

@ -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

View file

@ -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>,

View file

@ -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>,

View file

@ -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 });
}
};

View file

@ -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

View file

@ -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(

View file

@ -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>
}
/>
);
};

View file

@ -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}
/>
);
};

View file

@ -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}
/>
);
};

View file

@ -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

View file

@ -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>

View file

@ -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}
/>
);
}
};

View file

@ -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}
/>
);
}
};

View file

@ -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}
/>
);

View file

@ -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));

View file

@ -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 {

View file

@ -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,

View file

@ -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,

View file

@ -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));

View file

@ -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,

View file

@ -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,

View file

@ -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 }) => (
<>

View file

@ -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 };

View file

@ -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 };

View file

@ -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'],
};
});
};

View file

@ -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),
},
}));
};

View file

@ -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,
};
};

View file

@ -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,
};
};

View file

@ -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,
};
};

View file

@ -12,5 +12,7 @@ import {
FeatureUsageTestPluginStart,
} from './plugin';
export const plugin: PluginInitializer<FeatureUsageTestPluginSetup, FeatureUsageTestPluginStart> =
() => new FeatureUsageTestPlugin();
export const plugin: PluginInitializer<
FeatureUsageTestPluginSetup,
FeatureUsageTestPluginStart
> = () => new FeatureUsageTestPlugin();

View file

@ -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"