[Cases] Adding oldestPushDate field to get connectors API (#149451)

This PR adds the `oldestPushDate` field to the `_connectors` API. This
is needed to determine whether to show the text `pushed as new incident
<name>` or `updated incident <name>`.

Update response
```
{
    "8548e270-9c26-11ed-8376-87998de9968e": {
        "name": "Jira",
        "type": ".jira",
        "fields": {
            "issueType": "10001",
            "parent": null,
            "priority": null
        },
        "id": "8548e270-9c26-11ed-8376-87998de9968e",
        "needsToBePushed": false,
        "latestPushDate": "2023-01-24T20:35:54.325Z",
        "oldestPushDate": "2023-01-24T20:35:43.730Z", <--- New field
        "hasBeenPushed": true
    }
}
```
This commit is contained in:
Jonathan Buttner 2023-01-25 08:15:48 -05:00 committed by GitHub
parent 026d347305
commit e6f24a2272
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 118 additions and 49 deletions

View file

@ -120,7 +120,7 @@ export const GetCaseConnectorsResponseRt = rt.record(
rt.string,
rt.intersection([
rt.type({ needsToBePushed: rt.boolean, hasBeenPushed: rt.boolean }),
rt.partial(rt.type({ latestPushDate: rt.string }).props),
rt.partial(rt.type({ latestPushDate: rt.string, oldestPushDate: rt.string }).props),
CaseConnectorRt,
])
);

View file

@ -22,7 +22,7 @@ import type { CasesClientArgs } from '..';
import type { Authorization, OwnerEntity } from '../../authorization';
import { Operations } from '../../authorization';
import type { GetConnectorsRequest } from './types';
import type { CaseConnectorActivity, PushInfo } from '../../services/user_actions/types';
import type { CaseConnectorActivity } from '../../services/user_actions/types';
import type { CaseUserActionService } from '../../services';
export const getConnectors = async (
@ -82,10 +82,18 @@ const checkConnectorsAuthorization = async ({
});
if (connector.push) {
entities.push({
owner: connector.push.attributes.owner,
id: connector.connectorId,
});
entities.push(
...[
{
owner: connector.push.mostRecent.attributes.owner,
id: connector.connectorId,
},
{
owner: connector.push.oldest.attributes.owner,
id: connector.connectorId,
},
]
);
}
}
@ -96,7 +104,8 @@ const checkConnectorsAuthorization = async ({
};
interface EnrichedPushInfo {
pushDate: Date;
latestPushDate: Date;
oldestPushDate: Date;
connectorFieldsUsedInPush: CaseConnector;
}
@ -123,6 +132,12 @@ const getConnectorsInfo = async ({
return createConnectorInfoResult({ actionConnectors, connectors, pushInfo, latestUserAction });
};
interface PushTimeFrameDetails {
connectorId: string;
mostRecentPush: Date;
oldestPush: Date;
}
const getPushInfo = async ({
caseId,
activity,
@ -132,29 +147,39 @@ const getPushInfo = async ({
activity: CaseConnectorActivity[];
userActionService: CaseUserActionService;
}): Promise<Map<string, EnrichedPushInfo>> => {
const pushRequest: PushInfo[] = [];
const pushDetails: PushTimeFrameDetails[] = [];
for (const connectorInfo of activity) {
const pushCreatedAt = getDate(connectorInfo.push?.attributes.created_at);
const mostRecentPushCreatedAt = getDate(connectorInfo.push?.mostRecent.attributes.created_at);
const oldestPushCreatedAt = getDate(connectorInfo.push?.oldest.attributes.created_at);
if (connectorInfo.push != null && pushCreatedAt != null) {
pushRequest.push({ connectorId: connectorInfo.connectorId, date: pushCreatedAt });
if (
connectorInfo.push != null &&
mostRecentPushCreatedAt != null &&
oldestPushCreatedAt != null
) {
pushDetails.push({
connectorId: connectorInfo.connectorId,
mostRecentPush: mostRecentPushCreatedAt,
oldestPush: oldestPushCreatedAt,
});
}
}
const connectorFieldsForPushes = await userActionService.getConnectorFieldsBeforeLatestPush(
caseId,
pushRequest
pushDetails.map((push) => ({ connectorId: push.connectorId, date: push.mostRecentPush }))
);
const enrichedPushInfo = new Map<string, EnrichedPushInfo>();
for (const request of pushRequest) {
const connectorFieldsSO = connectorFieldsForPushes.get(request.connectorId);
for (const pushInfo of pushDetails) {
const connectorFieldsSO = connectorFieldsForPushes.get(pushInfo.connectorId);
const connectorFields = getConnectorInfoFromSavedObject(connectorFieldsSO);
if (connectorFields != null) {
enrichedPushInfo.set(request.connectorId, {
pushDate: request.date,
enrichedPushInfo.set(pushInfo.connectorId, {
latestPushDate: pushInfo.mostRecentPush,
oldestPushDate: pushInfo.oldestPush,
connectorFieldsUsedInPush: connectorFields,
});
}
@ -223,7 +248,8 @@ const createConnectorInfoResult = ({
...connector,
name: connectorDetails.name,
needsToBePushed,
latestPushDate: enrichedPushInfo?.pushDate.toISOString(),
latestPushDate: enrichedPushInfo?.latestPushDate.toISOString(),
oldestPushDate: enrichedPushInfo?.oldestPushDate.toISOString(),
hasBeenPushed: hasBeenPushed(enrichedPushInfo),
};
}
@ -256,7 +282,9 @@ const hasDataToPush = ({
* push fields will be undefined which will not equal the latest connector fields anyway.
*/
!isEqual(connector, pushInfo?.connectorFieldsUsedInPush) ||
(pushInfo != null && latestUserActionDate != null && latestUserActionDate > pushInfo.pushDate)
(pushInfo != null &&
latestUserActionDate != null &&
latestUserActionDate > pushInfo.latestPushDate)
);
};

View file

@ -26,7 +26,13 @@ import {
MAX_DOCS_PER_PAGE,
} from '../../../common/constants';
import { buildFilter, combineFilters } from '../../client/utils';
import type { CaseConnectorActivity, CaseConnectorFields, PushInfo, ServiceContext } from './types';
import type {
CaseConnectorActivity,
CaseConnectorFields,
PushInfo,
PushTimeFrameInfo,
ServiceContext,
} from './types';
import { defaultSortField } from '../../common/utils';
import { UserActionPersister } from './operations/create';
import { UserActionFinder } from './operations/find';
@ -37,15 +43,18 @@ export interface UserActionItem {
references: SavedObjectReference[];
}
interface MostRecentResults {
mostRecent: {
hits: {
total: number;
hits: SavedObjectsRawDoc[];
};
interface TopHits {
hits: {
total: number;
hits: SavedObjectsRawDoc[];
};
}
interface TimeFrameInfo {
mostRecent: TopHits;
oldest: TopHits;
}
interface ConnectorActivityAggsResult {
references: {
connectors: {
@ -55,9 +64,9 @@ interface ConnectorActivityAggsResult {
reverse: {
connectorActivity: {
buckets: {
changeConnector: MostRecentResults;
createCase: MostRecentResults;
pushInfo: MostRecentResults;
changeConnector: TimeFrameInfo;
createCase: TimeFrameInfo;
pushInfo: TimeFrameInfo;
};
};
};
@ -72,7 +81,7 @@ interface ConnectorFieldsBeforePushAggsResult {
connectors: {
reverse: {
ids: {
buckets: Record<string, MostRecentResults>;
buckets: Record<string, TimeFrameInfo>;
};
};
};
@ -381,28 +390,13 @@ export class CaseUserActionService {
);
}
const pushInfo = connectorInfo.reverse.connectorActivity.buckets.pushInfo;
let pushDoc: SavedObject<CaseUserActionInjectedAttributesWithoutActionId> | undefined;
if (pushInfo.mostRecent.hits.hits.length > 0) {
const rawPushDoc = pushInfo.mostRecent.hits.hits[0];
const doc =
this.context.savedObjectsSerializer.rawToSavedObject<CaseUserActionAttributesWithoutConnectorId>(
rawPushDoc
);
pushDoc = transformToExternalModel(
doc,
this.context.persistableStateAttachmentTypeRegistry
);
}
const pushDocs = this.getPushDocs(connectorInfo.reverse.connectorActivity.buckets.pushInfo);
if (fieldsDoc != null) {
caseConnectorInfo.push({
connectorId: connectorInfo.key,
fields: fieldsDoc,
push: pushDoc,
push: pushDocs,
});
} else {
this.context.log.warn(`Unable to find fields for connector id: ${connectorInfo.key}`);
@ -412,6 +406,33 @@ export class CaseUserActionService {
return caseConnectorInfo;
}
private getPushDocs(pushTimeFrameInfo: TimeFrameInfo): PushTimeFrameInfo | undefined {
const mostRecentPushDoc = this.getTopHitsDoc(pushTimeFrameInfo.mostRecent);
const oldestPushDoc = this.getTopHitsDoc(pushTimeFrameInfo.oldest);
if (mostRecentPushDoc && oldestPushDoc) {
return {
mostRecent: mostRecentPushDoc,
oldest: oldestPushDoc,
};
}
}
private getTopHitsDoc(
topHits: TopHits
): SavedObject<CaseUserActionInjectedAttributesWithoutActionId> | undefined {
if (topHits.hits.hits.length > 0) {
const rawPushDoc = topHits.hits.hits[0];
const doc =
this.context.savedObjectsSerializer.rawToSavedObject<CaseUserActionAttributesWithoutConnectorId>(
rawPushDoc
);
return transformToExternalModel(doc, this.context.persistableStateAttachmentTypeRegistry);
}
}
private static buildConnectorInfoAggs(): Record<
string,
estypes.AggregationsAggregationContainer
@ -480,6 +501,18 @@ export class CaseUserActionService {
size: 1,
},
},
oldest: {
top_hits: {
sort: [
{
[`${CASE_USER_ACTION_SAVED_OBJECT}.created_at`]: {
order: 'asc',
},
},
],
size: 1,
},
},
},
},
},

View file

@ -143,10 +143,15 @@ export interface ServiceContext {
auditLogger: AuditLogger;
}
export interface PushTimeFrameInfo {
mostRecent: SavedObject<CaseUserActionInjectedAttributesWithoutActionId>;
oldest: SavedObject<CaseUserActionInjectedAttributesWithoutActionId>;
}
export interface CaseConnectorActivity {
connectorId: string;
fields: SavedObject<CaseUserActionInjectedAttributesWithoutActionId>;
push?: SavedObject<CaseUserActionInjectedAttributesWithoutActionId>;
push?: PushTimeFrameInfo;
}
export type CaseConnectorFields = Map<

View file

@ -233,7 +233,7 @@ export default ({ getService }: FtrProviderContext): void => {
describe('push', () => {
describe('latestPushDate', () => {
it('does not set latestPushDate when the connector has not been used to push', async () => {
it('does not set latestPushDate or oldestPushDate when the connector has not been used to push', async () => {
const { postedCase, connector } = await createCaseWithConnector({
supertest,
serviceNowSimulatorURL,
@ -245,9 +245,10 @@ export default ({ getService }: FtrProviderContext): void => {
expect(Object.keys(connectors).length).to.be(1);
expect(connectors).to.have.property(connector.id);
expect(connectors[connector.id].latestPushDate).to.be(undefined);
expect(connectors[connector.id].oldestPushDate).to.be(undefined);
});
it('sets latestPushDate to the most recent push date', async () => {
it('sets latestPushDate to the most recent push date and oldestPushDate to the first push date', async () => {
const { postedCase, connector } = await createCaseWithConnector({
supertest,
serviceNowSimulatorURL,
@ -278,10 +279,12 @@ export default ({ getService }: FtrProviderContext): void => {
]);
const pushes = userActions.filter((ua) => ua.type === ActionTypes.pushed);
const oldestPush = pushes[0];
const latestPush = pushes[pushes.length - 1];
expect(Object.keys(connectors).length).to.be(1);
expect(connectors[connector.id].latestPushDate).to.eql(latestPush.created_at);
expect(connectors[connector.id].oldestPushDate).to.eql(oldestPush.created_at);
});
});