Skip to content

Commit

Permalink
[Cases] Separating http request contract from saved object persistenc…
Browse files Browse the repository at this point in the history
…e layer (#155325)

This PR separates the http API io-ts types from the types that are used
in the cases service layer to interact with the saved object client.
This PR is specifically for the user actions it only affects the types
used when interacting with the saved object client and doesn't touch the
transformation logic yet.

Issue: #153726
  • Loading branch information
jonathan-buttner committed Apr 26, 2023
1 parent bf588bf commit ed3d037
Show file tree
Hide file tree
Showing 7 changed files with 235 additions and 218 deletions.
2 changes: 1 addition & 1 deletion x-pack/plugins/cases/server/common/types/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export interface User {
email: string | null | undefined;
full_name: string | null | undefined;
username: string | null | undefined;
profile_uid?: string | undefined;
profile_uid?: string;
}

export interface UserProfile {
Expand Down
20 changes: 20 additions & 0 deletions x-pack/plugins/cases/server/common/types/user_actions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import type { User } from './user';

interface UserActionCommonPersistedAttributes {
action: string;
created_at: string;
created_by: User;
owner: string;
}

export interface UserActionPersistedAttributes extends UserActionCommonPersistedAttributes {
type: string;
payload: Record<string, unknown>;
}
160 changes: 37 additions & 123 deletions x-pack/plugins/cases/server/services/user_actions/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,13 @@
* 2.0.
*/

import type {
SavedObject,
SavedObjectReference,
SavedObjectsFindResponse,
SavedObjectsRawDoc,
} from '@kbn/core/server';
import type { SavedObject, SavedObjectsFindResponse, SavedObjectsRawDoc } from '@kbn/core/server';

import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
import type { KueryNode } from '@kbn/es-query';
import type {
CaseUserActionAttributesWithoutConnectorId,
CaseUserActionDeprecatedResponse,
CaseUserActionInjectedAttributes,
User,
} from '../../../common/api';
import { ActionTypes } from '../../../common/api';
import {
Expand All @@ -30,97 +23,22 @@ import { buildFilter, combineFilters } from '../../client/utils';
import type {
CaseConnectorActivity,
CaseConnectorFields,
ConnectorActivityAggsResult,
ConnectorFieldsBeforePushAggsResult,
GetUsersResponse,
ParticipantsAggsResult,
PushInfo,
PushTimeFrameInfo,
ServiceContext,
TimeFrameInfo,
TopHits,
UserActionsStatsAggsResult,
} from './types';
import { defaultSortField } from '../../common/utils';
import { UserActionPersister } from './operations/create';
import { UserActionFinder } from './operations/find';
import { transformToExternalModel, legacyTransformFindResponseToExternalModel } from './transform';

export interface UserActionItem {
attributes: CaseUserActionAttributesWithoutConnectorId;
references: SavedObjectReference[];
}

interface TopHits {
hits: {
total: number;
hits: SavedObjectsRawDoc[];
};
}

interface TimeFrameInfo {
mostRecent: TopHits;
oldest: TopHits;
}

interface ConnectorActivityAggsResult {
references: {
connectors: {
ids: {
buckets: Array<{
key: string;
reverse: {
connectorActivity: {
buckets: {
changeConnector: TimeFrameInfo;
createCase: TimeFrameInfo;
pushInfo: TimeFrameInfo;
};
};
};
}>;
};
};
};
}

interface ConnectorFieldsBeforePushAggsResult {
references: {
connectors: {
reverse: {
ids: {
buckets: Record<string, TimeFrameInfo>;
};
};
};
};
}

interface UserActionsStatsAggsResult {
total: number;
totals: {
buckets: Array<{
key: string;
doc_count: number;
}>;
};
}

interface ParticipantsAggsResult {
participants: {
buckets: Array<{
key: string;
docs: {
hits: {
hits: SavedObjectsRawDoc[];
};
};
}>;
};
assignees: {
buckets: Array<{
key: string;
}>;
};
}

interface GetUsersResponse {
participants: Array<{ id: string; owner: string; user: User }>;
assignedAndUnassignedUsers: Set<string>;
}
import type { UserActionPersistedAttributes } from '../../common/types/user_actions';

export class CaseUserActionService {
private readonly _creator: UserActionPersister;
Expand Down Expand Up @@ -160,7 +78,7 @@ export class CaseUserActionService {
});

const response = await this.context.unsecuredSavedObjectsClient.find<
CaseUserActionAttributesWithoutConnectorId,
UserActionPersistedAttributes,
ConnectorFieldsBeforePushAggsResult
>({
type: CASE_USER_ACTION_SAVED_OBJECT,
Expand Down Expand Up @@ -286,7 +204,7 @@ export class CaseUserActionService {
if (fields.mostRecent.hits.hits.length > 0) {
const rawFieldsDoc = fields.mostRecent.hits.hits[0];
const doc =
this.context.savedObjectsSerializer.rawToSavedObject<CaseUserActionAttributesWithoutConnectorId>(
this.context.savedObjectsSerializer.rawToSavedObject<UserActionPersistedAttributes>(
rawFieldsDoc
);

Expand Down Expand Up @@ -326,17 +244,15 @@ export class CaseUserActionService {
});

const userActions =
await this.context.unsecuredSavedObjectsClient.find<CaseUserActionAttributesWithoutConnectorId>(
{
type: CASE_USER_ACTION_SAVED_OBJECT,
hasReference: { type, id },
page: 1,
perPage: 1,
sortField: 'created_at',
sortOrder: 'desc',
filter: connectorsFilter,
}
);
await this.context.unsecuredSavedObjectsClient.find<UserActionPersistedAttributes>({
type: CASE_USER_ACTION_SAVED_OBJECT,
hasReference: { type, id },
page: 1,
perPage: 1,
sortField: 'created_at',
sortOrder: 'desc',
filter: connectorsFilter,
});

if (userActions.saved_objects.length <= 0) {
return;
Expand Down Expand Up @@ -366,7 +282,7 @@ export class CaseUserActionService {
});

const response = await this.context.unsecuredSavedObjectsClient.find<
CaseUserActionAttributesWithoutConnectorId,
UserActionPersistedAttributes,
ConnectorActivityAggsResult
>({
type: CASE_USER_ACTION_SAVED_OBJECT,
Expand Down Expand Up @@ -414,7 +330,7 @@ export class CaseUserActionService {
let fieldsDoc: SavedObject<CaseUserActionInjectedAttributes> | undefined;
if (rawFieldsDoc != null) {
const doc =
this.context.savedObjectsSerializer.rawToSavedObject<CaseUserActionAttributesWithoutConnectorId>(
this.context.savedObjectsSerializer.rawToSavedObject<UserActionPersistedAttributes>(
rawFieldsDoc
);

Expand Down Expand Up @@ -459,7 +375,7 @@ export class CaseUserActionService {
const rawPushDoc = topHits.hits.hits[0];

const doc =
this.context.savedObjectsSerializer.rawToSavedObject<CaseUserActionAttributesWithoutConnectorId>(
this.context.savedObjectsSerializer.rawToSavedObject<UserActionPersistedAttributes>(
rawPushDoc
);

Expand Down Expand Up @@ -568,16 +484,14 @@ export class CaseUserActionService {
const type = CASE_SAVED_OBJECT;

const userActions =
await this.context.unsecuredSavedObjectsClient.find<CaseUserActionAttributesWithoutConnectorId>(
{
type: CASE_USER_ACTION_SAVED_OBJECT,
hasReference: { type, id },
page: 1,
perPage: MAX_DOCS_PER_PAGE,
sortField: 'created_at',
sortOrder: 'asc',
}
);
await this.context.unsecuredSavedObjectsClient.find<UserActionPersistedAttributes>({
type: CASE_USER_ACTION_SAVED_OBJECT,
hasReference: { type, id },
page: 1,
perPage: MAX_DOCS_PER_PAGE,
sortField: 'created_at',
sortOrder: 'asc',
});

return legacyTransformFindResponseToExternalModel(
userActions,
Expand All @@ -595,6 +509,8 @@ export class CaseUserActionService {
`Attempting to retrieve user actions associated with cases: [${caseIds}]`
);

// We are intentionally not adding the type here because we only want to interact with the id and this function
// should not use the attributes
const finder = this.context.unsecuredSavedObjectsClient.createPointInTimeFinder({
type: CASE_USER_ACTION_SAVED_OBJECT,
hasReference: caseIds.map((id) => ({ id, type: CASE_SAVED_OBJECT })),
Expand Down Expand Up @@ -640,7 +556,7 @@ export class CaseUserActionService {
const combinedFilter = combineFilters([connectorsFilter, filter]);

const response = await this.context.unsecuredSavedObjectsClient.find<
CaseUserActionAttributesWithoutConnectorId,
UserActionPersistedAttributes,
{ references: { connectors: { ids: { buckets: Array<{ key: string }> } } } }
>({
type: CASE_USER_ACTION_SAVED_OBJECT,
Expand Down Expand Up @@ -698,7 +614,7 @@ export class CaseUserActionService {

public async getCaseUserActionStats({ caseId }: { caseId: string }) {
const response = await this.context.unsecuredSavedObjectsClient.find<
CaseUserActionAttributesWithoutConnectorId,
UserActionPersistedAttributes,
UserActionsStatsAggsResult
>({
type: CASE_USER_ACTION_SAVED_OBJECT,
Expand Down Expand Up @@ -742,7 +658,7 @@ export class CaseUserActionService {

public async getUsers({ caseId }: { caseId: string }): Promise<GetUsersResponse> {
const response = await this.context.unsecuredSavedObjectsClient.find<
CaseUserActionAttributesWithoutConnectorId,
UserActionPersistedAttributes,
ParticipantsAggsResult
>({
type: CASE_USER_ACTION_SAVED_OBJECT,
Expand All @@ -762,9 +678,7 @@ export class CaseUserActionService {
for (const bucket of participantsBuckets) {
const rawDoc = bucket.docs.hits.hits[0];
const user =
this.context.savedObjectsSerializer.rawToSavedObject<CaseUserActionAttributesWithoutConnectorId>(
rawDoc
);
this.context.savedObjectsSerializer.rawToSavedObject<UserActionPersistedAttributes>(rawDoc);

/**
* We are interested only for the created_by
Expand Down
Loading

0 comments on commit ed3d037

Please sign in to comment.