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:
Oleg Ivaniv 2024-11-12 16:42:21 +01:00
parent 7adfbd236c
commit 44b8fab493
No known key found for this signature in database
4 changed files with 53 additions and 30 deletions

View file

@ -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;
} }

View file

@ -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',

View file

@ -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,

View file

@ -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>