Files
Automata/packages/editor-ui/src/components/RunDataJson.vue
Alex Grozav 885dba6f12 refactor: Migrate externalHooks mixin to composables (no-changelog) (#7930)
## Summary
Provide details about your pull request and what it adds, fixes, or
changes. Photos and videos are recommended.

As part of NodeView refactor, this PR migrates all externalHooks calls
to `useExternalHooks` composable.

#### How to test the change:
1. Run using env `export N8N_DEPLOYMENT_TYPE=cloud` 
2. Hooks should still run as expected


## Issues fixed
Include links to Github issue or Community forum post or **Linear
ticket**:
> Important in order to close automatically and provide context to
reviewers

https://linear.app/n8n/issue/N8N-6349/externalhooks


## Review / Merge checklist
- [x] PR title and summary are descriptive. **Remember, the title
automatically goes into the changelog. Use `(no-changelog)` otherwise.**
([conventions](https://github.com/n8n-io/n8n/blob/master/.github/pull_request_title_conventions.md))
- [x] [Docs updated](https://github.com/n8n-io/n8n-docs) or follow-up
ticket created.
- [x] Tests included.
> A bug is not considered fixed, unless a test is added to prevent it
from happening again. A feature is not complete without tests.
  >
> *(internal)* You can use Slack commands to trigger [e2e
tests](https://www.notion.so/n8n/How-to-use-Test-Instances-d65f49dfc51f441ea44367fb6f67eb0a?pvs=4#a39f9e5ba64a48b58a71d81c837e8227)
or [deploy test
instance](https://www.notion.so/n8n/How-to-use-Test-Instances-d65f49dfc51f441ea44367fb6f67eb0a?pvs=4#f6a177d32bde4b57ae2da0b8e454bfce)
or [deploy early access version on
Cloud](https://www.notion.so/n8n/Cloudbot-3dbe779836004972b7057bc989526998?pvs=4#fef2d36ab02247e1a0f65a74f6fb534e).
2023-12-06 17:28:09 +02:00

324 lines
7.1 KiB
Vue

<template>
<div :class="$style.jsonDisplay">
<Suspense>
<run-data-json-actions
v-if="!editMode.enabled"
:node="node"
:sessioId="sessionId"
:displayMode="displayMode"
:distanceFromActive="distanceFromActive"
:selectedJsonPath="selectedJsonPath"
:jsonData="jsonData"
:paneType="paneType"
/>
</Suspense>
<draggable
type="mapping"
targetDataKey="mappable"
:disabled="!mappingEnabled"
@dragstart="onDragStart"
@dragend="onDragEnd"
>
<template #preview="{ canDrop, el }">
<MappingPill v-if="el" :html="getShortKey(el)" :can-drop="canDrop" />
</template>
<vue-json-pretty
:data="jsonData"
:deep="10"
:showLength="true"
:selectedValue="selectedJsonPath"
rootPath=""
selectableType="single"
class="json-data"
@update:selectedValue="selectedJsonPath = $event"
>
<template #renderNodeKey="{ node }">
<span
data-target="mappable"
:data-value="getJsonParameterPath(node.path)"
:data-name="node.key"
:data-path="node.path"
:data-depth="node.level"
:class="{
[$style.mappable]: mappingEnabled,
[$style.dragged]: draggingPath === node.path,
}"
v-html="highlightSearchTerm(node.key)"
/>
</template>
<template #renderNodeValue="{ node }">
<span v-if="isNaN(node.index)" v-html="highlightSearchTerm(node.content)" />
<span
v-else
data-target="mappable"
:data-value="getJsonParameterPath(node.path)"
:data-name="getListItemName(node.path)"
:data-path="node.path"
:data-depth="node.level"
:class="{
[$style.mappable]: mappingEnabled,
[$style.dragged]: draggingPath === node.path,
}"
class="ph-no-capture"
v-html="highlightSearchTerm(node.content)"
/>
</template>
</vue-json-pretty>
</draggable>
</div>
</template>
<script lang="ts">
import { defineAsyncComponent, defineComponent, ref } from 'vue';
import type { PropType } from 'vue';
import VueJsonPretty from 'vue-json-pretty';
import type { IDataObject, INodeExecutionData } from 'n8n-workflow';
import Draggable from '@/components/Draggable.vue';
import { executionDataToJson } from '@/utils/nodeTypesUtils';
import { isString } from '@/utils/typeGuards';
import { highlightText, sanitizeHtml } from '@/utils/htmlUtils';
import { shorten } from '@/utils/typesUtils';
import type { INodeUi } from '@/Interface';
import { mapStores } from 'pinia';
import { useNDVStore } from '@/stores/ndv.store';
import MappingPill from './MappingPill.vue';
import { getMappedExpression } from '@/utils/mappingUtils';
import { useWorkflowsStore } from '@/stores/workflows.store';
import { nonExistingJsonPath } from '@/constants';
import { useExternalHooks } from '@/composables/useExternalHooks';
const RunDataJsonActions = defineAsyncComponent(
async () => import('@/components/RunDataJsonActions.vue'),
);
export default defineComponent({
name: 'run-data-json',
components: {
VueJsonPretty,
Draggable,
RunDataJsonActions,
MappingPill,
},
props: {
editMode: {
type: Object as () => { enabled?: boolean; value?: string },
},
sessionId: {
type: String,
},
paneType: {
type: String,
},
node: {
type: Object as PropType<INodeUi>,
},
inputData: {
type: Array as PropType<INodeExecutionData[]>,
},
mappingEnabled: {
type: Boolean,
},
distanceFromActive: {
type: Number,
},
runIndex: {
type: Number,
},
totalRuns: {
type: Number,
},
search: {
type: String,
},
},
setup() {
const externalHooks = useExternalHooks();
const selectedJsonPath = ref(nonExistingJsonPath);
const draggingPath = ref<null | string>(null);
const displayMode = ref('json');
return {
externalHooks,
selectedJsonPath,
draggingPath,
displayMode,
};
},
computed: {
...mapStores(useNDVStore, useWorkflowsStore),
jsonData(): IDataObject[] {
return executionDataToJson(this.inputData);
},
},
methods: {
getShortKey(el: HTMLElement): string {
if (!el) {
return '';
}
return shorten(el.dataset.name || '', 16, 2);
},
getJsonParameterPath(path: string): string {
const subPath = path.replace(/^(\["?\d"?])/, ''); // remove item position
return getMappedExpression({
nodeName: this.node.name,
distanceFromActive: this.distanceFromActive,
path: subPath,
});
},
onDragStart(el: HTMLElement) {
if (el?.dataset.path) {
this.draggingPath = el.dataset.path;
}
this.ndvStore.resetMappingTelemetry();
},
onDragEnd(el: HTMLElement) {
this.draggingPath = null;
const mappingTelemetry = this.ndvStore.mappingTelemetry;
const telemetryPayload = {
src_node_type: this.node.type,
src_field_name: el.dataset.name || '',
src_nodes_back: this.distanceFromActive,
src_run_index: this.runIndex,
src_runs_total: this.totalRuns,
src_field_nest_level: el.dataset.depth || 0,
src_view: 'json',
src_element: el,
success: false,
...mappingTelemetry,
};
setTimeout(() => {
void this.externalHooks.run('runDataJson.onDragEnd', telemetryPayload);
this.$telemetry.track('User dragged data for mapping', telemetryPayload);
}, 1000); // ensure dest data gets set if drop
},
getContent(value: unknown): string {
return isString(value) ? `"${value}"` : JSON.stringify(value);
},
getListItemName(path: string): string {
return path.replace(/^(\["?\d"?]\.?)/g, '');
},
highlightSearchTerm(value: string): string {
return sanitizeHtml(highlightText(this.getContent(value), this.search));
},
},
});
</script>
<style lang="scss" module>
.jsonDisplay {
position: absolute;
top: 0;
left: 0;
padding-left: var(--spacing-s);
right: 0;
overflow-y: auto;
line-height: 1.5;
word-break: normal;
height: 100%;
padding-bottom: var(--spacing-3xl);
&:hover {
/* Shows .actionsGroup element from <run-data-json-actions /> child component */
> div:first-child {
opacity: 1;
}
}
}
.mappable {
cursor: grab;
&:hover {
background-color: var(--color-json-highlight);
}
}
.dragged {
&,
&:hover {
background-color: var(--color-primary-tint-2);
}
}
</style>
<style lang="scss">
.vjs-tree {
color: var(--color-json-default);
}
.vjs-tree-node {
&:hover {
background-color: transparent;
}
&.is-highlight {
background-color: var(--color-json-highlight);
}
}
.vjs-key,
.vjs-value {
> span {
color: var(--color-text-dark);
line-height: 1.7;
border-radius: var(--border-radius-base);
}
}
.vjs-value {
> span {
padding: 0 var(--spacing-5xs) 0 var(--spacing-5xs);
margin-left: var(--spacing-5xs);
}
}
.vjs-tree .vjs-value-null {
&,
span {
color: var(--color-json-null);
}
}
.vjs-tree .vjs-value-boolean {
&,
span {
color: var(--color-json-boolean);
}
}
.vjs-tree .vjs-value-number {
&,
span {
color: var(--color-json-number);
}
}
.vjs-tree .vjs-value-string {
&,
span {
color: var(--color-json-string);
}
}
.vjs-tree .vjs-key {
color: var(--color-json-key);
}
.vjs-tree .vjs-tree__brackets {
color: var(--color-json-brackets);
}
.vjs-tree .vjs-tree__brackets:hover {
color: var(--color-json-brackets-hover);
}
.vjs-tree .vjs-tree__content.has-line {
border-left: 1px dotted var(--color-json-line);
}
</style>