[Synthetics UI] return 404 in project monitor APIs with non-existing spaces. (#149136)

## Summary

Closes #148930

Returns a 404 when the project monitor APIs are called with a
non-existent space.

When testing, note that the API _does_ return 404, but the
@elastic/synthetics package seems to ignore this.

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
Co-authored-by: shahzad31 <shahzad31comp@gmail.com>
This commit is contained in:
Alejandro Fernández Gómez 2023-01-26 16:54:20 +01:00 committed by GitHub
parent cf907f7a98
commit 1d5e25ae27
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 65 additions and 36 deletions

View file

@ -19,7 +19,6 @@ import {
import { euiStyled } from '@kbn/kibana-react-plugin/common';
import { OverviewStatusState } from '../../../../../../../common/runtime_types';
import { useSyntheticsRefreshContext } from '../../../../contexts/synthetics_refresh_context';
import * as labels from '../labels';
import { MonitorTestRunsCount } from './monitor_test_runs';
@ -27,8 +26,6 @@ import { MonitorTestRunsSparkline } from './monitor_test_runs_sparkline';
export const MonitorStats = ({ status }: { status: OverviewStatusState | null }) => {
const { euiTheme } = useEuiTheme();
const { lastRefresh } = useSyntheticsRefreshContext();
const to = new Date(lastRefresh).toISOString();
return (
<>
@ -67,9 +64,9 @@ export const MonitorStats = ({ status }: { status: OverviewStatusState | null })
<EuiFlexItem
css={{ display: 'flex', flexDirection: 'row', gap: euiTheme.size.l, height: '200px' }}
>
<MonitorTestRunsCount to={to} />
<MonitorTestRunsCount />
<EuiFlexItem grow={true}>
<MonitorTestRunsSparkline to={to} />
<MonitorTestRunsSparkline />
</EuiFlexItem>
</EuiFlexItem>
</EuiPanel>

View file

@ -11,30 +11,25 @@ import { useKibana } from '@kbn/kibana-react-plugin/public';
import { useTheme } from '@kbn/observability-plugin/public';
import { ReportTypes } from '@kbn/observability-plugin/public';
import { useAbsoluteDate } from '../../../../hooks';
import { ClientPluginsStart } from '../../../../../../plugin';
import * as labels from '../labels';
interface MonitorCompleteCountProps {
from?: string;
to?: string;
}
export const MonitorTestRunsCount = ({
from = 'now-30d',
to = 'now',
}: MonitorCompleteCountProps) => {
export const MonitorTestRunsCount = () => {
const { observability } = useKibana<ClientPluginsStart>().services;
const theme = useTheme();
const { ExploratoryViewEmbeddable } = observability;
const { from: absFrom, to: absTo } = useAbsoluteDate({ from: 'now-30d', to: 'now' });
return (
<ExploratoryViewEmbeddable
align="left"
reportType={ReportTypes.SINGLE_METRIC}
attributes={[
{
time: { from, to },
time: { from: absFrom, to: absTo },
reportDefinitions: {
'monitor.id': [],
'observer.geo.name': [],

View file

@ -10,20 +10,19 @@ import React from 'react';
import { useKibana } from '@kbn/kibana-react-plugin/public';
import { useTheme } from '@kbn/observability-plugin/public';
import { useAbsoluteDate } from '../../../../hooks';
import { ClientPluginsStart } from '../../../../../../plugin';
import * as labels from '../labels';
interface Props {
from?: string;
to?: string;
}
export const MonitorTestRunsSparkline = ({ from = 'now-30d', to = 'now' }: Props) => {
export const MonitorTestRunsSparkline = () => {
const { observability } = useKibana<ClientPluginsStart>().services;
const { ExploratoryViewEmbeddable } = observability;
const theme = useTheme();
const { from, to } = useAbsoluteDate({ from: 'now-30d', to: 'now' });
return (
<ExploratoryViewEmbeddable
reportType="kpi-over-time"

View file

@ -28,7 +28,7 @@ import { MlPluginSetup as MlSetup } from '@kbn/ml-plugin/server';
import { RuleRegistryPluginSetupContract } from '@kbn/rule-registry-plugin/server';
import { SecurityPluginStart } from '@kbn/security-plugin/server';
import { CloudSetup } from '@kbn/cloud-plugin/server';
import { SpacesPluginSetup } from '@kbn/spaces-plugin/server';
import { SpacesPluginStart } from '@kbn/spaces-plugin/server';
import { FleetStartContract } from '@kbn/fleet-plugin/server';
import { BfetchServerSetup } from '@kbn/bfetch-plugin/server';
import { UptimeEsClient } from '../../lib';
@ -54,7 +54,7 @@ export interface UptimeServerSetup {
router: UptimeRouter;
config: UptimeConfig;
cloud?: CloudSetup;
spaces: SpacesPluginSetup;
spaces: SpacesPluginStart;
fleet: FleetStartContract;
security: SecurityPluginStart;
savedObjectsClient?: SavedObjectsClientContract;
@ -76,7 +76,6 @@ export interface UptimeCorePluginsSetup {
usageCollection: UsageCollectionSetup;
ml: MlSetup;
cloud?: CloudSetup;
spaces: SpacesPluginSetup;
ruleRegistry: RuleRegistryPluginSetupContract;
encryptedSavedObjects: EncryptedSavedObjectsPluginSetup;
taskManager: TaskManagerSetupContract;
@ -90,4 +89,5 @@ export interface UptimeCorePluginsStart {
encryptedSavedObjects: EncryptedSavedObjectsPluginStart;
taskManager: TaskManagerStartContract;
telemetry: TelemetryPluginStart;
spaces: SpacesPluginStart;
}

View file

@ -86,7 +86,6 @@ export class Plugin implements PluginType {
logger: this.logger,
telemetry: this.telemetryEventsSender,
isDev: this.initContext.env.mode.dev,
spaces: plugins.spaces,
} as UptimeServerSetup;
this.syntheticsService = new SyntheticsService(this.server);
@ -126,6 +125,7 @@ export class Plugin implements PluginType {
this.server.fleet = pluginsStart.fleet;
this.server.encryptedSavedObjects = pluginsStart.encryptedSavedObjects;
this.server.savedObjectsClient = this.savedObjectsClient;
this.server.spaces = pluginsStart.spaces;
}
this.syntheticsService?.start(pluginsStart.taskManager);

View file

@ -58,8 +58,6 @@ export const addSyntheticsMonitorRoute: SyntheticsRestApiRouteFactory = () => ({
// usually id is auto generated, but this is useful for testing
const { id } = request.query;
const spaceId = server.spaces.spacesService.getSpaceId(request);
const monitor: SyntheticsMonitor = request.body as SyntheticsMonitor;
const monitorType = monitor[ConfigKey.MONITOR_TYPE];
const monitorWithDefaults = {
@ -79,6 +77,7 @@ export const addSyntheticsMonitorRoute: SyntheticsRestApiRouteFactory = () => ({
);
try {
const { id: spaceId } = await server.spaces.spacesService.getActiveSpace(request);
const { errors, newMonitor } = await syncNewMonitor({
normalizedMonitor: validationResult.decodedMonitor,
server,

View file

@ -41,7 +41,6 @@ export const addSyntheticsProjectMonitorRoute: SyntheticsRestApiRouteFactory = (
const { projectName } = request.params;
const decodedProjectName = decodeURI(projectName);
const monitors = (request.body?.monitors as ProjectMonitor[]) || [];
const spaceId = server.spaces.spacesService.getSpaceId(request);
if (monitors.length > 250) {
return response.badRequest({
@ -52,6 +51,7 @@ export const addSyntheticsProjectMonitorRoute: SyntheticsRestApiRouteFactory = (
}
try {
const { id: spaceId } = await server.spaces.spacesService.getActiveSpace(request);
const encryptedSavedObjectsClient = server.encryptedSavedObjects.getClient();
const pushMonitorFormatter = new ProjectMonitorFormatter({
@ -74,6 +74,11 @@ export const addSyntheticsProjectMonitorRoute: SyntheticsRestApiRouteFactory = (
};
} catch (error) {
server.logger.error(`Error adding monitors to project ${decodedProjectName}`);
if (error.output.statusCode === 404) {
const spaceId = server.spaces.spacesService.getSpaceId(request);
return response.notFound({ body: { message: `Kibana space '${spaceId}' does not exist` } });
}
throw error;
}
},

View file

@ -39,9 +39,11 @@ export const addSyntheticsProjectMonitorRouteLegacy: SyntheticsStreamingRouteFac
syntheticsMonitorClient,
subject,
}): Promise<any> => {
const monitors = (request.body?.monitors as ProjectMonitor[]) || [];
try {
const monitors = (request.body?.monitors as ProjectMonitor[]) || [];
const spaceId = server.spaces.spacesService.getSpaceId(request);
const { id: spaceId } = await server.spaces.spacesService.getActiveSpace(request);
const { keep_stale: keepStale, project: projectId } = request.body || {};
const { publicLocations, privateLocations } = await getAllLocations(
server,
@ -76,7 +78,13 @@ export const addSyntheticsProjectMonitorRouteLegacy: SyntheticsStreamingRouteFac
failedStaleMonitors: pushMonitorFormatter.failedStaleMonitors,
});
} catch (error) {
subject?.error(error);
if (error?.output?.statusCode === 404) {
const spaceId = server.spaces.spacesService.getSpaceId(request);
subject?.next(`Unable to create monitors. Kibana space '${spaceId}' does not exist.`);
subject?.next({ failedMonitors: monitors.map((m) => m.id) });
} else {
subject?.error(error);
}
} finally {
subject?.complete();
}

View file

@ -35,9 +35,9 @@ export const deleteMonitorBulk = async ({
request: KibanaRequest;
}) => {
const { logger, telemetry, stackVersion } = server;
const spaceId = server.spaces.spacesService.getSpaceId(request);
try {
const { id: spaceId } = await server.spaces.spacesService.getActiveSpace(request);
const deleteSyncPromise = syntheticsMonitorClient.deleteMonitors(
monitors.map((normalizedMonitor) => ({
...normalizedMonitor.attributes,

View file

@ -88,7 +88,6 @@ export const deleteMonitor = async ({
request: KibanaRequest;
}) => {
const { logger, telemetry, stackVersion } = server;
const spaceId = server.spaces.spacesService.getSpaceId(request);
const { monitor, monitorWithSecret } = await getMonitorToDelete(
monitorId,
@ -96,6 +95,7 @@ export const deleteMonitor = async ({
server
);
try {
const { id: spaceId } = await server.spaces.spacesService.getActiveSpace(request);
const deleteSyncPromise = syntheticsMonitorClient.deleteMonitors(
[
{

View file

@ -56,9 +56,9 @@ export const editSyntheticsMonitorRoute: SyntheticsRestApiRouteFactory = () => (
const monitor = request.body as SyntheticsMonitor;
const { monitorId } = request.params;
const spaceId = server.spaces.spacesService.getSpaceId(request);
try {
const { id: spaceId } = await server.spaces.spacesService.getActiveSpace(request);
const previousMonitor: SavedObject<EncryptedSyntheticsMonitor> = await savedObjectsClient.get(
syntheticsMonitorType,
monitorId

View file

@ -27,8 +27,7 @@ export const addSyntheticsParamsRoute: SyntheticsRestApiRouteFactory = () => ({
writeAccess: true,
handler: async ({ request, server, savedObjectsClient }): Promise<any> => {
const { namespaces, ...data } = request.body as SyntheticsParam;
const spaceId = server.spaces.spacesService.getSpaceId(request);
const { id: spaceId } = await server.spaces.spacesService.getActiveSpace(request);
const result = await savedObjectsClient.create(syntheticsParamType, data, {
initialNamespaces: (namespaces ?? []).length > 0 ? namespaces : [spaceId],

View file

@ -99,6 +99,20 @@ export default function ({ getService }: FtrProviderContext) {
});
});
it('project monitors - returns 404 for non-existing spaces', async () => {
const project = `test-project-${uuidv4()}`;
await supertest
.put(
`/s/i_dont_exist${API_URLS.SYNTHETICS_MONITORS_PROJECT_UPDATE.replace(
'{projectName}',
project
)}`
)
.set('kbn-xsrf', 'true')
.send(projectMonitors)
.expect(404);
});
it('project monitors - handles browser monitors', async () => {
const successfulMonitors = [projectMonitors.monitors[0]];
const project = `test-project-${uuidv4()}`;

View file

@ -559,6 +559,19 @@ export default function ({ getService }: FtrProviderContext) {
}
});
it('project monitors - returns error if the space does not exist', async () => {
const messages = await parseStreamApiResponse(
kibanaServerUrl + '/s/i_dont_exist' + API_URLS.SYNTHETICS_MONITORS_PROJECT_LEGACY,
JSON.stringify(projectMonitors)
);
expect(messages).to.have.length(2);
expect(messages[0]).to.equal(
"Unable to create monitors. Kibana space 'i_dont_exist' does not exist."
);
expect(messages[1].failedMonitors).to.eql(projectMonitors.monitors.map((m) => m.id));
});
it('project monitors - returns a list of successfully updated monitors', async () => {
try {
await supertest