Skip to content

Commit

Permalink
[Synthetics] Added ability to hide public locations (elastic#164863)
Browse files Browse the repository at this point in the history
  • Loading branch information
shahzad31 authored Sep 14, 2023
1 parent 39cf371 commit 519c4d6
Show file tree
Hide file tree
Showing 30 changed files with 470 additions and 196 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,4 @@
* 2.0.
*/

export * from './locations';
export * from './state';

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,10 @@ export const TLSSensitiveFieldsCodec = t.partial({

export const TLSCodec = t.intersection([TLSFieldsCodec, TLSSensitiveFieldsCodec]);

const MonitorLocationsCodec = t.array(t.union([MonitorServiceLocationCodec, PrivateLocationCodec]));

export type MonitorLocations = t.TypeOf<typeof MonitorLocationsCodec>;

// CommonFields
export const CommonFieldsCodec = t.intersection([
t.interface({
Expand All @@ -56,7 +60,7 @@ export const CommonFieldsCodec = t.intersection([
[ConfigKey.SCHEDULE]: ScheduleCodec,
[ConfigKey.APM_SERVICE_NAME]: t.string,
[ConfigKey.TAGS]: t.array(t.string),
[ConfigKey.LOCATIONS]: t.array(t.union([MonitorServiceLocationCodec, PrivateLocationCodec])),
[ConfigKey.LOCATIONS]: MonitorLocationsCodec,
[ConfigKey.MONITOR_QUERY_ID]: t.string,
[ConfigKey.CONFIG_ID]: t.string,
}),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,14 @@ export const FleetPermissionsCallout = () => {
*/
export const NoPermissionsTooltip = ({
canEditSynthetics = true,
canUsePublicLocations = true,
children,
}: {
canEditSynthetics?: boolean;
canUsePublicLocations?: boolean;
children: ReactNode;
}) => {
const disabledMessage = getRestrictionReasonLabel(canEditSynthetics);
const disabledMessage = getRestrictionReasonLabel(canEditSynthetics, canUsePublicLocations);
if (disabledMessage) {
return (
<EuiToolTip content={disabledMessage}>
Expand All @@ -47,8 +49,16 @@ export const NoPermissionsTooltip = ({
return <>{children}</>;
};

function getRestrictionReasonLabel(canEditSynthetics = true): string | undefined {
return !canEditSynthetics ? CANNOT_PERFORM_ACTION_SYNTHETICS : undefined;
function getRestrictionReasonLabel(
canEditSynthetics = true,
canUsePublicLocations = true
): string | undefined {
const message = !canEditSynthetics ? CANNOT_PERFORM_ACTION_SYNTHETICS : undefined;
if (message) {
return message;
}

return !canUsePublicLocations ? CANNOT_PERFORM_ACTION_PUBLIC_LOCATIONS : undefined;
}

export const NEED_PERMISSIONS_PRIVATE_LOCATIONS = i18n.translate(
Expand Down Expand Up @@ -83,3 +93,10 @@ export const CANNOT_PERFORM_ACTION_SYNTHETICS = i18n.translate(
defaultMessage: 'You do not have sufficient permissions to perform this action.',
}
);

export const CANNOT_PERFORM_ACTION_PUBLIC_LOCATIONS = i18n.translate(
'xpack.synthetics.monitorManagement.canUsePublicLocations',
{
defaultMessage: 'You do not have sufficient permissions to use Elastic managed locations.',
}
);
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ import {
ResponseCheckJSON,
ThrottlingConfig,
RequestBodyCheck,
SourceType,
} from '../types';
import { AlertConfigKey, ALLOWED_SCHEDULES_IN_MINUTES } from '../constants';
import { getDefaultFormFields } from './defaults';
Expand Down Expand Up @@ -404,8 +405,8 @@ export const FIELD = (readOnly?: boolean): FieldMap => ({
props: ({ field, setValue, locations, trigger }) => {
return {
options: Object.values(locations).map((location) => ({
label: locations?.find((loc) => location.id === loc.id)?.label || '',
id: location.id || '',
label: location.label,
id: location.id,
isServiceManaged: location.isServiceManaged || false,
isInvalid: location.isInvalid,
disabled: location.isInvalid,
Expand All @@ -417,7 +418,9 @@ export const FIELD = (readOnly?: boolean): FieldMap => ({
: location.isServiceManaged
? 'default'
: 'primary',
label: locations?.find((loc) => location.id === loc.id)?.label ?? location.id,
label:
(location.label || locations?.find((loc) => location.id === loc.id)?.label) ??
location.id,
id: location.id || '',
isServiceManaged: location.isServiceManaged || false,
})),
Expand Down Expand Up @@ -483,66 +486,78 @@ export const FIELD = (readOnly?: boolean): FieldMap => ({
helpText: i18n.translate('xpack.synthetics.monitorConfig.edit.enabled.label', {
defaultMessage: `When disabled, the monitor doesn't run any tests. You can enable it at any time.`,
}),
props: ({ setValue, field, trigger }): EuiSwitchProps => ({
id: 'syntheticsMontiorConfigIsEnabled',
label: i18n.translate('xpack.synthetics.monitorConfig.enabled.label', {
defaultMessage: 'Enable Monitor',
}),
checked: field?.value || false,
onChange: async (event) => {
setValue(ConfigKey.ENABLED, !!event.target.checked);
await trigger(ConfigKey.ENABLED);
},
'data-test-subj': 'syntheticsEnableSwitch',
// enabled is an allowed field for read only
// isDisabled: readOnly,
}),
props: ({ setValue, field, trigger, formState }): EuiSwitchProps => {
const isProjectMonitor =
formState.defaultValues?.[ConfigKey.MONITOR_SOURCE_TYPE] === SourceType.PROJECT;
return {
id: 'syntheticsMontiorConfigIsEnabled',
label: i18n.translate('xpack.synthetics.monitorConfig.enabled.label', {
defaultMessage: 'Enable Monitor',
}),
checked: field?.value || false,
onChange: async (event) => {
setValue(ConfigKey.ENABLED, !!event.target.checked);
await trigger(ConfigKey.ENABLED);
},
'data-test-subj': 'syntheticsEnableSwitch',
// enabled is an allowed field for read only
disabled: !isProjectMonitor && readOnly,
};
},
},
[AlertConfigKey.STATUS_ENABLED]: {
fieldKey: AlertConfigKey.STATUS_ENABLED,
component: Switch,
controlled: true,
props: ({ setValue, field, trigger }): EuiSwitchProps => ({
id: 'syntheticsMonitorConfigIsAlertEnabled',
label: field?.value
? i18n.translate('xpack.synthetics.monitorConfig.enabledAlerting.label', {
defaultMessage: 'Disable status alerts on this monitor',
})
: i18n.translate('xpack.synthetics.monitorConfig.disabledAlerting.label', {
defaultMessage: 'Enable status alerts on this monitor',
}),
checked: field?.value || false,
onChange: async (event) => {
setValue(AlertConfigKey.STATUS_ENABLED, !!event.target.checked);
await trigger(AlertConfigKey.STATUS_ENABLED);
},
'data-test-subj': 'syntheticsAlertStatusSwitch',
// alert config is an allowed field for read only
// isDisabled: readOnly,
}),
props: ({ setValue, field, trigger, formState }): EuiSwitchProps => {
const isProjectMonitor =
formState.defaultValues?.[ConfigKey.MONITOR_SOURCE_TYPE] === SourceType.PROJECT;
return {
id: 'syntheticsMonitorConfigIsAlertEnabled',
label: field?.value
? i18n.translate('xpack.synthetics.monitorConfig.enabledAlerting.label', {
defaultMessage: 'Disable status alerts on this monitor',
})
: i18n.translate('xpack.synthetics.monitorConfig.disabledAlerting.label', {
defaultMessage: 'Enable status alerts on this monitor',
}),
checked: field?.value || false,
onChange: async (event) => {
setValue(AlertConfigKey.STATUS_ENABLED, !!event.target.checked);
await trigger(AlertConfigKey.STATUS_ENABLED);
},
'data-test-subj': 'syntheticsAlertStatusSwitch',
// alert config is an allowed field for read only
disabled: !isProjectMonitor && readOnly,
};
},
},
[AlertConfigKey.TLS_ENABLED]: {
fieldKey: AlertConfigKey.TLS_ENABLED,
component: Switch,
controlled: true,
props: ({ setValue, field, trigger }): EuiSwitchProps => ({
id: 'syntheticsMonitorConfigIsTlsAlertEnabled',
label: field?.value
? i18n.translate('xpack.synthetics.monitorConfig.edit.alertTlsEnabled.label', {
defaultMessage: 'Disable TLS alerts on this monitor.',
})
: i18n.translate('xpack.synthetics.monitorConfig.create.alertTlsEnabled.label', {
defaultMessage: 'Enable TLS alerts on this monitor.',
}),
checked: field?.value || false,
onChange: async (event) => {
setValue(AlertConfigKey.TLS_ENABLED, !!event.target.checked);
await trigger(AlertConfigKey.TLS_ENABLED);
},
'data-test-subj': 'syntheticsAlertStatusSwitch',
// alert config is an allowed field for read only
// isDisabled: readOnly,
}),
props: ({ setValue, field, trigger, formState }): EuiSwitchProps => {
const isProjectMonitor =
formState.defaultValues?.[ConfigKey.MONITOR_SOURCE_TYPE] === SourceType.PROJECT;
return {
id: 'syntheticsMonitorConfigIsTlsAlertEnabled',
label: field?.value
? i18n.translate('xpack.synthetics.monitorConfig.edit.alertTlsEnabled.label', {
defaultMessage: 'Disable TLS alerts on this monitor.',
})
: i18n.translate('xpack.synthetics.monitorConfig.create.alertTlsEnabled.label', {
defaultMessage: 'Enable TLS alerts on this monitor.',
}),
checked: field?.value || false,
onChange: async (event) => {
setValue(AlertConfigKey.TLS_ENABLED, !!event.target.checked);
await trigger(AlertConfigKey.TLS_ENABLED);
},
'data-test-subj': 'syntheticsAlertStatusSwitch',
// alert config is an allowed field for read only
disabled: !isProjectMonitor && readOnly,
};
},
},
[ConfigKey.TAGS]: {
fieldKey: ConfigKey.TAGS,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ export const MonitorForm: React.FC<{
defaultValues?: SyntheticsMonitor;
space?: string;
readOnly?: boolean;
}> = ({ children, defaultValues, space, readOnly = false }) => {
canUsePublicLocations: boolean;
}> = ({ children, defaultValues, space, readOnly = false, canUsePublicLocations }) => {
const methods = useFormWrapped({
mode: 'onSubmit',
reValidateMode: 'onSubmit',
Expand All @@ -43,7 +44,7 @@ export const MonitorForm: React.FC<{
>
{children}
<EuiSpacer />
<ActionBar readOnly={readOnly} />
<ActionBar readOnly={readOnly} canUsePublicLocations={canUsePublicLocations} />
</EuiForm>
<Disclaimer />
</FormProvider>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,11 @@ import { format } from './formatter';
import { MonitorFields as MonitorFieldsType } from '../../../../../../common/runtime_types';
import { runOnceMonitor } from '../../../state/manual_test_runs/api';

export const RunTestButton = () => {
export const RunTestButton = ({
canUsePublicLocations = true,
}: {
canUsePublicLocations?: boolean;
}) => {
const { formState, getValues, handleSubmit } = useFormContext();

const [inProgress, setInProgress] = useState(false);
Expand Down Expand Up @@ -56,7 +60,7 @@ export const RunTestButton = () => {
<EuiButton
data-test-subj="syntheticsRunTestBtn"
color="success"
disabled={isDisabled}
disabled={isDisabled || !canUsePublicLocations}
aria-label={TEST_NOW_ARIA_LABEL}
iconType="play"
onClick={handleSubmit(handleTestNow)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,13 @@ import { format } from './formatter';

import { MONITORS_ROUTE } from '../../../../../../common/constants';

export const ActionBar = ({ readOnly = false }: { readOnly: boolean }) => {
export const ActionBar = ({
readOnly = false,
canUsePublicLocations = true,
}: {
readOnly: boolean;
canUsePublicLocations: boolean;
}) => {
const { monitorId } = useParams<{ monitorId: string }>();
const history = useHistory();
const {
Expand Down Expand Up @@ -59,6 +65,7 @@ export const ActionBar = ({ readOnly = false }: { readOnly: boolean }) => {
onClick={() => {
setMonitorPendingDeletion(defaultValues as SyntheticsMonitor);
}}
isDisabled={!canEditSynthetics || !canUsePublicLocations}
>
{DELETE_MONITOR_LABEL}
</EuiButton>
Expand All @@ -75,16 +82,19 @@ export const ActionBar = ({ readOnly = false }: { readOnly: boolean }) => {
</EuiLink>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<RunTestButton />
<RunTestButton canUsePublicLocations={canUsePublicLocations} />
</EuiFlexItem>
<EuiFlexItem grow={false} css={{ marginLeft: 'auto' }}>
<NoPermissionsTooltip canEditSynthetics={canEditSynthetics}>
<NoPermissionsTooltip
canEditSynthetics={canEditSynthetics}
canUsePublicLocations={canUsePublicLocations}
>
<EuiButton
fill
isLoading={loading}
onClick={handleSubmit(formSubmitter)}
data-test-subj="syntheticsMonitorConfigSubmitButton"
disabled={!canEditSynthetics}
disabled={!canEditSynthetics || !canUsePublicLocations}
>
{isEdit ? UPDATE_MONITOR_LABEL : CREATE_MONITOR_LABEL}
</EuiButton>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { EuiEmptyPrompt } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { useTrackPageview, useFetcher } from '@kbn/observability-shared-plugin/public';
import { IHttpFetchError, ResponseErrorBody } from '@kbn/core-http-browser';
import { useCanUsePublicLocations } from '../../../../hooks/use_capabilities';
import { EditMonitorNotFound } from './edit_monitor_not_found';
import { LoadingState } from '../monitors_page/overview/overview/monitor_detail_flyout';
import { ConfigKey, SourceType } from '../../../../../common/runtime_types';
Expand Down Expand Up @@ -50,11 +51,15 @@ export const MonitorEditPage: React.FC = () => {
data?.id
);

const canUsePublicLocations = useCanUsePublicLocations(data?.[ConfigKey.LOCATIONS]);

if (monitorNotFoundError) {
return <EditMonitorNotFound />;
}

const isReadOnly = data?.[ConfigKey.MONITOR_SOURCE_TYPE] === SourceType.PROJECT;
const isReadOnly =
data?.[ConfigKey.MONITOR_SOURCE_TYPE] === SourceType.PROJECT || !canUsePublicLocations;

const projectId = data?.[ConfigKey.PROJECT_ID];

if (locationsError) {
Expand Down Expand Up @@ -87,8 +92,13 @@ export const MonitorEditPage: React.FC = () => {
return data && locationsLoaded && !loading && !error ? (
<>
<AlertingCallout isAlertingEnabled={data[ConfigKey.ALERT_CONFIG]?.status?.enabled} />
<MonitorForm defaultValues={data} readOnly={isReadOnly}>
<MonitorForm
defaultValues={data}
readOnly={isReadOnly}
canUsePublicLocations={canUsePublicLocations}
>
<MonitorSteps
canUsePublicLocations={canUsePublicLocations}
stepMap={EDIT_MONITOR_STEPS(isReadOnly)}
isEditFlow={true}
readOnly={isReadOnly}
Expand Down
Loading

0 comments on commit 519c4d6

Please sign in to comment.