Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions Localize/lang/strings.json

Large diffs are not rendered by default.

97 changes: 94 additions & 3 deletions libs/designer/src/lib/core/knowledge/utils/__test__/helper.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { createKnowledgeHub } from '../helper';
import { createKnowledgeHub, deleteKnowledgeHubArtifacts } from '../helper';
import { beforeEach, describe, expect, it, vi } from 'vitest';

const mockExecuteResourceAction = vi.fn();
Expand Down Expand Up @@ -40,10 +40,10 @@ describe('knowledge helper utils', () => {

expect(mockExecuteResourceAction).toHaveBeenCalledTimes(1);
expect(mockExecuteResourceAction).toHaveBeenCalledWith(
`${siteResourceId}/hostruntime/runtime/webhooks/workflow/api/management/knowledgeHub/${groupName}`,
`${siteResourceId}/hostruntime/runtime/webhooks/workflow/api/management/knowledgeHubs/${groupName}`,
'PUT',
{
'api-version': '2025-11-01',
'api-version': '2018-11-01',
'Content-Type': 'application/json',
},
JSON.stringify({ description })
Expand Down Expand Up @@ -128,4 +128,95 @@ describe('knowledge helper utils', () => {
});
});
});

describe('deleteKnowledgeHubArtifacts', () => {
it('should call ResourceService to delete each hub', async () => {
mockExecuteResourceAction.mockResolvedValue({});
const hubs = ['hub1', 'hub2'];
const artifacts = {};

await deleteKnowledgeHubArtifacts(siteResourceId, hubs, artifacts);

expect(mockExecuteResourceAction).toHaveBeenCalledTimes(2);
expect(mockExecuteResourceAction).toHaveBeenCalledWith(
`${siteResourceId}/hostruntime/runtime/webhooks/workflow/api/management/knowledgeHubs/hub1`,
'DELETE',
{ 'api-version': '2018-11-01' }
);
expect(mockExecuteResourceAction).toHaveBeenCalledWith(
`${siteResourceId}/hostruntime/runtime/webhooks/workflow/api/management/knowledgeHubs/hub2`,
'DELETE',
{ 'api-version': '2018-11-01' }
);
});

it('should call ResourceService to delete each artifact', async () => {
mockExecuteResourceAction.mockResolvedValue({});
const hubs: string[] = [];
const artifacts = { artifact1: 'hubA', artifact2: 'hubB' };

await deleteKnowledgeHubArtifacts(siteResourceId, hubs, artifacts);

expect(mockExecuteResourceAction).toHaveBeenCalledTimes(2);
expect(mockExecuteResourceAction).toHaveBeenCalledWith(
`${siteResourceId}/hostruntime/runtime/webhooks/workflow/api/management/knowledgeHubs/hubA/artifacts/artifact1`,
'DELETE',
{ 'api-version': '2018-11-01' }
);
expect(mockExecuteResourceAction).toHaveBeenCalledWith(
`${siteResourceId}/hostruntime/runtime/webhooks/workflow/api/management/knowledgeHubs/hubB/artifacts/artifact2`,
'DELETE',
{ 'api-version': '2018-11-01' }
);
});

it('should delete both hubs and artifacts when both are provided', async () => {
mockExecuteResourceAction.mockResolvedValue({});
const hubs = ['hubToDelete'];
const artifacts = { 'my-artifact': 'hubWithArtifact' };

await deleteKnowledgeHubArtifacts(siteResourceId, hubs, artifacts);

expect(mockExecuteResourceAction).toHaveBeenCalledTimes(2);
expect(mockExecuteResourceAction).toHaveBeenCalledWith(
`${siteResourceId}/hostruntime/runtime/webhooks/workflow/api/management/knowledgeHubs/hubToDelete`,
'DELETE',
{ 'api-version': '2018-11-01' }
);
expect(mockExecuteResourceAction).toHaveBeenCalledWith(
`${siteResourceId}/hostruntime/runtime/webhooks/workflow/api/management/knowledgeHubs/hubWithArtifact/artifacts/my-artifact`,
'DELETE',
{ 'api-version': '2018-11-01' }
);
});

it('should return empty array when no hubs or artifacts are provided', async () => {
const result = await deleteKnowledgeHubArtifacts(siteResourceId, [], {});

expect(mockExecuteResourceAction).not.toHaveBeenCalled();
expect(result).toEqual([]);
});

it('should return all resolved promises', async () => {
const response1 = { deleted: 'hub1' };
const response2 = { deleted: 'artifact1' };
mockExecuteResourceAction.mockResolvedValueOnce(response1).mockResolvedValueOnce(response2);

const hubs = ['hub1'];
const artifacts = { artifact1: 'hubA' };

const result = await deleteKnowledgeHubArtifacts(siteResourceId, hubs, artifacts);

expect(result).toEqual([response1, response2]);
});

it('should reject when any delete operation fails', async () => {
const error = new Error('Delete failed');
mockExecuteResourceAction.mockResolvedValueOnce({}).mockRejectedValueOnce(error);

const hubs = ['hub1', 'hub2'];

await expect(deleteKnowledgeHubArtifacts(siteResourceId, hubs, {})).rejects.toThrow('Delete failed');
});
});
});
87 changes: 17 additions & 70 deletions libs/designer/src/lib/core/knowledge/utils/__test__/queries.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import { describe, test, expect, beforeEach, afterEach, vi } from 'vitest';
import { renderHook, waitFor } from '@testing-library/react';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { useAllKnowledgeHubs, getArtifactsInHub, useConnection, getCosmosDbEndpoint } from '../queries';
import { useAllKnowledgeHubs, useConnection, getCosmosDbEndpoint } from '../queries';
import React from 'react';

