feat(application): add license notification#3553
Conversation
Signed-off-by: Ashwin Vaidya <ashwinnitinvaidya@gmail.com>
Signed-off-by: Ashwin Vaidya <ashwinnitinvaidya@gmail.com>
Signed-off-by: Ashwin Vaidya <ashwinnitinvaidya@gmail.com>
Signed-off-by: Ashwin Vaidya <ashwinnitinvaidya@gmail.com>
There was a problem hiding this comment.
Pull request overview
Adds a version-scoped license acceptance flow to Anomalib Studio (application backend + UI), persisting acceptance in the Studio DB and blocking the UI until required licenses are accepted, with license lists varying by deployment type and model.
Changes:
- Backend: adds license acceptance persistence (
license_acceptancetable),/api/system/license+/api/system/license:acceptendpoints, and deployment type inference exposed viaSystemInfo. - UI: introduces a
LicenseGateprovider that fetches license status and prompts acceptance before continuing. - Updates tests/fixtures and
third-party-programs.txtto include SuperSimpleNet attribution.
Reviewed changes
Copilot reviewed 15 out of 15 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| third-party-programs.txt | Adds SuperSimpleNet model attribution entry. |
| application/ui/tests/fixtures.ts | Mocks new license endpoints for Playwright component tests. |
| application/ui/src/setup-tests.ts | Adds storage stubs and switches MSW server import to dynamic import for test setup. |
| application/ui/src/providers.tsx | Wraps router with LicenseGate to enforce license acceptance. |
| application/ui/src/features/license/license-gate.component.tsx | Implements UI license gating dialog + accept flow. |
| application/ui/src/features/license/license-gate.component.test.tsx | Adds unit test validating gating/accept flow. |
| application/backend/tests/unit/services/test_system_service.py | Adds unit tests for license status and required licenses. |
| application/backend/tests/unit/endpoints/test_system.py | Adds endpoint tests for license status + accept endpoints. |
| application/backend/src/services/system_service.py | Adds deployment inference, required license list, DB persistence, and extends SystemInfo. |
| application/backend/src/services/init.py | Exports SystemService. |
| application/backend/src/pydantic_models/system.py | Adds DeploymentType and license-related response models; extends SystemInfo. |
| application/backend/src/pydantic_models/init.py | Exposes new system/license models via package exports. |
| application/backend/src/db/schema.py | Adds LicenseAcceptanceDB table model. |
| application/backend/src/api/endpoints/system_endpoints.py | Adds /license and /license:accept endpoints. |
| application/backend/src/alembic/versions/7a213a27d666_initial_schema.py | Adds license_acceptance table to initial migration. |
Signed-off-by: Ashwin Vaidya <ashwinnitinvaidya@gmail.com>
Signed-off-by: Ashwin Vaidya <ashwinnitinvaidya@gmail.com>
There was a problem hiding this comment.
Pull request overview
Note
Copilot was unable to run its full agentic suite in this review.
Adds a backend/API + UI “license acceptance gate” so Windows app deployments can require users to accept a license before continuing, and mirrors related changes from upstream PRs.
Changes:
- Backend: introduce license status + acceptance persistence (DB table + service + endpoints) and expose
deployment_typeinSystemInfo. - UI: wrap the app router with a
LicenseGatecomponent and add corresponding unit tests/mocks. - Compliance: update third-party notices.
Reviewed changes
Copilot reviewed 15 out of 15 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| third-party-programs.txt | Adds third-party notice entry for SuperSimpleNet implementation. |
| application/ui/tests/fixtures.ts | Mocks new license endpoints for UI integration tests. |
| application/ui/src/setup-tests.ts | Adds storage polyfills and changes MSW setup import ordering for tests. |
| application/ui/src/providers.tsx | Wraps app routing with LicenseGate to enforce license acceptance. |
| application/ui/src/features/license/license-gate.component.tsx | Implements license fetch/accept flow and blocking dialog. |
| application/ui/src/features/license/license-gate.component.test.tsx | Adds unit coverage for license gate behaviors. |
| application/backend/tests/unit/services/test_system_service.py | Adds unit tests for license status + required licenses logic. |
| application/backend/tests/unit/endpoints/test_system.py | Adds endpoint tests for GET/POST license routes. |
| application/backend/src/services/system_service.py | Adds deployment detection + license status/accept persistence + enriches SystemInfo. |
| application/backend/src/services/init.py | Exports SystemService from services package. |
| application/backend/src/pydantic_models/system.py | Adds deployment + license-related schemas and extends SystemInfo. |
| application/backend/src/pydantic_models/init.py | Re-exports new system-related models. |
| application/backend/src/db/schema.py | Adds LicenseAcceptanceDB table schema. |
| application/backend/src/api/endpoints/system_endpoints.py | Adds /api/system/license and /api/system/license:accept endpoints. |
| application/backend/src/alembic/versions/7a213a27d666_initial_schema.py | Adds license_acceptance table creation/drop to existing “initial” migration. |
| acceptance = await self._get_latest_license_acceptance() | ||
| return LicenseStatus( | ||
| accepted=acceptance is not None, | ||
| accepted_version=acceptance.accepted_version if acceptance is not None else None, | ||
| app_version=settings.version, | ||
| deployment_type=deployment_type, | ||
| licenses=self.get_required_licenses(), | ||
| ) |
| assert len(status.licenses) == 1 | ||
|
|
||
| @pytest.mark.asyncio | ||
| async def test_get_license_status_requires_reaccept_on_version_change(self, fxt_system_service: SystemService): |
|
|
||
| status = await fxt_system_service.get_license_status() | ||
|
|
||
| assert status.accepted is True |
| By continuing you agree to the{' '} | ||
| <Link | ||
| href={licenseStatus.licenses[0]?.url ?? '#'} | ||
| target='_blank' | ||
| rel='noreferrer' | ||
| > | ||
| {licenseStatus.licenses[0]?.name ?? 'license terms'} | ||
| </Link> |
| expect(screen.getByText('Studio content')).toBeInTheDocument(); | ||
| }); | ||
|
|
||
| expect(screen.queryByText('License Agreement')).not.toBeInTheDocument(); |
Signed-off-by: Ashwin Vaidya <ashwinnitinvaidya@gmail.com>
Signed-off-by: Ashwin Vaidya <ashwinnitinvaidya@gmail.com>
Signed-off-by: Ashwin Vaidya <ashwinnitinvaidya@gmail.com>
There was a problem hiding this comment.
Pull request overview
Adds end-user license acceptance plumbing for Anomalib Studio (backend endpoints + DB persistence) and introduces a UI “gate” that can block the app behind a license agreement dialog, alongside test/fixture updates and third-party notices maintenance.
Changes:
- Add backend license status/acceptance APIs and persist acceptance in SQLite via a new table.
- Add a UI
LicenseGatewrapper around routing plus unit/component test coverage and MSW/Playwright fixtures. - Update
third-party-programs.txtwith an additional model notice.
Reviewed changes
Copilot reviewed 15 out of 15 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| third-party-programs.txt | Adds third-party notice for SuperSimpleNet implementation. |
| application/ui/tests/fixtures.ts | Adds MSW handlers for new license endpoints in Playwright component tests. |
| application/ui/src/setup-tests.ts | Adds storage mocks and adjusts MSW server import timing for Vitest setup. |
| application/ui/src/providers.tsx | Wraps the app router with LicenseGate to enforce license confirmation. |
| application/ui/src/features/license/license-gate.component.tsx | Introduces the license dialog gate powered by React Query and fetch. |
| application/ui/src/features/license/license-gate.component.test.tsx | Adds unit tests for license dialog behavior. |
| application/backend/tests/unit/services/test_system_service.py | Adds service-level tests for license status/info behavior. |
| application/backend/tests/unit/endpoints/test_system.py | Adds endpoint tests for /api/system/license and /api/system/license:accept. |
| application/backend/src/services/system_service.py | Implements desktop detection, license status, and acceptance persistence; adds is_desktop to system info. |
| application/backend/src/services/init.py | Exports SystemService. |
| application/backend/src/pydantic_models/system.py | Adds LicenseInfo, LicenseStatus, LicenseAcceptanceResponse and extends SystemInfo with is_desktop. |
| application/backend/src/pydantic_models/init.py | Exposes new license-related pydantic models via package exports. |
| application/backend/src/db/schema.py | Adds LicenseAcceptanceDB table mapping. |
| application/backend/src/api/endpoints/system_endpoints.py | Adds license endpoints to the system router. |
| application/backend/src/alembic/versions/7a213a27d666_initial_schema.py | Adds license_acceptance table creation/drop to Alembic migration. |
| it('does not show dialog for non-desktop deployments', async () => { | ||
| vi.spyOn(global, 'fetch').mockImplementation(async (input, init) => { | ||
| const url = String(input); | ||
| const method = init?.method ?? 'GET'; | ||
|
|
||
| if (url.endsWith('/api/system/license') && method === 'GET') { | ||
| return new Response( | ||
| JSON.stringify({ | ||
| accepted: true, | ||
| app_version: '1.2.3', | ||
| is_desktop: false, | ||
| license: null, | ||
| }), | ||
| { status: 200, headers: { 'Content-Type': 'application/json' } } | ||
| ); | ||
| } | ||
|
|
||
| return new Response(null, { status: 404 }); | ||
| }); | ||
|
|
||
| renderGate(); | ||
|
|
||
| await waitFor(() => { | ||
| expect(screen.getByText('Studio content')).toBeInTheDocument(); | ||
| }); | ||
|
|
||
| expect(screen.queryByText('License Agreement')).not.toBeInTheDocument(); | ||
| }); |
| class LicenseAcceptanceDB(Base): | ||
| __tablename__ = "license_acceptance" | ||
|
|
||
| id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True) | ||
| created_at: Mapped[datetime] = mapped_column(DateTime, server_default=func.current_timestamp()) | ||
|
|
| def upgrade() -> None: | ||
| """Upgrade schema - create all tables matching schema.py.""" | ||
| op.create_table( | ||
| "license_acceptance", | ||
| sa.Column("id", sa.Integer(), autoincrement=True, nullable=False), | ||
| sa.Column("created_at", sa.DateTime(), server_default=sa.text("(CURRENT_TIMESTAMP)"), nullable=False), | ||
| sa.PrimaryKeyConstraint("id"), | ||
| ) |
| async def _get_latest_license_acceptance() -> LicenseAcceptanceDB | None: | ||
| async with get_async_db_session_ctx() as session: | ||
| result = await session.execute(select(LicenseAcceptanceDB).order_by(desc(LicenseAcceptanceDB.id)).limit(1)) | ||
| return result.scalar_one_or_none() | ||
|
|
||
| async def get_license_status(self) -> LicenseStatus: | ||
| """Return whether the end-user has accepted the license terms. | ||
|
|
||
| Non-desktop deployments are auto-accepted. Desktop builds check for | ||
| an existing acceptance record in the database. | ||
|
|
||
| Returns: | ||
| A :class:`LicenseStatus` with acceptance state and license metadata. | ||
| """ | ||
| settings = get_settings() | ||
|
|
||
| if not self.is_desktop_deployment(): | ||
| return LicenseStatus( | ||
| accepted=True, | ||
| app_version=settings.version, | ||
| is_desktop=False, | ||
| license=None, | ||
| ) | ||
|
|
||
| acceptance = await self._get_latest_license_acceptance() | ||
| return LicenseStatus( | ||
| accepted=acceptance is not None, | ||
| app_version=settings.version, | ||
| is_desktop=True, | ||
| license=self.get_license_info(), | ||
| ) |
| if not self.is_desktop_deployment(): | ||
| return LicenseStatus( | ||
| accepted=True, | ||
| app_version=settings.version, | ||
| is_desktop=False, | ||
| license=None, | ||
| ) |
| const shouldBlock = !licenseStatus.accepted; | ||
|
|
||
| return ( | ||
| <> | ||
| {children} | ||
| <DialogContainer onDismiss={() => undefined}> | ||
| {shouldBlock ? ( | ||
| <AlertDialog | ||
| variant='confirmation' |
📝 Description
✨ Changes
Select what type of change your PR is:
✅ Checklist
Before you submit your pull request, please make sure you have completed the following steps:
For more information about code review checklists, see the Code Review Checklist.