diff --git a/.github/workflows/DotNET-build.yml b/.github/workflows/DotNET-build.yml
index 41953c96..7a39ec1d 100644
--- a/.github/workflows/DotNET-build.yml
+++ b/.github/workflows/DotNET-build.yml
@@ -30,7 +30,22 @@ jobs:
ubuntu-latest:
name: ubuntu-latest
runs-on: ubuntu-latest
+
steps:
+ # temporary from here...
+ - name: Setup dotnet 6
+ uses: actions/setup-dotnet@v4
+ with:
+ dotnet-version: '6.0.x'
+ - name: Setup dotnet 7
+ uses: actions/setup-dotnet@v4
+ with:
+ dotnet-version: '7.0.x'
+ - name: Setup dotnet 8
+ uses: actions/setup-dotnet@v4
+ with:
+ dotnet-version: '8.0.x'
+ # ...to here
- uses: actions/setup-java@v4
with:
@@ -52,7 +67,7 @@ jobs:
SonarToken: ${{ secrets.SONAR_TOKEN }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- - uses: actions/upload-artifact@v3
+ - uses: actions/upload-artifact@v4
if: always()
with:
name: test-results
diff --git a/.github/workflows/JS-build.yml b/.github/workflows/JS-build.yml
index 36e0f6f0..233bd1b4 100644
--- a/.github/workflows/JS-build.yml
+++ b/.github/workflows/JS-build.yml
@@ -31,7 +31,6 @@ jobs:
name: ubuntu-latest
runs-on: ubuntu-latest
steps:
-
- uses: actions/setup-java@v4
with:
distribution: 'temurin'
@@ -68,7 +67,7 @@ jobs:
-Dsonar.test.inclusions=src/Serilog.Ui.Web/src/__tests__/**/*
-Dsonar.javascript.lcov.reportPaths=./src/Serilog.Ui.Web/src/reports/coverage/lcov.info
- - uses: actions/upload-artifact@v3
+ - uses: actions/upload-artifact@v4
if: always()
with:
name: test-results
diff --git a/.github/workflows/Release.yml b/.github/workflows/Release.yml
index 3da37125..962e9b37 100644
--- a/.github/workflows/Release.yml
+++ b/.github/workflows/Release.yml
@@ -20,22 +20,22 @@ on:
workflow_dispatch:
inputs:
ElasticProvider:
- description: "Elastic Provider"
+ description: 'Elastic Provider'
required: true
MongoProvider:
- description: "Mongo Provider"
+ description: 'Mongo Provider'
required: true
MsSqlProvider:
- description: "Ms Sql Provider"
+ description: 'Ms Sql Provider'
required: true
MySqlProvider:
- description: "My Sql Provider"
+ description: 'My Sql Provider'
required: true
PostgresProvider:
- description: "Postgres Provider"
+ description: 'Postgres Provider'
required: true
Ui:
- description: "Ui"
+ description: 'Ui'
required: true
jobs:
@@ -43,7 +43,6 @@ jobs:
name: ubuntu-latest
runs-on: ubuntu-latest
steps:
-
- uses: actions/setup-java@v4
with:
distribution: 'temurin'
diff --git a/.nuke/build.schema.json b/.nuke/build.schema.json
index 7a201556..12486dce 100644
--- a/.nuke/build.schema.json
+++ b/.nuke/build.schema.json
@@ -1,119 +1,68 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
- "properties": {
- "Configuration": {
- "type": "string",
- "description": "Configuration to build - Default is 'Debug' (local) or 'Release' (server)",
- "enum": [
- "Debug",
- "Release"
- ]
- },
- "ElasticProvider": {
- "type": "string"
- },
- "MongoProvider": {
- "type": "string"
- },
- "MsSqlProvider": {
- "type": "string"
- },
- "MySqlProvider": {
- "type": "string"
- },
- "NugetApiKey": {
- "type": "string",
- "default": "Secrets must be entered via 'nuke :secrets [profile]'"
- },
- "PostgresProvider": {
- "type": "string"
- },
- "Solution": {
- "type": "string",
- "description": "Path to a solution file that is automatically loaded"
- },
- "SonarToken": {
- "type": "string",
- "default": "Secrets must be entered via 'nuke :secrets [profile]'"
- },
- "SonarTokenUi": {
- "type": "string",
- "default": "Secrets must be entered via 'nuke :secrets [profile]'"
- },
- "Ui": {
- "type": "string"
- }
- },
+ "$ref": "#/definitions/build",
+ "title": "Build Schema",
"definitions": {
- "Host": {
- "type": "string",
- "enum": [
- "AppVeyor",
- "AzurePipelines",
- "Bamboo",
- "Bitbucket",
- "Bitrise",
- "GitHubActions",
- "GitLab",
- "Jenkins",
- "Rider",
- "SpaceAutomation",
- "TeamCity",
- "Terminal",
- "TravisCI",
- "VisualStudio",
- "VSCode"
- ]
- },
- "ExecutableTarget": {
- "type": "string",
- "enum": [
- "Backend_Clean",
- "Backend_Compile",
- "Backend_Report_Ci",
- "Backend_Restore",
- "Backend_SonarScan_End",
- "Backend_SonarScan_Start",
- "Backend_Test",
- "Backend_Test_Ci",
- "Clean",
- "Frontend_Build",
- "Frontend_Clean",
- "Frontend_Restore",
- "Frontend_Tests",
- "Frontend_Tests_Ci",
- "Pack",
- "Publish"
- ]
- },
- "Verbosity": {
- "type": "string",
- "description": "",
- "enum": [
- "Verbose",
- "Normal",
- "Minimal",
- "Quiet"
- ]
- },
- "NukeBuild": {
+ "build": {
+ "type": "object",
"properties": {
+ "Configuration": {
+ "type": "string",
+ "description": "Configuration to build - Default is 'Debug' (local) or 'Release' (server)",
+ "enum": [
+ "Debug",
+ "Release"
+ ]
+ },
"Continue": {
"type": "boolean",
"description": "Indicates to continue a previously failed build attempt"
},
+ "ElasticProvider": {
+ "type": "string"
+ },
"Help": {
"type": "boolean",
"description": "Shows the help text for this build assembly"
},
"Host": {
+ "type": "string",
"description": "Host for execution. Default is 'automatic'",
- "$ref": "#/definitions/Host"
+ "enum": [
+ "AppVeyor",
+ "AzurePipelines",
+ "Bamboo",
+ "Bitbucket",
+ "Bitrise",
+ "GitHubActions",
+ "GitLab",
+ "Jenkins",
+ "Rider",
+ "SpaceAutomation",
+ "TeamCity",
+ "Terminal",
+ "TravisCI",
+ "VisualStudio",
+ "VSCode"
+ ]
+ },
+ "MongoProvider": {
+ "type": "string"
+ },
+ "MsSqlProvider": {
+ "type": "string"
+ },
+ "MySqlProvider": {
+ "type": "string"
},
"NoLogo": {
"type": "boolean",
"description": "Disables displaying the NUKE logo"
},
+ "NugetApiKey": {
+ "type": "string",
+ "default": "Secrets must be entered via 'nuke :secrets [profile]'"
+ },
"Partition": {
"type": "string",
"description": "Partition to use on CI"
@@ -122,6 +71,9 @@
"type": "boolean",
"description": "Shows the execution plan (HTML)"
},
+ "PostgresProvider": {
+ "type": "string"
+ },
"Profile": {
"type": "array",
"description": "Defines the profiles to load",
@@ -137,22 +89,78 @@
"type": "array",
"description": "List of targets to be skipped. Empty list skips all dependencies",
"items": {
- "$ref": "#/definitions/ExecutableTarget"
+ "type": "string",
+ "enum": [
+ "Backend_Clean",
+ "Backend_Compile",
+ "Backend_Report_Ci",
+ "Backend_Restore",
+ "Backend_SonarScan_End",
+ "Backend_SonarScan_Start",
+ "Backend_Test",
+ "Backend_Test_Ci",
+ "Clean",
+ "Frontend_Build",
+ "Frontend_Clean",
+ "Frontend_Restore",
+ "Frontend_Tests",
+ "Frontend_Tests_Ci",
+ "Pack",
+ "Publish"
+ ]
}
},
+ "Solution": {
+ "type": "string",
+ "description": "Path to a solution file that is automatically loaded"
+ },
+ "SonarToken": {
+ "type": "string",
+ "default": "Secrets must be entered via 'nuke :secrets [profile]'"
+ },
+ "SonarTokenUi": {
+ "type": "string",
+ "default": "Secrets must be entered via 'nuke :secrets [profile]'"
+ },
"Target": {
"type": "array",
"description": "List of targets to be invoked. Default is '{default_target}'",
"items": {
- "$ref": "#/definitions/ExecutableTarget"
+ "type": "string",
+ "enum": [
+ "Backend_Clean",
+ "Backend_Compile",
+ "Backend_Report_Ci",
+ "Backend_Restore",
+ "Backend_SonarScan_End",
+ "Backend_SonarScan_Start",
+ "Backend_Test",
+ "Backend_Test_Ci",
+ "Clean",
+ "Frontend_Build",
+ "Frontend_Clean",
+ "Frontend_Restore",
+ "Frontend_Tests",
+ "Frontend_Tests_Ci",
+ "Pack",
+ "Publish"
+ ]
}
},
+ "Ui": {
+ "type": "string"
+ },
"Verbosity": {
+ "type": "string",
"description": "Logging verbosity during build execution. Default is 'Normal'",
- "$ref": "#/definitions/Verbosity"
+ "enum": [
+ "Minimal",
+ "Normal",
+ "Quiet",
+ "Verbose"
+ ]
}
}
}
- },
- "$ref": "#/definitions/NukeBuild"
+ }
}
diff --git a/build/CustomGithubActionsAttribute.cs b/build/CustomGithubActionsAttribute.cs
index 37a36ecb..9e984889 100644
--- a/build/CustomGithubActionsAttribute.cs
+++ b/build/CustomGithubActionsAttribute.cs
@@ -89,7 +89,7 @@ public override void Write(CustomFileWriter writer)
{
writer.WriteLine(); // empty line to separate tasks
- writer.WriteLine("- uses: actions/upload-artifact@v3");
+ writer.WriteLine("- uses: actions/upload-artifact@v4");
using (writer.Indent())
{
diff --git a/build/_build.csproj b/build/_build.csproj
index 0027e160..414edcea 100644
--- a/build/_build.csproj
+++ b/build/_build.csproj
@@ -25,8 +25,8 @@
-
-
-
+
+
+
diff --git a/samples/WebApi/WebApi.csproj b/samples/WebApi/WebApi.csproj
index 574a2a7a..1d87072a 100644
--- a/samples/WebApi/WebApi.csproj
+++ b/samples/WebApi/WebApi.csproj
@@ -22,7 +22,7 @@
diff --git a/samples/WebApp/WebApp.csproj b/samples/WebApp/WebApp.csproj
index 4771bc46..85205044 100644
--- a/samples/WebApp/WebApp.csproj
+++ b/samples/WebApp/WebApp.csproj
@@ -38,7 +38,7 @@
diff --git a/src/Serilog.Ui.Web/Serilog.Ui.Web.csproj b/src/Serilog.Ui.Web/Serilog.Ui.Web.csproj
index 02173b62..09fc749b 100644
--- a/src/Serilog.Ui.Web/Serilog.Ui.Web.csproj
+++ b/src/Serilog.Ui.Web/Serilog.Ui.Web.csproj
@@ -4,7 +4,7 @@
Serilog.UI
net6.0;net7.0;net8.0
latest
- 3.1.0
+ 3.1.1
diff --git a/src/Serilog.Ui.Web/package.json b/src/Serilog.Ui.Web/package.json
index 99959803..6d3ca716 100644
--- a/src/Serilog.Ui.Web/package.json
+++ b/src/Serilog.Ui.Web/package.json
@@ -14,62 +14,62 @@
"test:ui": "vitest --ui --reporter=default"
},
"dependencies": {
- "@fontsource/mononoki": "^5.1.0",
- "@mantine/core": "^7.13.2",
- "@mantine/dates": "^7.13.2",
- "@mantine/hooks": "^7.13.2",
- "@mantine/notifications": "^7.13.2",
- "@tabler/icons-react": "^3.19.0",
- "@tanstack/react-query": "^5.59.8",
+ "@fontsource/mononoki": "^5.2.5",
+ "@mantine/core": "^7.17.2",
+ "@mantine/dates": "^7.17.2",
+ "@mantine/hooks": "^7.17.2",
+ "@mantine/notifications": "^7.17.2",
+ "@tabler/icons-react": "^3.31.0",
+ "@tanstack/react-query": "^5.68.0",
"dayjs": "^1.11.13",
- "jose": "^5.9.3",
+ "jose": "^6.0.10",
"react": "^18.3.1",
"react-dom": "^18.3.1",
- "react-hook-form": "^7.53.0",
- "react-router-dom": "^6.26.2",
- "xml-formatter": "^3.6.3"
+ "react-hook-form": "^7.54.2",
+ "react-router": "^7.3.0",
+ "xml-formatter": "^3.6.4"
},
"devDependencies": {
- "@faker-js/faker": "^9.0.3",
+ "@faker-js/faker": "^9.6.0",
"@testing-library/dom": "^10.4.0",
- "@testing-library/jest-dom": "^6.5.0",
- "@testing-library/react": "^16.0.1",
- "@testing-library/user-event": "^14.5.2",
- "@types/node": "^22.7.5",
+ "@testing-library/jest-dom": "^6.6.3",
+ "@testing-library/react": "^16.2.0",
+ "@testing-library/user-event": "^14.6.1",
+ "@types/node": "^22.13.10",
"@types/react": "^18.3.11",
"@types/react-dom": "^18.3.0",
- "@vitejs/plugin-react-swc": "^3.7.1",
- "@vitest/coverage-istanbul": "^2.1.2",
- "@vitest/ui": "^2.1.2",
- "@welldone-software/why-did-you-render": "^8.0.3",
+ "@vitejs/plugin-react-swc": "^3.8.0",
+ "@vitest/coverage-istanbul": "^3.0.8",
+ "@vitest/ui": "^3.0.8",
+ "@welldone-software/why-did-you-render": "^10.0.1",
"eslint": "^8.57.0",
- "eslint-config-prettier": "^9.1.0",
+ "eslint-config-prettier": "^10.1.1",
"eslint-plugin-html": "^8.1.2",
"eslint-plugin-import": "^2.31.0",
- "eslint-plugin-jsx-a11y": "^6.10.0",
- "eslint-plugin-prettier": "^5.2.1",
- "eslint-plugin-promise": "^7.1.0",
- "eslint-plugin-react": "^7.37.1",
- "eslint-plugin-react-hooks": "^4.6.2",
- "eslint-plugin-testing-library": "^6.3.0",
+ "eslint-plugin-jsx-a11y": "^6.10.2",
+ "eslint-plugin-prettier": "^5.2.3",
+ "eslint-plugin-promise": "^7.2.1",
+ "eslint-plugin-react": "^7.37.4",
+ "eslint-plugin-react-hooks": "^5.2.0",
+ "eslint-plugin-testing-library": "^7.1.1",
"eslint-plugin-vitest": "^0.5.4",
"eslint-plugin-vitest-globals": "^1.5.0",
- "happy-dom": "^15.10.2",
- "msw": "^2.4.9",
- "postcss": "^8.4.47",
+ "happy-dom": "^17.4.4",
+ "msw": "^2.7.3",
+ "postcss": "^8.5.3",
"postcss-preset-mantine": "^1.17.0",
"postcss-simple-vars": "^7.0.1",
- "prettier": "^3.3.3",
+ "prettier": "^3.5.3",
"prettier-plugin-organize-imports": "^4.1.0",
- "shiki": "^1.22.0",
+ "shiki": "^3.2.1",
"testing-library-selector": "^0.3.1",
- "typescript": "^5.6.3",
- "typescript-eslint": "^8.8.1",
- "vite": "^5.4.8",
- "vite-plugin-checker": "^0.8.0",
- "vite-plugin-mkcert": "^1.17.6",
- "vite-tsconfig-paths": "^5.0.1",
- "vitest": "^2.1.2",
+ "typescript": "^5.8.2",
+ "typescript-eslint": "^8.26.1",
+ "vite": "^6.2.2",
+ "vite-plugin-checker": "^0.9.0",
+ "vite-plugin-mkcert": "^1.17.8",
+ "vite-tsconfig-paths": "^5.1.4",
+ "vitest": "^3.0.8",
"vitest-sonar-reporter": "^2.0.0"
},
"engines": {
diff --git a/src/Serilog.Ui.Web/src/__tests__/App.spec.tsx b/src/Serilog.Ui.Web/src/__tests__/App.spec.tsx
index 45374215..a9ef1900 100644
--- a/src/Serilog.Ui.Web/src/__tests__/App.spec.tsx
+++ b/src/Serilog.Ui.Web/src/__tests__/App.spec.tsx
@@ -2,7 +2,7 @@ import { render, within } from '@testing-library/react';
import { act, render as customRender } from '__tests__/_setup/testing-utils';
import App from 'app/App';
import { routes } from 'app/routes';
-import { RouterProvider, createMemoryRouter } from 'react-router-dom';
+import { RouterProvider, createMemoryRouter } from 'react-router';
import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest';
const propsMock = {
diff --git a/src/Serilog.Ui.Web/src/__tests__/components/ErrorPage.spec.tsx b/src/Serilog.Ui.Web/src/__tests__/components/ErrorPage.spec.tsx
index f37f7960..b9817259 100644
--- a/src/Serilog.Ui.Web/src/__tests__/components/ErrorPage.spec.tsx
+++ b/src/Serilog.Ui.Web/src/__tests__/components/ErrorPage.spec.tsx
@@ -4,7 +4,7 @@ import { ReactNode } from 'react';
import { describe, expect, it, vi } from 'vitest';
const mockFn = vi.hoisted(() => vi.fn());
-vi.mock('react-router-dom', async () => {
+vi.mock('react-router', async () => {
return {
Link: ({ children }: { children: ReactNode }) => {children},
isRouteErrorResponse: mockFn,
diff --git a/src/Serilog.Ui.Web/src/__tests__/components/HomePageNotAuthorized.spec.tsx b/src/Serilog.Ui.Web/src/__tests__/components/HomePageNotAuthorized.spec.tsx
index 0fa4db14..4b2776fc 100644
--- a/src/Serilog.Ui.Web/src/__tests__/components/HomePageNotAuthorized.spec.tsx
+++ b/src/Serilog.Ui.Web/src/__tests__/components/HomePageNotAuthorized.spec.tsx
@@ -7,7 +7,7 @@ const propsMock = {
setAuthenticatedFromAccessDenied: vi.fn(),
blockHomeAccess: false,
};
-vi.mock('react-router-dom', async () => {
+vi.mock('react-router', async () => {
return {
Navigate: () => Move to
,
};
diff --git a/src/Serilog.Ui.Web/src/__tests__/components/Index.spec.tsx b/src/Serilog.Ui.Web/src/__tests__/components/Index.spec.tsx
index 151b0478..0d646165 100644
--- a/src/Serilog.Ui.Web/src/__tests__/components/Index.spec.tsx
+++ b/src/Serilog.Ui.Web/src/__tests__/components/Index.spec.tsx
@@ -1,6 +1,6 @@
import { act, render, within } from '__tests__/_setup/testing-utils';
import { Index } from 'app/components/Index';
-import { MemoryRouter } from 'react-router-dom';
+import { MemoryRouter } from 'react-router';
import { describe, expect, it, vi } from 'vitest';
const propsMock = {
diff --git a/src/Serilog.Ui.Web/src/__tests__/components/Search/PagingLeftColumn.spec.tsx b/src/Serilog.Ui.Web/src/__tests__/components/Search/PagingLeftColumn.spec.tsx
index 28d39c49..3dc6c5fa 100644
--- a/src/Serilog.Ui.Web/src/__tests__/components/Search/PagingLeftColumn.spec.tsx
+++ b/src/Serilog.Ui.Web/src/__tests__/components/Search/PagingLeftColumn.spec.tsx
@@ -11,7 +11,6 @@ const defaultReturn: SearchResult = {
};
const mockQueryLogs = {
data: defaultReturn,
- refetch: vi.fn(),
};
vi.mock('../../../app/hooks/useQueryLogs', () => {
return {
@@ -66,7 +65,6 @@ describe('Paging', () => {
expect(mockChangePage).toHaveBeenCalledTimes(1);
expect(inputEntriesPerPage.value).toBe('25');
- expect(mockQueryLogs.refetch).toHaveBeenCalledTimes(2);
});
it('changes sort on value', async () => {
@@ -88,7 +86,6 @@ describe('Paging', () => {
await userEvent.selectOptions(listBox, selectOption);
expect(sortOn.value).toBe('Level');
- expect(mockQueryLogs.refetch).toHaveBeenCalledTimes(2);
});
it('changes sort by value', async () => {
@@ -103,7 +100,6 @@ describe('Paging', () => {
await userEvent.click(sortBy);
expect(sortBy.innerHTML).toContain('sort-ascending');
- expect(mockQueryLogs.refetch).toHaveBeenCalledTimes(2);
});
it('disables the sort on field', async () => {
diff --git a/src/Serilog.Ui.Web/src/__tests__/components/Search/PagingRightColumn.spec.tsx b/src/Serilog.Ui.Web/src/__tests__/components/Search/PagingRightColumn.spec.tsx
index 7ba8b9c3..d18ba967 100644
--- a/src/Serilog.Ui.Web/src/__tests__/components/Search/PagingRightColumn.spec.tsx
+++ b/src/Serilog.Ui.Web/src/__tests__/components/Search/PagingRightColumn.spec.tsx
@@ -12,7 +12,6 @@ const defaultReturn: () => SearchResult = () => ({
});
const mockQueryLogs = {
data: defaultReturn(),
- refetch: vi.fn(),
};
vi.mock('../../../app/hooks/useQueryLogs', async () => {
return {
@@ -47,7 +46,6 @@ describe('PagingRightColumn', () => {
expect(screen.getByRole('button', { name: 'pagination-dialog' })).not.toBeDisabled();
field.value = '2';
- expect(mockQueryLogs.refetch).toHaveBeenCalledTimes(1);
});
it('calls onChange on pagination button click', async () => {
@@ -75,6 +73,5 @@ describe('PagingRightColumn', () => {
await userEvent.click(setPage);
expect(field.onChange).toHaveBeenCalledTimes(1);
- expect(mockQueryLogs.refetch).toHaveBeenCalledTimes(1);
});
});
diff --git a/src/Serilog.Ui.Web/src/__tests__/components/Search/Search.spec.tsx b/src/Serilog.Ui.Web/src/__tests__/components/Search/Search.spec.tsx
index 329519b4..d181e05d 100644
--- a/src/Serilog.Ui.Web/src/__tests__/components/Search/Search.spec.tsx
+++ b/src/Serilog.Ui.Web/src/__tests__/components/Search/Search.spec.tsx
@@ -1,5 +1,6 @@
import { dbKeysMock } from '__tests__/_setup/mocks/samples';
import {
+ act,
fireEvent,
render,
screen,
@@ -8,12 +9,13 @@ import {
within,
} from '__tests__/_setup/testing-utils';
import Search from 'app/components/Search/Search';
+import { searchFormInitialValues } from 'app/hooks/useSearchForm';
import * as logs from 'app/queries/logs';
import { IAuthPropertiesStorageKeys } from 'app/util/auth';
import dayjs from 'dayjs';
import objectSupport from 'dayjs/plugin/objectSupport';
import { byLabelText, byRole } from 'testing-library-selector';
-import { AuthType } from 'types/types';
+import { AuthType, DispatchedCustomEvents } from 'types/types';
import { beforeAll, describe, expect, it, vi } from 'vitest';
dayjs.extend(objectSupport);
@@ -73,147 +75,229 @@ describe('Search', () => {
expect(screen.getByRole('form', { name: 'search-logs-form' })).toBeInTheDocument();
});
- it('fetch with selected table', async () => {
- const spy = vi.spyOn(logs, 'fetchLogs');
+ describe('fields', () => {
+ it('fetch with selected table', async () => {
+ const spy = vi.spyOn(logs, 'fetchLogs');
- render(, AuthType.Jwt);
+ render(, AuthType.Jwt);
- await selectTable();
+ await selectTable();
- expect(spy).toHaveBeenLastCalledWith(
- expect.objectContaining({
- table: dbKeysMock[0],
- }),
- expect.any(Object),
- '',
- );
- });
+ expect(spy).toHaveBeenLastCalledWith(
+ expect.objectContaining({
+ table: dbKeysMock[0],
+ }),
+ expect.any(Object),
+ '',
+ );
+ });
- it('fetch with selected level', async () => {
- const spy = vi.spyOn(logs, 'fetchLogs');
+ it('fetch with selected level', async () => {
+ const spy = vi.spyOn(logs, 'fetchLogs');
- render(, AuthType.Jwt);
+ render(, AuthType.Jwt);
- await selectTable();
+ await selectTable();
- const levelInput = ui.textbox('Level').get();
+ const levelInput = ui.textbox('Level').get();
- await userEvent.click(levelInput);
+ await userEvent.click(levelInput);
- const selectOption = ui.options(ui.listbox)[2];
+ const selectOption = ui.options(ui.listbox)[2];
- await userEvent.selectOptions(ui.listbox.get(), selectOption);
+ await userEvent.selectOptions(ui.listbox.get(), selectOption);
- await userEvent.click(ui.submit.get());
+ await userEvent.click(ui.submit.get());
- expect(spy).toHaveBeenLastCalledWith(
- expect.objectContaining({
- table: dbKeysMock[0],
- level: selectOption.getAttribute('value'),
- }),
- expect.any(Object),
- '',
- );
+ expect(spy).toHaveBeenLastCalledWith(
+ expect.objectContaining({
+ table: dbKeysMock[0],
+ level: selectOption.getAttribute('value'),
+ }),
+ expect.any(Object),
+ '',
+ );
+ });
+
+ it('fetch with selected text search', async () => {
+ const spy = vi.spyOn(logs, 'fetchLogs');
+
+ render(, AuthType.Jwt);
+
+ await selectTable();
+
+ const levelInput = ui.textbox('Search').get();
+
+ await userEvent.type(levelInput, 'my search');
+
+ await userEvent.click(ui.submit.get());
+
+ expect(spy).toHaveBeenLastCalledWith(
+ expect.objectContaining({
+ table: dbKeysMock[0],
+ search: 'my search',
+ }),
+ expect.any(Object),
+ '',
+ );
+ });
+
+ it('fetch with selected start date', async () => {
+ const spy = vi.spyOn(logs, 'fetchLogs');
+
+ render(, AuthType.Jwt);
+
+ await selectTable();
+
+ // open end date modal
+ await userEvent.click(ui.start_date.get());
+
+ // click sample day button
+ await userEvent.click(ui.day_btn(sampleDate.format('DD MMMM YYYY')).get());
+ // click sample time button
+ // using fireEvent due to userEvent.type not supporting seconds
+ // ref https://github.com/testing-library/user-event/blob/d0362796a33c2d39713998f82ae309020c37b385/tests/event/input.ts#L298
+ fireEvent.change(ui.time_btn('start-time-input').get(), {
+ target: { value: '15:15:30' },
+ });
+
+ // click submit date button
+ const submitBtn = within(screen.getByRole('dialog'))
+ .getAllByRole('button')
+ .slice(-1);
+ await userEvent.click(submitBtn[0]);
+ await waitForElementToBeRemoved(screen.queryByRole('dialog'));
+
+ // submit request
+ await userEvent.click(ui.submit.get());
+
+ expect(spy).toHaveBeenLastCalledWith(
+ expect.objectContaining({
+ table: dbKeysMock[0],
+ startDate: expect.toBeSameDate(sampleDate, { unit: 'seconds' }),
+ }),
+ expect.any(Object),
+ '',
+ );
+ });
+
+ it('fetch with selected end date', async () => {
+ const spy = vi.spyOn(logs, 'fetchLogs');
+
+ render(, AuthType.Jwt);
+
+ await selectTable();
+
+ // open end date modal
+ await userEvent.click(ui.end_date.get());
+
+ // click sample day button
+ await userEvent.click(ui.day_btn(sampleDate.format('DD MMMM YYYY')).get());
+ // click sample time button
+ // using fireEvent due to userEvent.type not supporting seconds
+ // ref https://github.com/testing-library/user-event/blob/d0362796a33c2d39713998f82ae309020c37b385/tests/event/input.ts#L298
+ fireEvent.change(ui.time_btn('end-time-input').get(), {
+ target: { value: '15:15:30' },
+ });
+
+ // click submit date button
+ const submitBtn = within(screen.getByRole('dialog'))
+ .getAllByRole('button')
+ .slice(-1);
+ await userEvent.click(submitBtn[0]);
+ await waitForElementToBeRemoved(screen.queryByRole('dialog'));
+
+ // submit request
+ await userEvent.click(ui.submit.get());
+
+ expect(spy).toHaveBeenCalledWith(
+ expect.objectContaining({
+ table: dbKeysMock[0],
+ endDate: expect.toBeSameDate(sampleDate, { unit: 'seconds' }),
+ }),
+ expect.any(Object),
+ '',
+ );
+ });
});
- it('fetch with selected text search', async () => {
+ it('refetches automatically when page is more than one', async () => {
const spy = vi.spyOn(logs, 'fetchLogs');
+ searchFormInitialValues.page = 2;
render(, AuthType.Jwt);
await selectTable();
-
const levelInput = ui.textbox('Search').get();
-
await userEvent.type(levelInput, 'my search');
-
await userEvent.click(ui.submit.get());
- expect(spy).toHaveBeenLastCalledWith(
+ expect(spy).toHaveBeenNthCalledWith(
+ 1,
+ expect.objectContaining({
+ table: dbKeysMock[0],
+ search: '',
+ page: 2,
+ }),
+ expect.any(Object),
+ '',
+ );
+ expect(spy).toHaveBeenNthCalledWith(
+ 2,
expect.objectContaining({
table: dbKeysMock[0],
search: 'my search',
+ level: null,
+ page: 1,
}),
expect.any(Object),
'',
);
+ searchFormInitialValues.page = 1;
});
- it('fetch with selected start date', async () => {
+ it('invokes onRefetch on submit', async () => {
const spy = vi.spyOn(logs, 'fetchLogs');
- render(, AuthType.Jwt);
+ const onRefetchMock = vi.fn();
+ render(, AuthType.Jwt);
await selectTable();
- // open end date modal
- await userEvent.click(ui.start_date.get());
-
- // click sample day button
- await userEvent.click(ui.day_btn(sampleDate.format('DD MMMM YYYY')).get());
- // click sample time button
- // using fireEvent due to userEvent.type not supporting seconds
- // ref https://github.com/testing-library/user-event/blob/d0362796a33c2d39713998f82ae309020c37b385/tests/event/input.ts#L298
- fireEvent.change(ui.time_btn('start-time-input').get(), {
- target: { value: '15:15:30' },
- });
+ const levelInput = ui.textbox('Search').get();
- // click submit date button
- const submitBtn = within(screen.getByRole('dialog')).getAllByRole('button').slice(-1);
- await userEvent.click(submitBtn[0]);
- await waitForElementToBeRemoved(screen.queryByRole('dialog'));
+ await userEvent.type(levelInput, 'my search');
- // submit request
await userEvent.click(ui.submit.get());
- expect(spy).toHaveBeenLastCalledWith(
+ expect(spy).toHaveBeenNthCalledWith(
+ 2,
expect.objectContaining({
table: dbKeysMock[0],
- startDate: expect.toBeSameDate(sampleDate, { unit: 'seconds' }),
+ search: 'my search',
+ level: null,
}),
expect.any(Object),
'',
);
+ expect(onRefetchMock).toHaveBeenCalledOnce();
});
- it('fetch with selected end date', async () => {
- const spy = vi.spyOn(logs, 'fetchLogs');
-
+ it('invokes reset on RemoveTableKey event, removing the table value', async () => {
+ const tableInput = ui.textbox('Table').get;
render(, AuthType.Jwt);
await selectTable();
+ expect(tableInput()).toHaveValue(dbKeysMock[0]);
- // open end date modal
- await userEvent.click(ui.end_date.get());
-
- // click sample day button
- await userEvent.click(ui.day_btn(sampleDate.format('DD MMMM YYYY')).get());
- // click sample time button
- // using fireEvent due to userEvent.type not supporting seconds
- // ref https://github.com/testing-library/user-event/blob/d0362796a33c2d39713998f82ae309020c37b385/tests/event/input.ts#L298
- fireEvent.change(ui.time_btn('end-time-input').get(), {
- target: { value: '15:15:30' },
+ act(() => {
+ document.dispatchEvent(new CustomEvent(DispatchedCustomEvents.RemoveTableKey));
});
- // click submit date button
- const submitBtn = within(screen.getByRole('dialog')).getAllByRole('button').slice(-1);
- await userEvent.click(submitBtn[0]);
- await waitForElementToBeRemoved(screen.queryByRole('dialog'));
-
- // submit request
- await userEvent.click(ui.submit.get());
-
- expect(spy).toHaveBeenLastCalledWith(
- expect.objectContaining({
- table: dbKeysMock[0],
- endDate: expect.toBeSameDate(sampleDate, { unit: 'seconds' }),
- }),
- expect.any(Object),
- '',
- );
+ expect(tableInput()).toHaveValue('');
});
- it('clean inputs', async () => {
+ it('clean inputs calling refetch', async () => {
const spy = vi.spyOn(logs, 'fetchLogs');
render(, AuthType.Jwt);
@@ -226,7 +310,8 @@ describe('Search', () => {
await userEvent.click(ui.submit.get());
- expect(spy).toHaveBeenLastCalledWith(
+ expect(spy).toHaveBeenNthCalledWith(
+ 2,
expect.objectContaining({
table: dbKeysMock[0],
search: 'my search',
@@ -238,7 +323,8 @@ describe('Search', () => {
await userEvent.click(ui.clear.get());
- expect(spy).toHaveBeenLastCalledWith(
+ expect(spy).toHaveBeenNthCalledWith(
+ 3,
expect.objectContaining({
table: dbKeysMock[0],
search: '',
diff --git a/src/Serilog.Ui.Web/src/__tests__/components/ShellStructure/FilterButton.spec.tsx b/src/Serilog.Ui.Web/src/__tests__/components/ShellStructure/FilterButton.spec.tsx
index b7de2e92..e58718df 100644
--- a/src/Serilog.Ui.Web/src/__tests__/components/ShellStructure/FilterButton.spec.tsx
+++ b/src/Serilog.Ui.Web/src/__tests__/components/ShellStructure/FilterButton.spec.tsx
@@ -44,21 +44,28 @@ describe('FilterButton', () => {
});
});
- it('clears search state and refetch data', async () => {
- render();
+ it.each([
+ { resetRes: true, times: 1 },
+ { resetRes: false, times: 0 },
+ ])(
+ 'clears search state and refetch data if reset returns $resetRes',
+ async ({ resetRes, times }) => {
+ useMocks.reset.mockImplementationOnce(() => resetRes);
- const filterBtn = screen.getByRole('button');
- expect(filterBtn).toBeInTheDocument();
+ render();
- await userEvent.click(filterBtn);
+ const filterBtn = screen.getByRole('button');
- expect(useMocks.refetch).toHaveBeenCalledOnce();
+ expect(filterBtn).toBeInTheDocument();
- await userEvent.click(screen.getByRole('button', { name: 'reset filters' }));
+ await userEvent.click(filterBtn);
- expect(useMocks.reset).toHaveBeenCalledOnce();
- expect(useMocks.refetch).toHaveBeenCalledTimes(2);
- });
+ await userEvent.click(screen.getByRole('button', { name: 'reset filters' }));
+
+ expect(useMocks.reset).toHaveBeenCalledOnce();
+ expect(useMocks.refetch).toHaveBeenCalledTimes(times);
+ },
+ );
it('closes modal on resize', async () => {
render();
diff --git a/src/Serilog.Ui.Web/src/__tests__/hooks/useAuthProperties.spec.ts b/src/Serilog.Ui.Web/src/__tests__/hooks/useAuthProperties.spec.ts
index bd7ed7de..1253756c 100644
--- a/src/Serilog.Ui.Web/src/__tests__/hooks/useAuthProperties.spec.ts
+++ b/src/Serilog.Ui.Web/src/__tests__/hooks/useAuthProperties.spec.ts
@@ -1,7 +1,8 @@
import { act, renderHook } from '__tests__/_setup/testing-utils';
import { useAuthProperties } from 'app/hooks/useAuthProperties';
import { IAuthPropertiesStorageKeys } from 'app/util/auth';
-import { describe, expect, it } from 'vitest';
+import { DispatchedCustomEvents } from 'types/types';
+import { describe, expect, it, vi } from 'vitest';
describe('useAuthProperties', () => {
it('renders with default values', () => {
@@ -52,6 +53,8 @@ describe('useAuthProperties', () => {
});
it('clears auth state', () => {
+ const mock = vi.fn()
+ document.addEventListener(DispatchedCustomEvents.RemoveTableKey, mock)
sessionStorage.setItem(IAuthPropertiesStorageKeys.jwt_bearerToken, 'token');
sessionStorage.setItem(IAuthPropertiesStorageKeys.basic_user, 'user');
@@ -66,5 +69,6 @@ describe('useAuthProperties', () => {
expect(result.current.basic_user).toBe('');
expect(result.current.basic_pwd).toBe('');
expect(result.current.jwt_bearerToken).toBe('');
+ expect(mock).toHaveBeenCalledOnce()
});
});
diff --git a/src/Serilog.Ui.Web/src/__tests__/hooks/useSearchForm.spec.ts b/src/Serilog.Ui.Web/src/__tests__/hooks/useSearchForm.spec.ts
index 3646dc2f..7dd9f6b1 100644
--- a/src/Serilog.Ui.Web/src/__tests__/hooks/useSearchForm.spec.ts
+++ b/src/Serilog.Ui.Web/src/__tests__/hooks/useSearchForm.spec.ts
@@ -1,12 +1,12 @@
import { dbKeysMock } from '__tests__/_setup/mocks/samples';
-import { renderHook, waitFor } from '__tests__/_setup/testing-utils';
+import { act, renderHook, waitFor } from '__tests__/_setup/testing-utils';
import { useSearchForm } from 'app/hooks/useSearchForm';
import { IAuthPropertiesStorageKeys } from 'app/util/auth';
import { AuthType } from 'types/types';
import { describe, expect, it } from 'vitest';
describe('useSearchForm', () => {
- it('renders and sets default table key on reset', async () => {
+ it('sets default table key on reset', async () => {
sessionStorage.setItem(IAuthPropertiesStorageKeys.jwt_bearerToken, 'token');
const { result } = renderHook(() => useSearchForm(), { authType: AuthType.Jwt });
@@ -17,6 +17,60 @@ describe('useSearchForm', () => {
});
});
+ it('sets NULL table key on reset', async () => {
+ sessionStorage.setItem(IAuthPropertiesStorageKeys.jwt_bearerToken, 'token');
+
+ const { result } = renderHook(() => useSearchForm(), { authType: AuthType.Jwt });
+
+ await waitFor(() => {
+ result.current.reset(true);
+ expect(result.current.getValues('table')).toBe(null);
+ });
+ });
+
+ type Properties = Parameters['setValue']>['0'];
+ it.each([{
+ property: 'level' as Properties, resetResult: true,
+ },
+ {
+ property: 'search' as Properties, resetResult: true,
+ },
+ {
+ property: 'startDate' as Properties, resetResult: true,
+ },
+ {
+ property: 'endDate' as Properties, resetResult: true,
+ },
+ {
+ property: 'entriesPerPage' as Properties, resetResult: false
+ },
+ {
+ property: 'page' as Properties, resetResult: false
+ },
+ {
+ property: 'sortBy' as Properties, resetResult: false
+ },
+ {
+ property: 'table' as Properties, resetResult: false
+ },
+ {
+ property: 'sortOn' as Properties, resetResult: false
+ }
+ ])('hints for refetch, returning $resetResult on reset, for property $property', async ({ property, resetResult }: { property: Properties, resetResult: boolean }) => {
+ sessionStorage.setItem(IAuthPropertiesStorageKeys.jwt_bearerToken, 'token');
+
+ const { result } = renderHook(() => useSearchForm(), { authType: AuthType.Jwt });
+
+ act(() => {
+ result.current.setValue(property, 'test')
+ })
+
+ act(() => {
+ const shouldRefetch = result.current.reset();
+ expect(shouldRefetch).toBe(resetResult);
+ })
+ });
+
it('renders and leaves default table key undefined, if internal query was not successful', async () => {
sessionStorage.removeItem(IAuthPropertiesStorageKeys.jwt_bearerToken);
diff --git a/src/Serilog.Ui.Web/src/app/App.tsx b/src/Serilog.Ui.Web/src/app/App.tsx
index 15fe69fc..fa470a12 100644
--- a/src/Serilog.Ui.Web/src/app/App.tsx
+++ b/src/Serilog.Ui.Web/src/app/App.tsx
@@ -1,7 +1,7 @@
import { ColorSchemeScript, MantineProvider } from '@mantine/core';
import { Notifications } from '@mantine/notifications';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
-import { RouterProvider, createBrowserRouter } from 'react-router-dom';
+import { RouterProvider, createBrowserRouter } from 'react-router';
import { theme } from 'style/theme';
import { AuthPropertiesProvider } from './hooks/useAuthProperties';
import { useSerilogUiProps } from './hooks/useSerilogUiProps';
diff --git a/src/Serilog.Ui.Web/src/app/components/Authorization/BasicModal.tsx b/src/Serilog.Ui.Web/src/app/components/Authorization/BasicModal.tsx
index 7e657a0e..602d3369 100644
--- a/src/Serilog.Ui.Web/src/app/components/Authorization/BasicModal.tsx
+++ b/src/Serilog.Ui.Web/src/app/components/Authorization/BasicModal.tsx
@@ -61,12 +61,7 @@ const BasicModal = ({ onClose }: { onClose: () => void }) => {
>
Save
-
- {
- clearAuthState();
- }}
- >
+
Change Token
Close
diff --git a/src/Serilog.Ui.Web/src/app/components/ErrorPage.tsx b/src/Serilog.Ui.Web/src/app/components/ErrorPage.tsx
index 539e0cd5..1fd3ad85 100644
--- a/src/Serilog.Ui.Web/src/app/components/ErrorPage.tsx
+++ b/src/Serilog.Ui.Web/src/app/components/ErrorPage.tsx
@@ -1,7 +1,7 @@
import { Box, Button, Text } from '@mantine/core';
import { IconArrowBarRight, IconBackspace, IconMoodSad } from '@tabler/icons-react';
import { serilogUiUrl } from 'app/util/prettyPrints';
-import { Link, isRouteErrorResponse, useRouteError } from 'react-router-dom';
+import { Link, isRouteErrorResponse, useRouteError } from 'react-router';
export const ErrorBoundaryPage = () => {
const error = useRouteError();
diff --git a/src/Serilog.Ui.Web/src/app/components/HomePageNotAuthorized.tsx b/src/Serilog.Ui.Web/src/app/components/HomePageNotAuthorized.tsx
index 1d13896c..24a501e4 100644
--- a/src/Serilog.Ui.Web/src/app/components/HomePageNotAuthorized.tsx
+++ b/src/Serilog.Ui.Web/src/app/components/HomePageNotAuthorized.tsx
@@ -1,7 +1,7 @@
import { Box, Text } from '@mantine/core';
import { useQueryAuth } from 'app/hooks/useQueryAuth';
import { useSerilogUiProps } from 'app/hooks/useSerilogUiProps';
-import { Navigate } from 'react-router-dom';
+import { Navigate } from 'react-router';
import AuthorizeButton from './Authorization/AuthorizeButton';
export const HomePageNotAuthorized = () => {
diff --git a/src/Serilog.Ui.Web/src/app/components/Index.tsx b/src/Serilog.Ui.Web/src/app/components/Index.tsx
index 24f67f87..9dfdbf32 100644
--- a/src/Serilog.Ui.Web/src/app/components/Index.tsx
+++ b/src/Serilog.Ui.Web/src/app/components/Index.tsx
@@ -8,7 +8,7 @@ import { useCloseOnResize } from 'app/hooks/useCloseOnResize';
import { useQueryAuth } from 'app/hooks/useQueryAuth';
import { useSerilogUiProps } from 'app/hooks/useSerilogUiProps';
import { Suspense, lazy } from 'react';
-import { Navigate } from 'react-router-dom';
+import { Navigate } from 'react-router';
const AppBody = lazy(() => import('./AppBody'));
const Head = lazy(() => import('./ShellStructure/Header'));
diff --git a/src/Serilog.Ui.Web/src/app/components/Search/PagingLeftColumn.tsx b/src/Serilog.Ui.Web/src/app/components/Search/PagingLeftColumn.tsx
index d2142289..23b9e0b8 100644
--- a/src/Serilog.Ui.Web/src/app/components/Search/PagingLeftColumn.tsx
+++ b/src/Serilog.Ui.Web/src/app/components/Search/PagingLeftColumn.tsx
@@ -6,7 +6,6 @@ import {
IconSortAscending,
IconSortDescending,
} from '@tabler/icons-react';
-import useQueryLogs from 'app/hooks/useQueryLogs';
import { useSearchForm } from 'app/hooks/useSearchForm';
import { useSerilogUiProps } from 'app/hooks/useSerilogUiProps';
import { memo, useEffect, useMemo } from 'react';
@@ -26,7 +25,6 @@ const sortOnOptions = Object.values(SortPropertyOptions).map((entry) => ({
export const PagingLeftColumn = memo(
({ changePage }: { changePage: (page: number) => void }) => {
const { disabledSortOnKeys } = useSerilogUiProps();
- const { refetch } = useQueryLogs();
const { control, watch } = useSearchForm();
const currentDbKey = watch('table');
@@ -58,10 +56,6 @@ export const PagingLeftColumn = memo(
}
}, [disableSortOn, fieldSortOn]);
- useEffect(() => {
- void refetch();
- }, [refetch, fieldEntries.value, fieldSortOn.value, fieldSortBy.value]);
-
return (
}) => {
const [opened, { close, toggle }] = useDisclosure(false);
- const { data, refetch } = useQueryLogs();
+ const { data } = useQueryLogs();
const lessPages = useMediaQuery(`(max-width: ${em(800)})`);
const totalPages = useMemo(() => {
@@ -30,10 +30,6 @@ export const PagingRightColumn = memo(
return Number.isNaN(pages) ? 1 : pages;
}, [data]);
- useEffect(() => {
- void refetch();
- }, [refetch, field.value]);
-
return (
({
value: level,
@@ -27,25 +27,39 @@ const levelsArray = Object.keys(LogLevel).map((level) => ({
const Search = ({ onRefetch }: { onRefetch?: () => void }) => {
const { isError } = useQueryTableKeys(true);
const { isUtc, setIsUtc } = useSerilogUiProps();
- const { handleSubmit, reset, setValue, watch } = useSearchForm();
-
+ const { handleSubmit, reset, setValue } = useSearchForm();
const { refetch } = useQueryLogs();
- const currentDbKey = watch('table');
+ const currentPage = useWatch({ name: 'page' });
- const clean = async () => {
- reset();
- await refetch();
- };
+ const onSubmit = async () => {
+ if (currentPage === 1) {
+ await refetch();
+ } else {
+ setValue('page', 1);
+ }
- const submit = async () => {
- setValue('page', 1);
- await refetch();
onRefetch?.();
};
+ const onClear = async () => {
+ const shouldRefetch = reset();
+
+ if (shouldRefetch) {
+ await refetch();
+ }
+ };
+
useEffect(() => {
- void refetch();
- }, [currentDbKey, refetch]);
+ const resetTableKey = () => {
+ reset(true);
+ };
+
+ document.addEventListener(DispatchedCustomEvents.RemoveTableKey, resetTableKey);
+
+ return () =>
+ document.removeEventListener(DispatchedCustomEvents.RemoveTableKey, resetTableKey);
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, []);
return (