Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 4 additions & 9 deletions packages/components/src/components/DiagramSequence.vue
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
<template v-for="(actor, index) in visuallyReachableActors">
<VActor
:actor="actor"
:key="actorKey(actor)"
:key="actor.id"
:row="1"
:index="index"
:height="diagramSpec.actions.length"
Expand Down Expand Up @@ -61,6 +61,7 @@ import {
Diagram,
Specification,
Action,
getActors,
} from '@appland/sequence-diagram';
import VLoopAction from '@/components/sequence/LoopAction.vue';
import VCallAction from '@/components/sequence/CallAction.vue';
Expand Down Expand Up @@ -121,11 +122,8 @@ export default {
let { appMap } = this.$store.state;
if (!appMap) appMap = this.appMap;

// TODO: optimize for performance building actor priority separately
const specification = Specification.build(appMap, { loops: true });
const diagram = buildDiagram('<an AppMap file>', appMap, specification);

return diagram.actors;
return getActors(appMap, specification);
},
priority() {
const priority = {};
Expand Down Expand Up @@ -194,11 +192,8 @@ export default {
},
},
methods: {
actorKey(actor: Actor): string {
return ['actor', this.diagramSpec.uniqueId, actor.id].join(':');
},
actionKey(action: ActionSpec): string {
return ['action', this.diagramSpec.uniqueId, action.index].join(':');
return `action:${action.index}:${action.action.digest}`;
},
isSelected(action: ActionSpec): boolean {
return !!this.selectedEvents?.find(({ id }) => action.eventIds.includes(id));
Expand Down
20 changes: 16 additions & 4 deletions packages/models/src/event.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/* eslint-disable */
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks like it's been removed upstream already, but please make sure it doesn't make it to main :-)

import { addHiddenProperty, hasProp, transformToJSON } from './util';
import analyzeSQL, { abstractSqlAstJSON } from './sql/analyze';
import normalizeSQL from './sql/normalize';
Expand All @@ -19,6 +20,8 @@ function alias(obj, prop, alias) {
// This class supercedes `CallTree` and `CallNode`. Events are stored in a flat
// array and can also be traversed like a tree via `parent` and `children`.
export default class Event {
static parsedSqlCache = {};

static contentType(...messages) {
const msg = messages.find((message) => (message?.headers || {})['Content-Type']);
if (!msg) {
Expand Down Expand Up @@ -526,10 +529,19 @@ export default class Event {

let properties;
if (sqlQuery) {
const sqlNormalized = abstractSqlAstJSON(sqlQuery, this.sql.database_type)
// Collapse repeated variable literals and parameter tokens (e.g. '?, ?' in an IN clause)
.split(/{"type":"variable"}(?:,{"type":"variable"})*/g)
.join(`{"type":"variable"}`);
let sqlNormalized;
const cacheKey = `${this.sql.database_type}:${sqlQuery}`;
if (!Event.parsedSqlCache[cacheKey]) {
sqlNormalized = abstractSqlAstJSON(sqlQuery, this.sql.database_type)
// Collapse repeated variable literals and parameter tokens (e.g. '?, ?' in an IN clause)
.split(/{"type":"variable"}(?:,{"type":"variable"})*/g)
.join(`{"type":"variable"}`);

Event.parsedSqlCache[cacheKey] = sqlNormalized;
} else {
sqlNormalized = Event.parsedSqlCache[cacheKey];
}

properties = {
event_type: 'sql',
sql_normalized: sqlNormalized,
Expand Down
62 changes: 39 additions & 23 deletions packages/sequence-diagram/src/buildDiagram.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,43 +20,52 @@ import {

const MAX_WINDOW_SIZE = 5;

export default function buildDiagram(
_appmapFile: string,
appmap: AppMap,
specification: Specification
): Diagram {
const events = selectEvents(appmap, specification);
class ActorManager {
private _actorsByCodeObjectId = new Map<string, Actor>();
private _actors: Actor[] = [];

const actors: Actor[] = [];
const actorsByCodeObjectId = new Map<string, Actor>();
constructor(private specification: Specification) {}

const findOrCreateActor = (event: Event): Actor | undefined => {
const actorCodeObject = specification.isIncludedCodeObject(event.codeObject);
findOrCreateActor(event: Event): Actor | undefined {
const actorCodeObject = this.specification.isIncludedCodeObject(event.codeObject);
if (!actorCodeObject) throw Error('actor code object not found');
const order = specification.priorityOf(actorCodeObject);
const order = this.specification.priorityOf(actorCodeObject);

const actorKey = actorCodeObject.fqid;

let actor = actorsByCodeObjectId.get(actorKey);
let actor = this._actorsByCodeObjectId.get(actorKey);
if (actor) return actor;

actor = {
id: actorCodeObject.fqid,
name: actorCodeObject.name,
order,
} as Actor;
actors.push(actor);
actorsByCodeObjectId.set(actorKey, actor);
this._actors.push(actor);
this._actorsByCodeObjectId.set(actorKey, actor);
return actor;
};
}

getSortedActors(): Actor[] {
return this._actors.sort((a, b) => a.order - b.order);
}
}

export default function buildDiagram(
_appmapFile: string,
appmap: AppMap,
specification: Specification
): Diagram {
const events = selectEvents(appmap, specification);
const actorManager = new ActorManager(specification);

function buildRequest(caller?: Event | undefined, callee?: Event): Action | undefined {
if (callee?.httpServerRequest && callee?.httpServerResponse) {
if (!callee.route) throw Error('callee.route not found');
const response = callee.httpServerResponse as any;
return {
nodeType: NodeType.ServerRPC,
callee: findOrCreateActor(callee),
callee: actorManager.findOrCreateActor(callee),
route: callee.route,
status: response.status || response.status_code,
digest: callee.hash,
Expand All @@ -70,8 +79,8 @@ export default function buildDiagram(
const response = callee.httpClientResponse as any;
return {
nodeType: NodeType.ClientRPC,
caller: caller ? findOrCreateActor(caller) : undefined,
callee: findOrCreateActor(callee),
caller: caller ? actorManager.findOrCreateActor(caller) : undefined,
callee: actorManager.findOrCreateActor(callee),
route: callee.route,
status: response.status || response.status_code,
digest: callee.hash,
Expand All @@ -84,8 +93,8 @@ export default function buildDiagram(
const truncatedQuery = callee.sqlQuery.endsWith('...');
return {
nodeType: NodeType.Query,
caller: caller ? findOrCreateActor(caller) : undefined,
callee: findOrCreateActor(callee),
caller: caller ? actorManager.findOrCreateActor(caller) : undefined,
callee: actorManager.findOrCreateActor(callee),
query: callee.sqlQuery,
digest: truncatedQuery ? 'truncatedQuery' : callee.hash,
subtreeDigest: 'undefined',
Expand All @@ -96,8 +105,8 @@ export default function buildDiagram(
} else if (callee) {
return {
nodeType: NodeType.Function,
caller: caller ? findOrCreateActor(caller) : undefined,
callee: findOrCreateActor(callee),
caller: caller ? actorManager.findOrCreateActor(caller) : undefined,
callee: actorManager.findOrCreateActor(callee),
name: callee.codeObject.name,
static: callee.codeObject.static,
digest: callee.hash,
Expand Down Expand Up @@ -216,7 +225,14 @@ export default function buildDiagram(
rootActions.forEach((root) => setParent(root));

return {
actors: actors.sort((a, b) => a.order - b.order),
actors: actorManager.getSortedActors(),
rootActions,
};
}

export function getActors(appmap: AppMap, specification: Specification): Actor[] {
const actorManager = new ActorManager(specification);
const events = selectEvents(appmap, specification);
events.forEach((event) => actorManager.findOrCreateActor(event));
return actorManager.getSortedActors();
}
4 changes: 2 additions & 2 deletions packages/sequence-diagram/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -196,12 +196,12 @@ export enum ValidationResult {
AppMap = 2,
}

import buildDiagram from './buildDiagram';
import buildDiagram, { getActors } from './buildDiagram';
import buildDiffDiagram from './buildDiffDiagram';
import diff from './diff';
import unparseDiagram from './unparseDiagram';
import validateDiagram from './validateDiagram';
export { buildDiagram, buildDiffDiagram, diff, unparseDiagram, validateDiagram };
export { buildDiagram, buildDiffDiagram, diff, unparseDiagram, validateDiagram, getActors };

export enum FormatType {
JSON = 'json',
Expand Down