Verified Commit 31d0ede5 authored by Juhee Lee's avatar Juhee Lee Committed by GitLab
Browse files

chore(duo-agent-platform-v2): add feature toggle button

parent 51babe51
Loading
Loading
Loading
Loading
+36 −9
Original line number Diff line number Diff line
@@ -347,6 +347,16 @@
        "category": "GitLab",
        "icon": "$(add)"
      },
      {
        "command": "gl.duoAgentPlatform.enableNewUi",
        "title": "Use new layout",
        "category": "GitLab"
      },
      {
        "command": "gl.duoAgentPlatform.disableNewUi",
        "title": "Use classic layout",
        "category": "GitLab"
      },
      {
        "command": "gl.mcp.openUserConfig",
        "title": "Open User Settings (JSON)",
@@ -664,6 +674,14 @@
        {
          "command": "gl.openFlowBuilder",
          "when": "false"
        },
        {
          "command": "gl.duoAgentPlatform.enableNewUi",
          "when": "false"
        },
        {
          "command": "gl.duoAgentPlatform.disableNewUi",
          "when": "false"
        }
      ],
      "view/title": [
@@ -696,6 +714,16 @@
          "command": "gl.agenticChat.startNewConversation",
          "when": "false",
          "group": "navigation"
        },
        {
          "command": "gl.duoAgentPlatform.enableNewUi",
          "when": "view == gl.webview.agentic-tabs && gitlab:duoAgentPlatformNewUIConfigured",
          "group": "navigation"
        },
        {
          "command": "gl.duoAgentPlatform.disableNewUi",
          "when": "view == gl.webview.root.duoAgentPlatform && gitlab:duoAgentPlatformNewUIConfigured",
          "group": "navigation"
        }
      ],
      "view/item/context": [
@@ -901,11 +929,6 @@
          "id": "gitlab-agent-platform",
          "title": "GitLab Duo Agent Platform",
          "icon": "assets/icons/gitlab-agent-platform.svg"
        },
        {
          "id": "gitlab-agent-platform-next",
          "title": "GitLab Duo Agent Platform (Duo UI Next)",
          "icon": "$(comment-discussion)"
        }
      ]
    },
@@ -945,10 +968,8 @@
          "type": "webview",
          "id": "gl.webview.agentic-tabs",
          "name": "",
          "when": "config.gitlab.duoAgentPlatform.enabled && (gitlab.featureFlags.duo_agentic_chat || gitlab.featureFlags.duo_workflow)"
        }
      ],
      "gitlab-agent-platform-next": [
          "when": "config.gitlab.duoAgentPlatform.enabled && !gitlab.featureFlags.duoAgentPlatformNext && (gitlab.featureFlags.duo_agentic_chat || gitlab.featureFlags.duo_workflow)"
        },
        {
          "type": "webview",
          "id": "gl.webview.root.duoAgentPlatform",
@@ -1097,6 +1118,12 @@
            "default": false,
            "markdownDescription": "Enable the GitLab Flow Builder feature\n\n_This feature is experimental._",
            "tags": ["experimental"]
          },
          "gitlab.featureFlags.duoAgentPlatformNext": {
            "type": ["boolean", "null"],
            "default": null,
            "markdownDescription": "Show the new GitLab Duo Agent Platform UI in the Agent Platform view. When set to `true` the new UI is shown, when `false` the old UI is shown with a toggle to switch between them, and when unset (`null`) the old UI is shown without a toggle.\n\n_This feature is experimental._",
            "tags": ["experimental"]
          }
        }
      },
+3 −0
Original line number Diff line number Diff line
@@ -36,6 +36,9 @@ export const FEATURE_FLAGS_DEFAULT_VALUES = {
  [FeatureFlag.LsCredentialsSync]: true,
  [FeatureFlag.FormatEdits]: false,
  [FeatureFlag.LsRepositories]: false,
  // The config schema defaults this to `null` (unset) to distinguish "never
  // configured" from an explicit `false`. Here we intentionally coerce that to
  // `false` so the view context key stays boolean; keep both in sync.
  [FeatureFlag.DuoAgentPlatformNext]: false,
};