const mockExecuteResourceAction = vi.fn();
Expand Down Expand Up @@ -62,16 +62,8 @@ describe('knowledge queries', () => {
{ name: 'hub-a', description: 'First hub' },
];

const mockArtifacts = [
{ name: 'artifact-1', type: 'document' },
{ name: 'artifact-2', type: 'document' },
];

test('should fetch and sort knowledge hubs with their artifacts', async () => {
mockExecuteResourceAction
.mockResolvedValueOnce({ value: mockHubs })
.mockResolvedValueOnce({ value: mockArtifacts })
.mockResolvedValueOnce({ value: [] });
test('should fetch and sort knowledge hubs alphabetically', async () => {
mockGetResource.mockResolvedValueOnce(mockHubs);

const { result } = renderHook(() => useAllKnowledgeHubs(siteResourceId), {
wrapper: createWrapper,
Expand All @@ -81,11 +73,9 @@ describe('knowledge queries', () => {
expect(result.current.isSuccess).toBe(true);
});

expect(mockExecuteResourceAction).toHaveBeenCalledWith(
`${siteResourceId}/hostruntime/runtime/webhooks/workflow/api/management/knowledgeHub`,
'GET',
{ 'api-version': '2025-11-01' }
);
expect(mockGetResource).toHaveBeenCalledWith(`${siteResourceId}/hostruntime/runtime/webhooks/workflow/api/management/knowledgehubs`, {
'api-version': '2018-11-01',
});

// Hubs should be sorted alphabetically
expect(result.current.data?.[0].name).toBe('hub-a');
Expand All @@ -95,7 +85,7 @@ describe('knowledge queries', () => {

test('should return empty array and log error on failure', async () => {
const error = { code: 'NotFound', message: 'Resource not found' };
mockExecuteResourceAction.mockRejectedValue({ error });
mockGetResource.mockRejectedValue({ error });

const { result } = renderHook(() => useAllKnowledgeHubs(siteResourceId), {
wrapper: createWrapper,
Expand All @@ -121,11 +111,11 @@ describe('knowledge queries', () => {

// Query should not run
expect(result.current.fetchStatus).toBe('idle');
expect(mockExecuteResourceAction).not.toHaveBeenCalled();
expect(mockGetResource).not.toHaveBeenCalled();
});

test('should handle empty hubs response', async () => {
mockExecuteResourceAction.mockResolvedValueOnce({ value: [] });
mockGetResource.mockResolvedValueOnce([]);

const { result } = renderHook(() => useAllKnowledgeHubs(siteResourceId), {
wrapper: createWrapper,
Expand All @@ -137,62 +127,19 @@ describe('knowledge queries', () => {

expect(result.current.data).toEqual([]);
});
});

describe('getArtifactsInHub', () => {
test('should fetch and sort artifacts for a hub', async () => {
const hubName = 'test-hub-sort';
const mockArtifacts = [
{ name: 'doc-z', type: 'document' },
{ name: 'doc-a', type: 'document' },
];
mockExecuteResourceAction.mockResolvedValue({ value: mockArtifacts });

const result = await getArtifactsInHub(siteResourceId, hubName);

expect(mockExecuteResourceAction).toHaveBeenCalledWith(
`${siteResourceId}/hostruntime/runtime/webhooks/workflow/api/management/knowledgeHub/${hubName}/knowledgeArtifact`,
'GET',
{ 'api-version': '2025-11-01' }
);

// Artifacts should be sorted alphabetically
expect(result[0].name).toBe('doc-a');
expect(result[1].name).toBe('doc-z');
});

test('should return empty array and log error on failure', async () => {
const hubName = 'test-hub-error';
const error = { code: 'BadRequest', message: 'Invalid hub' };
mockExecuteResourceAction.mockRejectedValue({ error });

const result = await getArtifactsInHub(siteResourceId, hubName);
test('should handle null response', async () => {
mockGetResource.mockResolvedValueOnce(null);

expect(result).toEqual([]);
expect(mockLog).toHaveBeenCalledWith({
level: 'Error',
area: 'KnowledgeHub.listKnowledgeHubArtifacts',
error,
message: `Error while fetching knowledge artifacts for the app: ${siteResourceId}`,
const { result } = renderHook(() => useAllKnowledgeHubs(siteResourceId), {
wrapper: createWrapper,
});
});

test('should handle empty artifacts response', async () => {
const hubName = 'test-hub-empty';
mockExecuteResourceAction.mockResolvedValue({ value: [] });

const result = await getArtifactsInHub(siteResourceId, hubName);

expect(result).toEqual([]);
});

test('should handle response without value property', async () => {
const hubName = 'test-hub-no-value';
mockExecuteResourceAction.mockResolvedValue({});

const result = await getArtifactsInHub(siteResourceId, hubName);
await waitFor(() => {
expect(result.current.isSuccess).toBe(true);
});

expect(result).toEqual([]);
expect(result.current.data).toEqual([]);
});
});

Expand Down
31 changes: 29 additions & 2 deletions libs/designer/src/lib/core/knowledge/utils/helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@ import { getReactQueryClient } from '../../ReactQueryProvider';
export const createKnowledgeHub = async (siteResourceId: string, groupName: string, description: string) => {
try {
const response = await ResourceService().executeResourceAction(
`${siteResourceId}/hostruntime/runtime/webhooks/workflow/api/management/knowledgeHub/${groupName}`,
`${siteResourceId}/hostruntime/runtime/webhooks/workflow/api/management/knowledgeHubs/${groupName}`,
'PUT',
{
'api-version': '2025-11-01',
'api-version': '2018-11-01',
'Content-Type': 'application/json',
},
JSON.stringify({ description })
Expand All @@ -30,3 +30,30 @@ export const createKnowledgeHub = async (siteResourceId: string, groupName: stri
});
}
};

export const deleteKnowledgeHubArtifacts = async (siteResourceId: string, hubs: string[], artifacts: Record<string, string>) => {
const promises: Promise<any>[] = [];

for (const hubName of hubs) {
promises.push(
ResourceService().executeResourceAction(
`${siteResourceId}/hostruntime/runtime/webhooks/workflow/api/management/knowledgeHubs/${hubName}`,
'DELETE',
{ 'api-version': '2018-11-01' }
)
);
}

for (const artifactName of Object.keys(artifacts)) {
const hubName = artifacts[artifactName];
promises.push(
ResourceService().executeResourceAction(
`${siteResourceId}/hostruntime/runtime/webhooks/workflow/api/management/knowledgeHubs/${hubName}/artifacts/${artifactName}`,
'DELETE',
{ 'api-version': '2018-11-01' }
)
);
}

return Promise.all(promises);
};
47 changes: 5 additions & 42 deletions libs/designer/src/lib/core/knowledge/utils/queries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import {
ConnectionService,
equals,
type KnowledgeHub,
type KnowledgeHubArtifact,
type KnowledgeHubExtended,
LogEntryLevel,
LoggerService,
Expand All @@ -24,21 +23,14 @@ export const useAllKnowledgeHubs = (siteResourceId: string) => {
queryKey: ['knowledgehubs', siteResourceId.toLowerCase()],
queryFn: async (): Promise<KnowledgeHubExtended[]> => {
try {
const response: any = await ResourceService().executeResourceAction(
`${siteResourceId}/hostruntime/runtime/webhooks/workflow/api/management/knowledgeHub`,
'GET',
{ 'api-version': '2025-11-01' }
const response: any = await ResourceService().getResource(
`${siteResourceId}/hostruntime/runtime/webhooks/workflow/api/management/knowledgehubs`,
{ 'api-version': '2018-11-01' }
);

const hubs = (response.value ?? []).sort((a: KnowledgeHub, b: KnowledgeHub) => a.name.localeCompare(b.name));
const hubs = (response ?? []).sort((a: KnowledgeHub, b: KnowledgeHub) => a.name.localeCompare(b.name));

const promises: Promise<any>[] = hubs.map((hub: KnowledgeHub) => getArtifactsInHub(siteResourceId, hub.name));

const extendedHubs = await Promise.all(promises);
return hubs.map((hub: KnowledgeHub, index: number) => ({
...hub,
artifacts: extendedHubs[index],
}));
return hubs;
} catch (errorResponse: any) {
const error = errorResponse?.error || {};

Expand All @@ -57,35 +49,6 @@ export const useAllKnowledgeHubs = (siteResourceId: string) => {
});
};

export const getArtifactsInHub = async (siteResourceId: string, hubName: string) => {
const queryClient = getReactQueryClient();

return queryClient.fetchQuery(
['knowledgeartifacts', siteResourceId.toLowerCase(), hubName.toLowerCase()],
async (): Promise<KnowledgeHubArtifact[]> => {
try {
const response: any = await ResourceService().executeResourceAction(
`${siteResourceId}/hostruntime/runtime/webhooks/workflow/api/management/knowledgeHub/${hubName}/knowledgeArtifact`,
'GET',
{ 'api-version': '2025-11-01' }
);

return (response.value ?? []).sort((a: KnowledgeHubArtifact, b: KnowledgeHubArtifact) => a.name.localeCompare(b.name));
} catch (errorResponse: any) {
const error = errorResponse?.error || {};
// For now log the error and return empty list
LoggerService().log({
level: LogEntryLevel.Error,
area: 'KnowledgeHub.listKnowledgeHubArtifacts',
error,
message: `Error while fetching knowledge artifacts for the app: ${siteResourceId}`,
});
return [];
}
}
);
};

export const useConnection = () => {
return useQuery({
queryKey: ['knowledgeconnection'],
Expand Down
Loading
Loading