mirror of
https://github.com/n8n-io/n8n.git
synced 2024-11-15 00:54:06 -08:00
Implement feature flag for workflow evaluation experiment
- Add feature flag check - Update UI to show/hide evaluation tab - Fetch tags for test definitions - Handle routing when feature is disabled - Cleanup console logs
This commit is contained in:
parent
7adfbd236c
commit
44b8fab493
|
@ -9,6 +9,7 @@ import {
|
||||||
PLACEHOLDER_EMPTY_WORKFLOW_ID,
|
PLACEHOLDER_EMPTY_WORKFLOW_ID,
|
||||||
STICKY_NODE_TYPE,
|
STICKY_NODE_TYPE,
|
||||||
VIEWS,
|
VIEWS,
|
||||||
|
WORKFLOW_EVALUATION_EXPERIMENT,
|
||||||
} from '@/constants';
|
} from '@/constants';
|
||||||
import { useI18n } from '@/composables/useI18n';
|
import { useI18n } from '@/composables/useI18n';
|
||||||
import { useNDVStore } from '@/stores/ndv.store';
|
import { useNDVStore } from '@/stores/ndv.store';
|
||||||
|
@ -17,6 +18,7 @@ import { useUIStore } from '@/stores/ui.store';
|
||||||
import { useWorkflowsStore } from '@/stores/workflows.store';
|
import { useWorkflowsStore } from '@/stores/workflows.store';
|
||||||
import { useExecutionsStore } from '@/stores/executions.store';
|
import { useExecutionsStore } from '@/stores/executions.store';
|
||||||
import { usePushConnection } from '@/composables/usePushConnection';
|
import { usePushConnection } from '@/composables/usePushConnection';
|
||||||
|
import { usePostHog } from '@/stores/posthog.store';
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
|
@ -27,17 +29,24 @@ const uiStore = useUIStore();
|
||||||
const sourceControlStore = useSourceControlStore();
|
const sourceControlStore = useSourceControlStore();
|
||||||
const workflowsStore = useWorkflowsStore();
|
const workflowsStore = useWorkflowsStore();
|
||||||
const executionsStore = useExecutionsStore();
|
const executionsStore = useExecutionsStore();
|
||||||
|
const posthogStore = usePostHog();
|
||||||
|
|
||||||
const activeHeaderTab = ref(MAIN_HEADER_TABS.WORKFLOW);
|
const activeHeaderTab = ref(MAIN_HEADER_TABS.WORKFLOW);
|
||||||
const workflowToReturnTo = ref('');
|
const workflowToReturnTo = ref('');
|
||||||
const executionToReturnTo = ref('');
|
const executionToReturnTo = ref('');
|
||||||
const dirtyState = ref(false);
|
const dirtyState = ref(false);
|
||||||
|
|
||||||
const tabBarItems = computed(() => [
|
const tabBarItems = computed(() => {
|
||||||
{ value: MAIN_HEADER_TABS.WORKFLOW, label: locale.baseText('generic.editor') },
|
const items = [
|
||||||
{ value: MAIN_HEADER_TABS.EXECUTIONS, label: locale.baseText('generic.executions') },
|
{ value: MAIN_HEADER_TABS.WORKFLOW, label: locale.baseText('generic.editor') },
|
||||||
{ value: MAIN_HEADER_TABS.EVALUATION, label: locale.baseText('generic.tests') },
|
{ value: MAIN_HEADER_TABS.EXECUTIONS, label: locale.baseText('generic.executions') },
|
||||||
]);
|
];
|
||||||
|
|
||||||
|
if (posthogStore.isFeatureEnabled(WORKFLOW_EVALUATION_EXPERIMENT)) {
|
||||||
|
items.push({ value: MAIN_HEADER_TABS.EVALUATION, label: locale.baseText('generic.tests') });
|
||||||
|
}
|
||||||
|
return items;
|
||||||
|
});
|
||||||
|
|
||||||
const activeNode = computed(() => ndvStore.activeNode);
|
const activeNode = computed(() => ndvStore.activeNode);
|
||||||
const hideMenuBar = computed(() =>
|
const hideMenuBar = computed(() =>
|
||||||
|
@ -68,7 +77,6 @@ onMounted(async () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
function syncTabsWithRoute(to: RouteLocation, from?: RouteLocation): void {
|
function syncTabsWithRoute(to: RouteLocation, from?: RouteLocation): void {
|
||||||
console.log('🚀 ~ syncTabsWithRoute ~ to.name:', to.name);
|
|
||||||
if (to.name === VIEWS.WORKFLOW_EVALUATION) {
|
if (to.name === VIEWS.WORKFLOW_EVALUATION) {
|
||||||
activeHeaderTab.value = MAIN_HEADER_TABS.EVALUATION;
|
activeHeaderTab.value = MAIN_HEADER_TABS.EVALUATION;
|
||||||
}
|
}
|
||||||
|
|
|
@ -702,6 +702,8 @@ export const EXPERIMENTS_TO_TRACK = [
|
||||||
CREDENTIAL_DOCS_EXPERIMENT.name,
|
CREDENTIAL_DOCS_EXPERIMENT.name,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
export const WORKFLOW_EVALUATION_EXPERIMENT = '025_workflow_evaluation';
|
||||||
|
|
||||||
export const MFA_FORM = {
|
export const MFA_FORM = {
|
||||||
MFA_TOKEN: 'MFA_TOKEN',
|
MFA_TOKEN: 'MFA_TOKEN',
|
||||||
MFA_RECOVERY_CODE: 'MFA_RECOVERY_CODE',
|
MFA_RECOVERY_CODE: 'MFA_RECOVERY_CODE',
|
||||||
|
|
|
@ -3,6 +3,8 @@ import { computed, ref } from 'vue';
|
||||||
import { useRootStore } from './root.store';
|
import { useRootStore } from './root.store';
|
||||||
import { createTestDefinitionsApi } from '@/api/evaluations.ee';
|
import { createTestDefinitionsApi } from '@/api/evaluations.ee';
|
||||||
import type { ITestDefinition } from '@/api/evaluations.ee';
|
import type { ITestDefinition } from '@/api/evaluations.ee';
|
||||||
|
import { usePostHog } from './posthog.store';
|
||||||
|
import { WORKFLOW_EVALUATION_EXPERIMENT } from '@/constants';
|
||||||
|
|
||||||
export const useEvaluationsStore = defineStore(
|
export const useEvaluationsStore = defineStore(
|
||||||
'evaluations',
|
'evaluations',
|
||||||
|
@ -13,6 +15,7 @@ export const useEvaluationsStore = defineStore(
|
||||||
const fetchedAll = ref(false);
|
const fetchedAll = ref(false);
|
||||||
|
|
||||||
// Store instances
|
// Store instances
|
||||||
|
const posthogStore = usePostHog();
|
||||||
const rootStore = useRootStore();
|
const rootStore = useRootStore();
|
||||||
const testDefinitionsApi = createTestDefinitionsApi();
|
const testDefinitionsApi = createTestDefinitionsApi();
|
||||||
|
|
||||||
|
@ -21,13 +24,17 @@ export const useEvaluationsStore = defineStore(
|
||||||
return Object.values(testDefinitionsById.value).sort((a, b) => a.name.localeCompare(b.name));
|
return Object.values(testDefinitionsById.value).sort((a, b) => a.name.localeCompare(b.name));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Enable with `window.featureFlags.override('025_workflow_evaluation', true)`
|
||||||
|
const isFeatureEnabled = computed(() =>
|
||||||
|
posthogStore.isFeatureEnabled(WORKFLOW_EVALUATION_EXPERIMENT),
|
||||||
|
);
|
||||||
|
|
||||||
const isLoading = computed(() => loading.value);
|
const isLoading = computed(() => loading.value);
|
||||||
|
|
||||||
const hasTestDefinitions = computed(() => Object.keys(testDefinitionsById.value).length > 0);
|
const hasTestDefinitions = computed(() => Object.keys(testDefinitionsById.value).length > 0);
|
||||||
|
|
||||||
// Methods
|
// Methods
|
||||||
const setAllTestDefinitions = (definitions: ITestDefinition[]) => {
|
const setAllTestDefinitions = (definitions: ITestDefinition[]) => {
|
||||||
console.log('🚀 ~ setAllTestDefinitions ~ definitions:', definitions);
|
|
||||||
testDefinitionsById.value = definitions.reduce(
|
testDefinitionsById.value = definitions.reduce(
|
||||||
(acc: Record<number, ITestDefinition>, def: ITestDefinition) => {
|
(acc: Record<number, ITestDefinition>, def: ITestDefinition) => {
|
||||||
acc[def.id] = def;
|
acc[def.id] = def;
|
||||||
|
@ -76,7 +83,7 @@ export const useEvaluationsStore = defineStore(
|
||||||
rootStore.restApiContext,
|
rootStore.restApiContext,
|
||||||
{ includeScopes },
|
{ includeScopes },
|
||||||
);
|
);
|
||||||
console.log('🚀 ~ fetchAll ~ retrievedDefinitions:', retrievedDefinitions);
|
|
||||||
setAllTestDefinitions(retrievedDefinitions.testDefinitions);
|
setAllTestDefinitions(retrievedDefinitions.testDefinitions);
|
||||||
return retrievedDefinitions;
|
return retrievedDefinitions;
|
||||||
} finally {
|
} finally {
|
||||||
|
@ -131,6 +138,7 @@ export const useEvaluationsStore = defineStore(
|
||||||
allTestDefinitions,
|
allTestDefinitions,
|
||||||
isLoading,
|
isLoading,
|
||||||
hasTestDefinitions,
|
hasTestDefinitions,
|
||||||
|
isFeatureEnabled,
|
||||||
|
|
||||||
// Methods
|
// Methods
|
||||||
fetchAll,
|
fetchAll,
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed } from 'vue';
|
import { ref, computed, onMounted } from 'vue';
|
||||||
import { useRouter } from 'vue-router';
|
import { useRouter } from 'vue-router';
|
||||||
import { VIEWS } from '@/constants';
|
import { VIEWS } from '@/constants';
|
||||||
import { useEvaluationsStore } from '@/stores/evaluations.store.ee';
|
import { useEvaluationsStore } from '@/stores/evaluations.store.ee';
|
||||||
|
@ -8,8 +8,10 @@ import { useI18n } from '@/composables/useI18n';
|
||||||
import EmptyState from '@/components/WorkflowEvaluation/ListEvaluation/EmptyState.vue';
|
import EmptyState from '@/components/WorkflowEvaluation/ListEvaluation/EmptyState.vue';
|
||||||
import TestsList from '@/components/WorkflowEvaluation/ListEvaluation/TestsList.vue';
|
import TestsList from '@/components/WorkflowEvaluation/ListEvaluation/TestsList.vue';
|
||||||
import type { TestExecution, TestListItem } from '@/components/WorkflowEvaluation/types';
|
import type { TestExecution, TestListItem } from '@/components/WorkflowEvaluation/types';
|
||||||
|
import { useAnnotationTagsStore } from '@/stores/tags.store';
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
const tagsStore = useAnnotationTagsStore();
|
||||||
const evaluationsStore = useEvaluationsStore();
|
const evaluationsStore = useEvaluationsStore();
|
||||||
const isLoading = ref(false);
|
const isLoading = ref(false);
|
||||||
const toast = useToast();
|
const toast = useToast();
|
||||||
|
@ -20,25 +22,21 @@ const tests = computed<TestListItem[]>(() => {
|
||||||
id: test.id,
|
id: test.id,
|
||||||
name: test.name,
|
name: test.name,
|
||||||
tagName: test.annotationTagId ? getTagName(test.annotationTagId) : '',
|
tagName: test.annotationTagId ? getTagName(test.annotationTagId) : '',
|
||||||
testCases: 0, // This should come from the API
|
testCases: 0, // TODO: This should come from the API
|
||||||
execution: getTestExecution(test.id),
|
execution: getTestExecution(test.id),
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
const hasTests = computed(() => tests.value.length > 0);
|
const hasTests = computed(() => tests.value.length > 0);
|
||||||
|
const allTags = computed(() => tagsStore.allTags);
|
||||||
|
|
||||||
// Mock function to get tag name - replace with actual tag lookup
|
|
||||||
function getTagName(tagId: string) {
|
function getTagName(tagId: string) {
|
||||||
const tags = {
|
const matchingTag = allTags.value.find((t) => t.id === tagId);
|
||||||
tag1: 'marketing',
|
|
||||||
tag2: 'SupportOps',
|
return matchingTag?.name ?? '';
|
||||||
};
|
|
||||||
return tags[tagId] || '';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mock function to get test execution data - replace with actual API call
|
// TODO: Replace with actual API call once implemented
|
||||||
function getTestExecution(testId: number): TestExecution {
|
function getTestExecution(_testId: number): TestExecution {
|
||||||
console.log('🚀 ~ getTestExecution ~ testId:', testId);
|
|
||||||
// Mock data - replace with actual data from your API
|
|
||||||
const mockExecutions = {
|
const mockExecutions = {
|
||||||
12: {
|
12: {
|
||||||
lastRun: 'an hour ago',
|
lastRun: 'an hour ago',
|
||||||
|
@ -63,24 +61,18 @@ function onCreateTest() {
|
||||||
|
|
||||||
function onRunTest(testId: number) {
|
function onRunTest(testId: number) {
|
||||||
console.log('Running test:', testId);
|
console.log('Running test:', testId);
|
||||||
// Implement test run logic
|
// TODO: Implement test run logic
|
||||||
}
|
}
|
||||||
|
|
||||||
function onViewDetails(testId: number) {
|
function onViewDetails(testId: number) {
|
||||||
console.log('Viewing details for test:', testId);
|
|
||||||
void router.push({ name: VIEWS.WORKFLOW_EVALUATION_EDIT, params: { testId } });
|
void router.push({ name: VIEWS.WORKFLOW_EVALUATION_EDIT, params: { testId } });
|
||||||
// Implement navigation to test details
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function onEditTest(testId: number) {
|
function onEditTest(testId: number) {
|
||||||
console.log('Editing test:', testId);
|
|
||||||
void router.push({ name: VIEWS.WORKFLOW_EVALUATION_EDIT, params: { testId } });
|
void router.push({ name: VIEWS.WORKFLOW_EVALUATION_EDIT, params: { testId } });
|
||||||
// Implement edit navigation
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function onDeleteTest(testId: number) {
|
async function onDeleteTest(testId: number) {
|
||||||
console.log('Deleting test:', testId);
|
|
||||||
// Implement delete logic
|
|
||||||
await evaluationsStore.deleteById(testId);
|
await evaluationsStore.deleteById(testId);
|
||||||
|
|
||||||
toast.showMessage({
|
toast.showMessage({
|
||||||
|
@ -90,17 +82,30 @@ async function onDeleteTest(testId: number) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load initial data
|
// Load initial data
|
||||||
async function loadTests() {
|
async function loadInitialData() {
|
||||||
isLoading.value = true;
|
isLoading.value = true;
|
||||||
try {
|
try {
|
||||||
|
await tagsStore.fetchAll();
|
||||||
await evaluationsStore.fetchAll();
|
await evaluationsStore.fetchAll();
|
||||||
} finally {
|
} finally {
|
||||||
isLoading.value = false;
|
isLoading.value = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load tests on mount
|
onMounted(() => {
|
||||||
void loadTests();
|
if (!evaluationsStore.isFeatureEnabled) {
|
||||||
|
toast.showMessage({
|
||||||
|
// message: "Feature not enabled",
|
||||||
|
title: 'Feature not enabled',
|
||||||
|
type: 'error',
|
||||||
|
});
|
||||||
|
void router.push({
|
||||||
|
name: VIEWS.WORKFLOW,
|
||||||
|
params: { name: router.currentRoute.value.params.name },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
void loadInitialData();
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
Loading…
Reference in a new issue