+71 −0
Original line number Diff line number Diff line
import * as vscode from 'vscode';
import {
  ExtensionConfiguration,
  extensionConfigurationService,
} from '../utils/extension_configuration_service';
import { createFakePartial } from '../test_utils/create_fake_partial';
import { setupDuoAgentPlatformNextContext } from './duo_agent_platform_new_ui_context';
import { FeatureFlag } from './constants';

jest.mock('../utils/extension_configuration_service', () => ({
  extensionConfigurationService: {
    getConfiguration: jest.fn(),
    onChange: jest.fn(),
  },
}));

const CONTEXT_KEY = 'gitlab:duoAgentPlatformNewUIConfigured';

describe('setupDuoAgentPlatformNextContext', () => {
  const mockFlagValue = (value: unknown) => {
    jest.mocked(extensionConfigurationService.getConfiguration).mockReturnValue(
      createFakePartial<ExtensionConfiguration>({
        featureFlags: { [FeatureFlag.DuoAgentPlatformNext]: value as boolean },
      }),
    );
  };

  const latestContextValue = () => {
    const calls = jest
      .mocked(vscode.commands.executeCommand)
      .mock.calls.filter(([command, key]) => command === 'setContext' && key === CONTEXT_KEY);

    return calls.length ? calls[calls.length - 1][2] : undefined;
  };

  const latestChangeListener = () => {
    const { calls } = jest.mocked(extensionConfigurationService.onChange).mock;

    return calls[calls.length - 1][0];
  };

  beforeEach(() => {
    jest.mocked(vscode.commands.executeCommand).mockClear();
    jest.mocked(extensionConfigurationService.onChange).mockClear();
  });

  it.each`
    value        | configured
    ${true}      | ${true}
    ${false}     | ${true}
    ${null}      | ${false}
    ${undefined} | ${false}
  `('publishes configured=$configured when the flag is $value', async ({ value, configured }) => {
    mockFlagValue(value);

    await setupDuoAgentPlatformNextContext();

    expect(latestContextValue()).toBe(configured);
  });

  it('updates the context when the flag configuration changes', async () => {
    mockFlagValue(null);
    await setupDuoAgentPlatformNextContext();
    expect(latestContextValue()).toBe(false);

    mockFlagValue(false);
    await latestChangeListener()(createFakePartial<ExtensionConfiguration>({}));

    expect(latestContextValue()).toBe(true);
  });
});
+26 −0
Original line number Diff line number Diff line
import * as vscode from 'vscode';
import { extensionConfigurationService } from '../utils/extension_configuration_service';
import { FeatureFlag } from './constants';

// Published so the UI toggle only appears when the flag is explicitly set to a
// boolean. When the flag is unset (`null`) the old UI is shown without a toggle.
const CONFIGURED_CONTEXT_KEY = 'gitlab:duoAgentPlatformNewUIConfigured';

const updateContext = () => {
  const value =
    extensionConfigurationService.getConfiguration().featureFlags[FeatureFlag.DuoAgentPlatformNext];
  return vscode.commands.executeCommand(
    'setContext',
    CONFIGURED_CONTEXT_KEY,
    typeof value === 'boolean',
  );
};

// Reuses the same change flow the local feature flag service uses to publish
// the flag's view-driving context key, so the toggle's visibility and the
// active view switch atomically on a live flag change.
export const setupDuoAgentPlatformNextContext = async (): Promise<vscode.Disposable> => {
  await updateContext();

  return extensionConfigurationService.onChange(updateContext);
};
+10 −0
Original line number Diff line number Diff line
import * as vscode from 'vscode';
import { updateConfig } from '../utils/extension_configuration';
import { FeatureFlag } from './constants';

const FEATURE_FLAGS_CONFIG_NAMESPACE = 'gitlab.featureFlags';

export const setLocalFeatureFlag = async (flag: FeatureFlag, enabled: boolean): Promise<void> => {
  const config = vscode.workspace.getConfiguration(FEATURE_FLAGS_CONFIG_NAMESPACE);
  await updateConfig(config, flag, enabled);
};
Loading