refactor(onboarding): remove unnecessary fields from UpgradeStep type

- Removed `title`, `description`, and `icon` fields from the `UpgradeStep` type in the GraphQL schema and related TypeScript definitions to simplify the onboarding process.
- Updated documentation to reflect changes in how onboarding steps are tracked and displayed.
- Adjusted related tests and components to ensure compatibility with the updated schema.

This update streamlines the onboarding experience by focusing on essential information for upgrade steps.
This commit is contained in:
Eli Bosley
2025-10-13 21:53:07 -04:00
parent 99bb59d8bf
commit e7828c316f
7 changed files with 11 additions and 53 deletions

View File

@@ -929,15 +929,6 @@ type UpgradeStep {
"""Version of Unraid when this step was introduced"""
introducedIn: String
"""Display title for the onboarding step"""
title: String!
"""Display description for the onboarding step"""
description: String!
"""Icon identifier for the onboarding step"""
icon: String
}
type UpgradeInfo {

View File

@@ -148,7 +148,8 @@ export type ActivationOnboardingStep = {
export enum ActivationOnboardingStepId {
ACTIVATION = 'ACTIVATION',
PLUGINS = 'PLUGINS',
TIMEZONE = 'TIMEZONE'
TIMEZONE = 'TIMEZONE',
WELCOME = 'WELCOME'
}
export type AddPermissionInput = {
@@ -2682,18 +2683,12 @@ export type UpgradeInfo = {
export type UpgradeStep = {
__typename?: 'UpgradeStep';
/** Display description for the onboarding step */
description: Scalars['String']['output'];
/** Icon identifier for the onboarding step */
icon?: Maybe<Scalars['String']['output']>;
/** Identifier of the onboarding step */
id: Scalars['String']['output'];
/** Version of Unraid when this step was introduced */
introducedIn?: Maybe<Scalars['String']['output']>;
/** Whether the step is required to continue */
required: Scalars['Boolean']['output'];
/** Display title for the onboarding step */
title: Scalars['String']['output'];
};
export type Uptime = {

View File

@@ -44,6 +44,7 @@ const mockReadFile = vi.mocked(readFile);
const mockReaddir = vi.mocked(readdir);
const mockAccess = vi.mocked(access);
const mockAtomicWriteFile = vi.mocked(atomicWriteFile);
type ReaddirResult = Awaited<ReturnType<typeof readdir>>;
describe('ApiConfigPersistence', () => {
let service: ApiConfigPersistence;
@@ -84,7 +85,6 @@ describe('ApiConfigPersistence', () => {
ssoSubIds: [],
plugins: [],
});
expect(defaultConfig.lastSeenOsVersion).toBeUndefined();
});
it('should migrate config from legacy format', async () => {
@@ -110,7 +110,6 @@ describe('ApiConfigPersistence', () => {
ssoSubIds: ['sub1', 'sub2'],
plugins: [],
});
expect(result.lastSeenOsVersion).toBeUndefined();
});
it('sets api.version on bootstrap', async () => {
@@ -142,7 +141,7 @@ describe('OnboardingTracker', () => {
mockReadFile.mockReset();
mockReaddir.mockReset();
mockAccess.mockReset();
mockReaddir.mockResolvedValue([]);
mockReaddir.mockResolvedValue([] as unknown as ReaddirResult);
mockAccess.mockResolvedValue(undefined);
mockAtomicWriteFile.mockReset();
@@ -165,7 +164,6 @@ describe('OnboardingTracker', () => {
expect(setMock).toHaveBeenCalledWith('store.emhttp.var.version', '7.2.0-beta.3.4');
expect(setMock).toHaveBeenCalledWith('onboardingTracker.lastTrackedVersion', undefined);
expect(setMock).toHaveBeenCalledWith('onboardingTracker.completedSteps', {});
expect(configStore['api.lastSeenOsVersion']).toBeUndefined();
expect(mockAtomicWriteFile).not.toHaveBeenCalled();
await tracker.onApplicationShutdown();
@@ -209,7 +207,6 @@ describe('OnboardingTracker', () => {
TIMEZONE: expect.objectContaining({ version: '6.12.0' }),
})
);
expect(configStore['api.lastSeenOsVersion']).toBe('6.12.0');
expect(mockAtomicWriteFile).not.toHaveBeenCalled();
await tracker.onApplicationShutdown();
@@ -265,7 +262,6 @@ describe('OnboardingTracker', () => {
expect(configStore['onboardingTracker.lastTrackedVersion']).toBe('7.0.0');
expect(configStore['store.emhttp.var.version']).toBe('7.1.0');
expect(configStore['onboardingTracker.completedSteps']).toEqual({});
expect(configStore['api.lastSeenOsVersion']).toBe('7.0.0');
expect(mockAtomicWriteFile).not.toHaveBeenCalled();
});
@@ -281,7 +277,6 @@ describe('OnboardingTracker', () => {
expect(setMock).toHaveBeenCalledWith('onboardingTracker.lastTrackedVersion', undefined);
expect(setMock).toHaveBeenCalledWith('onboardingTracker.completedSteps', {});
expect(mockAtomicWriteFile).not.toHaveBeenCalled();
expect(configStore['api.lastSeenOsVersion']).toBeUndefined();
});
it('marks onboarding steps complete for the current version without clearing upgrade flag', async () => {
@@ -304,7 +299,6 @@ describe('OnboardingTracker', () => {
expect(configStore['store.emhttp.var.version']).toBe('7.2.0');
expect(configStore['onboardingTracker.lastTrackedVersion']).toBe('6.12.0');
expect(configStore['api.lastSeenOsVersion']).toBe('6.12.0');
setMock.mockClear();
mockAtomicWriteFile.mockReset();
@@ -427,7 +421,7 @@ describe('OnboardingTracker', () => {
throw Object.assign(new Error('Not found'), { code: 'ENOENT' });
});
mockReaddir.mockResolvedValueOnce(['pending.activationcode']);
mockReaddir.mockResolvedValueOnce(['pending.activationcode'] as unknown as ReaddirResult);
mockEmhttpState.var.regState = 'ENOKEYFILE';
const tracker = new OnboardingTracker(configService);
@@ -463,7 +457,6 @@ describe('loadApiConfig', () => {
ssoSubIds: [],
plugins: [],
});
expect(result.lastSeenOsVersion).toBeUndefined();
});
it('should handle errors gracefully and return defaults', async () => {
@@ -476,6 +469,5 @@ describe('loadApiConfig', () => {
ssoSubIds: [],
plugins: [],
});
expect(result.lastSeenOsVersion).toBeUndefined();
});
});

View File

@@ -96,7 +96,6 @@ export class OnboardingTracker implements OnApplicationBootstrap, OnApplicationS
const lastTrackedVersion =
this.sessionLastTrackedVersion ??
this.configService.get<string>(`${CONFIG_PREFIX}.lastTrackedVersion`) ??
this.configService.get<string>('api.lastSeenOsVersion') ??
undefined;
await this.ensureStateLoaded();
@@ -248,7 +247,6 @@ export class OnboardingTracker implements OnApplicationBootstrap, OnApplicationS
this.configService.set('store.emhttp.var.version', currentVersion);
this.configService.set(`${CONFIG_PREFIX}.lastTrackedVersion`, this.sessionLastTrackedVersion);
this.configService.set(`${CONFIG_PREFIX}.completedSteps`, completedStepsMap);
this.configService.set('api.lastSeenOsVersion', this.sessionLastTrackedVersion);
}
private async readCurrentVersion(): Promise<string | undefined> {

View File

@@ -59,18 +59,6 @@ export class UpgradeStep {
description: 'Version of Unraid when this step was introduced',
})
introducedIn?: string;
@Field(() => String, { description: 'Display title for the onboarding step' })
title!: string;
@Field(() => String, { description: 'Display description for the onboarding step' })
description!: string;
@Field(() => String, {
nullable: true,
description: 'Icon identifier for the onboarding step',
})
icon?: string;
}
@ObjectType()

View File

@@ -2,16 +2,16 @@
## Overview
This system shows contextual onboarding steps to users when they upgrade their Unraid OS to a new version. It tracks the last seen OS version in the API config and allows you to define which steps should be shown for specific version upgrades.
This system shows contextual onboarding steps to users when they upgrade their Unraid OS to a new version. It tracks the last seen OS version using the onboarding tracker state and allows you to define which steps should be shown for specific version upgrades.
## How It Works
### Backend (API)
1. **Version Tracking** - `api/src/unraid-api/config/api-config.module.ts`
- On boot, compares current OS version with `lastSeenOsVersion` in API config
- Automatically updates `lastSeenOsVersion` when version changes
- Persists to `/boot/config/modules/api.json`
1. **Version Tracking** - `api/src/unraid-api/config/onboarding-tracker.module.ts`
- On boot, compares current OS version with `lastTrackedVersion` in the onboarding tracker
- Automatically updates `lastTrackedVersion` when version changes
- Persists to `/boot/config/modules/onboarding-tracker.json`
2. **GraphQL API** - `api/src/unraid-api/graph/resolvers/customization/`
- Exposes `activationOnboarding` query which returns:
@@ -122,7 +122,7 @@ The `ActivationModal` is already integrated into the app and automatically handl
To test the upgrade flow:
1. Edit `/boot/config/modules/api.json` and set `lastSeenOsVersion` to an older version
1. Edit `/boot/config/modules/onboarding-tracker.json` and set `lastTrackedVersion` to an older version
2. Ensure an activation code exists (or remove it to test conditional logic)
3. Restart the API
4. The modal should appear on next page load with relevant steps from `activationOnboarding`

View File

@@ -2683,18 +2683,12 @@ export type UpgradeInfo = {
export type UpgradeStep = {
__typename?: 'UpgradeStep';
/** Display description for the onboarding step */
description: Scalars['String']['output'];
/** Icon identifier for the onboarding step */
icon?: Maybe<Scalars['String']['output']>;
/** Identifier of the onboarding step */
id: Scalars['String']['output'];
/** Version of Unraid when this step was introduced */
introducedIn?: Maybe<Scalars['String']['output']>;
/** Whether the step is required to continue */
required: Scalars['Boolean']['output'];
/** Display title for the onboarding step */
title: Scalars['String']['output'];
};
export type Uptime = {