Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Disable device item if device in progress #370

Merged
merged 1 commit into from
Apr 18, 2024
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
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
*ngIf="deviceView === DeviceView.WithActions"
class="device-item-with-actions">
<button
[disabled]="disabled"
[tabIndex]="tabIndex"
(click)="itemClick()"
attr.aria-label="Edit device {{ label }}"
Expand All @@ -57,6 +58,7 @@
</div>
</button>
<button
[disabled]="disabled"
(click)="startTestrunClick()"
attr.aria-label="Start new testrun for device {{ label }}"
class="button-start"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,11 @@ $border-radius: 12px;
width: 24px;
}
}

&:disabled {
pointer-events: none;
opacity: 0.5;
}
}

.item-manufacturer {
Expand Down Expand Up @@ -170,6 +175,10 @@ $border-radius: 12px;
color: $white;
}
}
&:disabled {
pointer-events: none;
opacity: 0.5;
}
}

.button-start-icon {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,5 +109,27 @@ describe('DeviceItemComponent', () => {

expect(clickSpy).toHaveBeenCalledWith(component.device);
});

it('should disable buttons if disable set to true', () => {
component.disabled = true;
fixture.detectChanges();

const startBtn = compiled.querySelector('.button-start') as HTMLElement;
const editBtn = compiled.querySelector('.button-edit') as HTMLElement;

expect(editBtn.getAttribute('disabled')).not.toBeNull();
expect(startBtn.getAttribute('disabled')).toBeTruthy();
});

it('should not disable buttons if disable set to false', () => {
component.disabled = false;
fixture.detectChanges();

const startBtn = compiled.querySelector('.button-start') as HTMLElement;
const editBtn = compiled.querySelector('.button-edit') as HTMLElement;

expect(editBtn.getAttribute('disabled')).toBeNull();
expect(startBtn.getAttribute('disabled')).toBeFalsy();
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ export class DeviceItemComponent {
@Input() device!: Device;
@Input() tabIndex = 0;
@Input() deviceView!: string;
@Input() disabled = false;
@Output() itemClicked = new EventEmitter<Device>();
@Output() startTestrunClicked = new EventEmitter<Device>();
readonly DeviceView = DeviceView;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,10 @@ <h2 class="title">Devices</h2>
(startTestrunClicked)="openStartTestrun($event, vm.devices)"
[deviceView]="DeviceView.WithActions"
[class.device-item-selected]="device === vm.selectedDevice"
[device]="device"></app-device-item>
[device]="device"
[disabled]="
device?.mac_addr === vm.deviceInProgress?.mac_addr
"></app-device-item>
</ng-container>
</div>
</ng-container>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ describe('DeviceRepositoryComponent', () => {
component.viewModel$ = of({
devices: [] as Device[],
selectedDevice: null,
deviceInProgress: null,
});
mockDevicesStore.devices$ = of([]);
mockDevicesStore.isOpenAddDevice$ = of(true);
Expand Down Expand Up @@ -107,6 +108,7 @@ describe('DeviceRepositoryComponent', () => {
component.viewModel$ = of({
devices: [device, device, device],
selectedDevice: device,
deviceInProgress: device,
});
fixture.detectChanges();
});
Expand Down Expand Up @@ -204,6 +206,12 @@ describe('DeviceRepositoryComponent', () => {
openSpy.calls.reset();
});
});

it('should disable device if deviceInProgress is exist', () => {
const item = compiled.querySelector('app-device-item');

expect(item?.getAttribute('ng-reflect-disabled')).toBeTruthy();
});
});

it('should call setIsOpenAddDevice if dialog closes with null', () => {
Expand Down Expand Up @@ -235,6 +243,7 @@ describe('DeviceRepositoryComponent', () => {
component.viewModel$ = of({
devices: [device, device, device],
selectedDevice: device,
deviceInProgress: null,
});
fixture.detectChanges();
});
Expand Down
7 changes: 6 additions & 1 deletion modules/ui/src/app/pages/devices/devices.store.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,10 @@ import { TestBed } from '@angular/core/testing';
import { of, skip, take } from 'rxjs';
import { MockStore, provideMockStore } from '@ngrx/store/testing';
import { AppState } from '../../store/state';
import { selectHasDevices } from '../../store/selectors';
import {
selectDeviceInProgress,
selectHasDevices,
} from '../../store/selectors';
import { TestRunService } from '../../services/test-run.service';
import SpyObj = jasmine.SpyObj;
import { device, updated_device } from '../../mocks/device.mock';
Expand Down Expand Up @@ -45,6 +48,7 @@ describe('DevicesStore', () => {
selectors: [
{ selector: selectDevices, value: [device] },
{ selector: selectIsOpenAddDevice, value: true },
{ selector: selectDeviceInProgress, value: device },
],
}),
{ provide: TestRunService, useValue: mockService },
Expand Down Expand Up @@ -79,6 +83,7 @@ describe('DevicesStore', () => {
expect(store).toEqual({
devices: [device],
selectedDevice: null,
deviceInProgress: device,
});
done();
});
Expand Down
8 changes: 7 additions & 1 deletion modules/ui/src/app/pages/devices/devices.store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,11 @@ import { tap, withLatestFrom } from 'rxjs/operators';
import { Device } from '../../model/device';
import { AppState } from '../../store/state';
import { Store } from '@ngrx/store';
import { selectDevices, selectIsOpenAddDevice } from '../../store/selectors';
import {
selectDeviceInProgress,
selectDevices,
selectIsOpenAddDevice,
} from '../../store/selectors';
import { setDevices, setIsOpenAddDevice } from '../../store/actions';

export interface DevicesComponentState {
Expand All @@ -34,12 +38,14 @@ export interface DevicesComponentState {
export class DevicesStore extends ComponentStore<DevicesComponentState> {
devices$ = this.store.select(selectDevices);
isOpenAddDevice$ = this.store.select(selectIsOpenAddDevice);
private deviceInProgress$ = this.store.select(selectDeviceInProgress);
private selectedDevice$ = this.select(state => state.selectedDevice);

testModules = this.testRunService.getTestModules();
viewModel$ = this.select({
devices: this.devices$,
selectedDevice: this.selectedDevice$,
deviceInProgress: this.deviceInProgress$,
});

selectDevice = this.updater((state, device: Device | null) => ({
Expand Down
7 changes: 6 additions & 1 deletion modules/ui/src/app/services/test-run.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ import { MOCK_PROGRESS_DATA_IN_PROGRESS } from '../mocks/progress.mock';
import { StatusOfTestResult, TestrunStatus } from '../model/testrun-status';
import { device } from '../mocks/device.mock';
import { NEW_VERSION, VERSION } from '../mocks/version.mock';
import { MockStore, provideMockStore } from '@ngrx/store/testing';
import { AppState } from '../store/state';

const MOCK_SYSTEM_CONFIG: SystemConfig = {
network: {
Expand All @@ -38,15 +40,18 @@ describe('TestRunService', () => {
let injector: TestBed;
let httpTestingController: HttpTestingController;
let service: TestRunService;
let store: MockStore<AppState>;

beforeEach(() => {
TestBed.configureTestingModule({
imports: [HttpClientTestingModule],
providers: [TestRunService],
providers: [TestRunService, provideMockStore({})],
});
injector = getTestBed();
httpTestingController = injector.get(HttpTestingController);
service = injector.get(TestRunService);
store = TestBed.inject(MockStore);
spyOn(store, 'dispatch').and.callFake(() => {});
});

afterEach(() => {
Expand Down
7 changes: 6 additions & 1 deletion modules/ui/src/app/services/test-run.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ import {
TestrunStatus,
} from '../model/testrun-status';
import { Version } from '../model/version';
import { Store } from '@ngrx/store';
import { AppState } from '../store/state';

const API_URL = `http://${window.location.hostname}:8000`;
export const SYSTEM_STOP = '/system/stop';
Expand Down Expand Up @@ -76,7 +78,10 @@ export class TestRunService {

private version = new BehaviorSubject<Version | null>(null);

constructor(private http: HttpClient) {}
constructor(
private http: HttpClient,
private store: Store<AppState>
) {}

fetchDevices(): Observable<Device[]> {
return this.http.get<Device[]>(`${API_URL}/devices`);
Expand Down
5 changes: 5 additions & 0 deletions modules/ui/src/app/store/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,3 +90,8 @@ export const setIsTestrunStarted = createAction(
'[Shared] Set Testrun Started',
props<{ isTestrunStarted: boolean }>()
);

export const setDeviceInProgress = createAction(
'[Shared] Set Device In Progress',
props<{ device: Device | null }>()
);
13 changes: 13 additions & 0 deletions modules/ui/src/app/store/effects.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import { MockStore, provideMockStore } from '@ngrx/store/testing';
import { AppState } from './state';
import { selectMenuOpened } from './selectors';
import { device } from '../mocks/device.mock';
import { MOCK_PROGRESS_DATA_IN_PROGRESS } from '../mocks/progress.mock';
describe('Effects', () => {
let actions$ = new Observable<Action>();
let effects: AppEffects;
Expand Down Expand Up @@ -55,6 +56,18 @@ describe('Effects', () => {
store.refreshState();
});

it('onSetDevices$ should call setDeviceInProgress when testrun in progress', done => {
const status = MOCK_PROGRESS_DATA_IN_PROGRESS;
actions$ = of(actions.setTestrunStatus({ systemStatus: status }));

effects.onSetTestrunStatus$.subscribe(action => {
expect(action).toEqual(
actions.setDeviceInProgress({ device: status.device })
);
done();
});
});

it('onSetDevices$ should call setHasDevices', done => {
actions$ = of(actions.setDevices({ devices: [device] }));

Expand Down
17 changes: 17 additions & 0 deletions modules/ui/src/app/store/effects.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import { AppState } from './state';
import { TestRunService } from '../services/test-run.service';
import { filter, combineLatest } from 'rxjs';
import { selectMenuOpened } from './selectors';
import { StatusOfTestrun } from '../model/testrun-status';

@Injectable()
export class AppEffects {
Expand Down Expand Up @@ -95,6 +96,22 @@ export class AppEffects {
);
});

onSetTestrunStatus$ = createEffect(() => {
return this.actions$.pipe(
ofType(AppActions.setTestrunStatus),
map(({ systemStatus }) =>
AppActions.setDeviceInProgress({
device:
systemStatus.status === StatusOfTestrun.Monitoring ||
systemStatus.status === StatusOfTestrun.InProgress ||
systemStatus.status === StatusOfTestrun.WaitingForDevice
? systemStatus.device
: null,
})
)
);
});

constructor(
private actions$: Actions,
private testrunService: TestRunService,
Expand Down
32 changes: 32 additions & 0 deletions modules/ui/src/app/store/reducers.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ import * as fromReducer from './reducers';
import { initialAppComponentState, initialSharedState } from './state';
import {
fetchInterfacesSuccess,
setDeviceInProgress,
setDevices,
setHasConnectionSettings,
setHasDevices,
setIsOpenAddDevice,
Expand All @@ -27,6 +29,7 @@ import {
updateError,
updateFocusNavigation,
} from './actions';
import { device } from '../mocks/device.mock';
import { MOCK_PROGRESS_DATA_CANCELLING } from '../mocks/progress.mock';

describe('Reducer', () => {
Expand Down Expand Up @@ -140,6 +143,35 @@ describe('Reducer', () => {
});
});

describe('setDevices action', () => {
it('should update state', () => {
const initialState = initialSharedState;
const devices = [device, device];
const action = setDevices({ devices });
const state = fromReducer.sharedReducer(initialState, action);
const newState = { ...initialState, ...{ devices } };

expect(state).toEqual(newState);
expect(state).not.toBe(initialState);
});
});

describe('setDeviceInProgress action', () => {
it('should update state', () => {
const initialState = initialSharedState;
const deviceInProgress = device;
const action = setDeviceInProgress({ device: deviceInProgress });
const state = fromReducer.sharedReducer(initialState, action);
const newState = {
...initialState,
...{ deviceInProgress: deviceInProgress },
};

expect(state).toEqual(newState);
expect(state).not.toBe(initialState);
});
});

describe('setTestrunStatus action', () => {
it('should update state', () => {
const initialState = initialSharedState;
Expand Down
6 changes: 6 additions & 0 deletions modules/ui/src/app/store/reducers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,12 @@ export const sharedReducer = createReducer(
...state,
isTestrunStarted,
};
}),
on(Actions.setDeviceInProgress, (state, { device }) => {
return {
...state,
deviceInProgress: device,
};
})
);

Expand Down
7 changes: 7 additions & 0 deletions modules/ui/src/app/store/selectors.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

import { AppState } from './state';
import {
selectDeviceInProgress,
selectDevices,
selectError,
selectHasConnectionSettings,
Expand Down Expand Up @@ -46,6 +47,7 @@ describe('Selectors', () => {
isOpenStartTestrun: false,
systemStatus: null,
isTestrunStarted: false,
deviceInProgress: null,
},
};

Expand Down Expand Up @@ -98,4 +100,9 @@ describe('Selectors', () => {
const result = selectIsOpenStartTestrun.projector(initialState);
expect(result).toEqual(false);
});

it('should select deviceInProgress', () => {
const result = selectDeviceInProgress.projector(initialState);
expect(result).toEqual(null);
});
});
Loading