mirror of
https://github.com/tiennm99/DocsGPT.git
synced 2026-10-13 04:14:42 +00:00
Show Linear's tools and the Knowledge choice on one screen after signing in
An MCP preset that also syncs asked about Knowledge first and showed its tools on a separate summary. After signing in it now shows the tools and their permissions, then Sync into Knowledge underneath; Done or Add to Knowledge closes the wizard, with a toast saying what syncs.
This commit is contained in:
1 parent
85175fa8c1
commit
7daa3eb972
2 files changed
+94
-34
No files matched your search
@@ -100,6 +100,9 @@ vi.mock('./LinearPicker', async (importOriginal) => {
|
||||
};
|
||||
});
|
||||
|
||||
import actionToastReducer, {
|
||||
selectActionToast,
|
||||
} from '../notifications/actionToastSlice';
|
||||
import notificationsReducer, {
|
||||
sseEventReceived,
|
||||
} from '../notifications/notificationsSlice';
|
||||
@@ -231,6 +234,7 @@ describe('ConnectWizard', () => {
|
||||
reducer: {
|
||||
connectors: connectorsReducer,
|
||||
notifications: notificationsReducer,
|
||||
actionToast: actionToastReducer,
|
||||
preference: (state = { token: null, selectedDocs: [] }) => state,
|
||||
conversation: (state = {}) => state,
|
||||
},
|
||||
@@ -881,7 +885,7 @@ describe('ConnectWizard', () => {
|
||||
publisher: 'preset',
|
||||
};
|
||||
|
||||
const signIn = async (props = {}) => {
|
||||
const signIn = async (props = {}, onClose = vi.fn()) => {
|
||||
const popup = { closed: false, close: vi.fn(), location: { href: '' } };
|
||||
const open = vi.spyOn(window, 'open').mockReturnValue(popup as never);
|
||||
mcpApi.testMCPConnection.mockResolvedValue({
|
||||
@@ -905,7 +909,7 @@ describe('ConnectWizard', () => {
|
||||
success: true,
|
||||
connection: { tools: [{ ...TELEGRAM_TOOL, display_name: 'Linear' }] },
|
||||
});
|
||||
const store = await render(linear, vi.fn(), props);
|
||||
const store = await render(linear, onClose, props);
|
||||
await click('settings.connectors.wizard.signIn');
|
||||
await act(async () => {
|
||||
store.dispatch(
|
||||
@@ -918,19 +922,28 @@ describe('ConnectWizard', () => {
|
||||
);
|
||||
});
|
||||
open.mockRestore();
|
||||
return store;
|
||||
};
|
||||
|
||||
it('signs in once, then picks teams to sync with the same connection', async () => {
|
||||
// The tools come first on the one screen after signing in.
|
||||
const toolsFirst = () => {
|
||||
const text = document.body.textContent ?? '';
|
||||
const tools = text.indexOf('settings.connectors.wizard.toolsHeading:1');
|
||||
const knowledge = text.indexOf(
|
||||
'settings.connectors.wizard.syncToKnowledge',
|
||||
);
|
||||
return tools >= 0 && (knowledge < 0 || tools < knowledge);
|
||||
};
|
||||
|
||||
it('signs in once, then shows its tools and picks teams to sync on one screen', async () => {
|
||||
service.setup.mockResolvedValue({
|
||||
success: true,
|
||||
tools: [],
|
||||
sources: [{ id: 'src-1', name: 'Linear · Engineering' }],
|
||||
});
|
||||
await signIn({ purpose: 'knowledge' });
|
||||
// The tools came with the sign-in; syncing is the next question.
|
||||
expect(document.body.textContent).toContain(
|
||||
'settings.connectors.wizard.chooseWhatToSync',
|
||||
);
|
||||
const onClose = vi.fn();
|
||||
const store = await signIn({ purpose: 'knowledge' }, onClose);
|
||||
expect(toolsFirst()).toBe(true);
|
||||
await click('pick-team');
|
||||
await click('modals.uploadDoc.train');
|
||||
const [id, body] = service.setup.mock.calls[0];
|
||||
@@ -949,14 +962,18 @@ describe('ConnectWizard', () => {
|
||||
config: optionsToConfig(DEFAULT_RETRIEVAL_OPTIONS),
|
||||
},
|
||||
});
|
||||
// The summary still lists the tools from the sign-in.
|
||||
expect(document.body.textContent).toContain(
|
||||
'settings.connectors.wizard.toolsHeading:1',
|
||||
);
|
||||
// Nothing more to show: the modal closes and says what syncs.
|
||||
expect(onClose).toHaveBeenCalled();
|
||||
expect(
|
||||
selectActionToast(
|
||||
store.getState() as Parameters<typeof selectActionToast>[0],
|
||||
)?.variant,
|
||||
).toBe('success');
|
||||
});
|
||||
|
||||
it('can skip syncing and keep only the tools', async () => {
|
||||
await signIn({ purpose: 'knowledge' });
|
||||
const onClose = vi.fn();
|
||||
await signIn({ purpose: 'knowledge' }, onClose);
|
||||
const train = Array.from(
|
||||
document.body.querySelectorAll<HTMLButtonElement>('button'),
|
||||
).find((b) => b.textContent?.trim() === 'modals.uploadDoc.train');
|
||||
@@ -964,26 +981,18 @@ describe('ConnectWizard', () => {
|
||||
expect(train?.disabled).toBe(true);
|
||||
await click('settings.connectors.wizard.skip');
|
||||
expect(service.setup).not.toHaveBeenCalled();
|
||||
expect(document.body.textContent).toContain(
|
||||
'settings.connectors.wizard.doneTitle',
|
||||
);
|
||||
expect(document.body.textContent).toContain(
|
||||
'settings.connectors.wizard.toolsHeading:1',
|
||||
);
|
||||
expect(onClose).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('asks before syncing on a plain connect, and keeps the tools', async () => {
|
||||
await signIn();
|
||||
it('asks about Knowledge under its tools on a plain connect', async () => {
|
||||
const onClose = vi.fn();
|
||||
await signIn({}, onClose);
|
||||
expect(toolsFirst()).toBe(true);
|
||||
expect(knowledgeSwitch()!.getAttribute('aria-checked')).toBe('false');
|
||||
expect(document.body.textContent).not.toContain('pick-team');
|
||||
await click('settings.connectors.wizard.continue');
|
||||
await click('settings.connectors.wizard.done');
|
||||
expect(service.setup).not.toHaveBeenCalled();
|
||||
expect(document.body.textContent).toContain(
|
||||
'settings.connectors.wizard.toolsHeading:1',
|
||||
);
|
||||
expect(document.body.textContent).toContain(
|
||||
'settings.connectors.wizard.syncLater',
|
||||
);
|
||||
expect(onClose).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('goes straight to the summary after signing in again', async () => {
|
||||
|
||||
@@ -23,6 +23,7 @@ import { Button } from '../components/ui/button';
|
||||
import { FormField } from '../components/ui/form-field';
|
||||
import { Input } from '../components/ui/input';
|
||||
import { Modal, ModalActions } from '../components/ui/modal';
|
||||
import { SectionHeader } from '../components/ui/section-header';
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
@@ -49,6 +50,7 @@ import RetrievalOptions, {
|
||||
type RetrievalOptionsValue,
|
||||
} from '../settings/components/RetrievalOptions';
|
||||
import useRetrievalAvailability from '../settings/components/useRetrievalAvailability';
|
||||
import { showActionToast } from '../notifications/actionToastSlice';
|
||||
import type { AppDispatch } from '../store';
|
||||
import { formatCount } from '../utils/dateTimeUtils';
|
||||
import { ACCOUNT_NAME_MAX } from './accounts';
|
||||
@@ -233,6 +235,10 @@ export default function ConnectWizard({
|
||||
// done step shows what it can do.
|
||||
const isMcpPreset =
|
||||
connector.auth_kind === 'mcp_oauth' && !!connector.mcp_url;
|
||||
// An MCP preset that also syncs (Linear) has its tools from the sign-in:
|
||||
// one screen shows them first, then asks about Knowledge, and finishing
|
||||
// it closes the wizard with no separate summary.
|
||||
const toolsFirst = isMcpPreset && mode === 'connect' && tools.length > 0;
|
||||
const mcp = useMcpOAuth();
|
||||
const mcpConfig = (): McpOAuthConfig => ({
|
||||
server_url: connector.mcp_url ?? '',
|
||||
@@ -387,7 +393,8 @@ export default function ConnectWizard({
|
||||
if (!connectionId) return;
|
||||
if (!wantsTools && !syncReady) {
|
||||
// Connected, nothing more to set up.
|
||||
setStep('done');
|
||||
if (toolsFirst) finish();
|
||||
else setStep('done');
|
||||
return;
|
||||
}
|
||||
setPending(true);
|
||||
@@ -426,6 +433,22 @@ export default function ConnectWizard({
|
||||
if (wantsTools) setTools(data.tools ?? []);
|
||||
setSources(data.sources ?? []);
|
||||
refresh();
|
||||
if (toolsFirst) {
|
||||
const count = (data.sources ?? []).length;
|
||||
dispatch(
|
||||
showActionToast({
|
||||
variant: 'success',
|
||||
message: t('settings.connectors.wizard.doneSources', {
|
||||
sources: t('settings.connectors.wizard.sourcesCount', {
|
||||
count,
|
||||
formatted: formatCount(count),
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
);
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
setStep('done');
|
||||
} catch {
|
||||
setError(t('settings.connectors.wizard.syncFailed'));
|
||||
@@ -615,6 +638,25 @@ export default function ConnectWizard({
|
||||
<AlertDescription>{error}</AlertDescription>
|
||||
</Alert>
|
||||
)}
|
||||
{toolsFirst && connectionId && (
|
||||
<section className="flex flex-col gap-3">
|
||||
<SectionHeader
|
||||
as="h3"
|
||||
size="xs"
|
||||
title={t('settings.connectors.wizard.toolsHeading', {
|
||||
count: toolCount,
|
||||
formatted: formatCount(toolCount),
|
||||
})}
|
||||
/>
|
||||
{tools.map((tool) => (
|
||||
<ToolPermissions
|
||||
key={tool.id}
|
||||
connectionId={connectionId}
|
||||
tool={tool}
|
||||
/>
|
||||
))}
|
||||
</section>
|
||||
)}
|
||||
{(offerTools || askSync) && (
|
||||
<SettingRows>
|
||||
{offerTools && (
|
||||
@@ -810,9 +852,14 @@ export default function ConnectWizard({
|
||||
interpolation: { escapeValue: false },
|
||||
})
|
||||
: step === 'setup'
|
||||
? offerTools || !syncing
|
||||
? t('settings.connectors.wizard.chooseWhatToSetUp')
|
||||
: t('settings.connectors.wizard.chooseWhatToSync')
|
||||
? toolsFirst
|
||||
? t('settings.connectors.wizard.doneTitle', {
|
||||
name,
|
||||
interpolation: { escapeValue: false },
|
||||
})
|
||||
: offerTools || !syncing
|
||||
? t('settings.connectors.wizard.chooseWhatToSetUp')
|
||||
: t('settings.connectors.wizard.chooseWhatToSync')
|
||||
: t('settings.connectors.wizard.doneTitle', {
|
||||
name,
|
||||
interpolation: { escapeValue: false },
|
||||
@@ -853,12 +900,16 @@ export default function ConnectWizard({
|
||||
) : step === 'setup' && !offerTools && !syncing ? (
|
||||
// Only the Knowledge question, answered no: Skip would do the same.
|
||||
<Button type="button" size="lg" shape="pill" onClick={addSource}>
|
||||
{t('settings.connectors.wizard.continue')}
|
||||
{toolsFirst
|
||||
? t('settings.connectors.wizard.done')
|
||||
: t('settings.connectors.wizard.continue')}
|
||||
</Button>
|
||||
) : step === 'setup' ? (
|
||||
<ModalActions
|
||||
cancelLabel={t('settings.connectors.wizard.skip')}
|
||||
onCancel={() => (mode === 'sync' ? onClose() : setStep('done'))}
|
||||
onCancel={() =>
|
||||
mode === 'sync' ? onClose() : toolsFirst ? finish() : setStep('done')
|
||||
}
|
||||
submitLabel={
|
||||
offerTools
|
||||
? t('settings.connectors.wizard.continue')
|
||||
|
||||
Reference in new issue
Block a user