diff --git a/src/app/stacks/stack-details/stack.controller.ts b/src/app/stacks/stack-details/stack.controller.ts index e8b449d857a..5e049f11a98 100644 --- a/src/app/stacks/stack-details/stack.controller.ts +++ b/src/app/stacks/stack-details/stack.controller.ts @@ -123,7 +123,7 @@ export class StackController { this.stackTags = tags ? tags : []; this.stackName = name ? name : ''; this.stackDescription = description; - this.stack = stack; + this.stack = angular.copy(stack); } /** diff --git a/src/app/stacks/stack-details/stack.spec.ts b/src/app/stacks/stack-details/stack.spec.ts new file mode 100644 index 00000000000..eefbf65fb7b --- /dev/null +++ b/src/app/stacks/stack-details/stack.spec.ts @@ -0,0 +1,355 @@ +/* + * Copyright (c) 2015-2018 Red Hat, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ + +import { CheHttpBackend } from '../../../components/api/test/che-http-backend'; +import { StackController } from './stack.controller'; + +'use strict'; + +/** + * Test for Stack Details directive. + * + * @author Oleksii Kurinny + */ +describe(`StackDetailsDirective >`, () => { + + let controller: StackController; + + let $scope: ng.IScope; + let $compile: ng.ICompileService; + let $timeout: ng.ITimeoutService; + let $httpBackend: ng.IHttpBackendService; + let cheHttpBackend: CheHttpBackend; + let compiledDirective; + + let installersList: Array; + + /** + * Setup module + */ + beforeEach(() => { + angular.mock.module('userDashboard'); + + angular.module('stackDetailsMock', []) + .directive('stackDetails', function () { + return { + restrict: 'E', + scope: {}, + controller: 'StackController', + controllerAs: 'stackController', + templateUrl: 'app/stacks/stack-details/stack.html' + }; + }) + .service('initData', function() { + return getInitData(); + }) + .service('cheNotification', function() { + this.showInfo = (text: string) => console.log(text); + }); + + angular.mock.module('stackDetailsMock'); + }); + + beforeEach(inject(( + _$rootScope_: ng.IRootScopeService, + _$compile_: ng.ICompileService, + _$timeout_: ng.ITimeoutService, + _cheHttpBackend_: CheHttpBackend + ) => { + $scope = _$rootScope_.$new(); + $compile = _$compile_; + $timeout = _$timeout_; + cheHttpBackend = _cheHttpBackend_; + $httpBackend = cheHttpBackend.getHttpBackend(); + + cheHttpBackend.setup(); + installersList = mockInstallers(cheHttpBackend); + $httpBackend.flush(); + })); + + /** + * Compile the directive. + */ + beforeEach(() => { + compiledDirective = $compile(angular.element(``))($scope); + $scope.$digest(); + $httpBackend.flush(); + $timeout.flush(); + controller = compiledDirective.controller('stackDetails'); + }); + + afterEach(() => { + $timeout.verifyNoPendingTasks(); + }); + + /** + * Check assertion after the test + */ + afterEach(() => { + $httpBackend.verifyNoOutstandingExpectation(); + $httpBackend.verifyNoOutstandingRequest(); + }); + + describe(`"Runtimes" section >`, () => { + + let runtimeElements; + + beforeEach(() => { + runtimeElements = compiledDirective.find('.workspace-environments'); + }); + + it(`"should have one environment >`, () => { + expect(runtimeElements).toBeTruthy(); + expect(runtimeElements.length).toEqual(1); + }); + + describe(`"Machines" section >`, () => { + + let machineElements; + + beforeEach(() => { + machineElements = angular.element(runtimeElements[0]).find('.workspace-machine-config'); + }); + + it(`"should have one machine >`, () => { + expect(machineElements).toBeTruthy(); + expect(machineElements.length).toEqual(1); + }); + + describe(`"Installers" subsection >`, () => { + + let availableInstallerIds = new Set(); + let initiallyEnabledInstallers; + + let editModeOverlay; + let saveButton, cancelButton; + + function getAllInstallerElements() { + return machineElements.find('che-machine-agents .machine-agents-item'); + } + function getEnabledInstallerElements() { + return getAllInstallerElements().find('md-switch[aria-checked="true"]'); + } + function getInstallersFromStack(stack: che.IStack) { + return (stack.workspaceConfig.environments as any).default.machines['dev-machine'].installers; + } + + beforeEach(() => { + installersList.forEach((installer: che.IAgent) => { + availableInstallerIds.add(installer.id); + }); + initiallyEnabledInstallers = getInstallersFromStack(getTestStack()); + + editModeOverlay = compiledDirective.find('.workspace-edit-mode-overlay'); + saveButton = editModeOverlay.find('che-button-save-flat button'); + cancelButton = editModeOverlay.find('che-button-cancel-flat button'); + }); + + it(`should have correct number of installers available >`, () => { + const allInstallerElements = getAllInstallerElements(); + expect(allInstallerElements).toBeTruthy(); + expect(allInstallerElements.length).toEqual(availableInstallerIds.size); + }); + + it(`should have correct number of installers enabled >`, () => { + const enabledInstallerElements = getEnabledInstallerElements(); + expect(enabledInstallerElements).toBeTruthy(); + expect(enabledInstallerElements.length).toEqual(initiallyEnabledInstallers.length); + }); + + it(`should have edit-mode overlay hidden >`, () => { + expect(editModeOverlay.attr('aria-hidden')).toEqual('true'); + }); + + describe(`switching an installer >`, () => { + + beforeEach(() => { + // enable first three installers + let i = 3; + while (i > 0) { + angular.element(getAllInstallerElements().get(i)).find('md-switch').click(); + i--; + } + $timeout.flush(); + }); + + it(`should enable edit-mode overlay >`, () => { + expect(editModeOverlay.attr('aria-hidden')).toEqual('false'); + }); + + it(`should update installers list in stack configuration >`, () => { + const enabledInstallers = getInstallersFromStack(controller.stack); + expect(enabledInstallers.length).toEqual(initiallyEnabledInstallers.length + 3); + }); + + describe(`click on "Cancel" button >`, () => { + + beforeEach(() => { + cancelButton.click(); + }); + + it(`should hide edit-mode overlay >`, () => { + expect(editModeOverlay.attr('aria-hidden')).toEqual('true'); + }); + + it (`should reset installers list in stack configuration >`, () => { + const enabledInstallers = getInstallersFromStack(controller.stack); + expect(enabledInstallers.length).toEqual(initiallyEnabledInstallers.length); + }); + + it(`should reset installer switchers to initial state >`, () => { + const enabledInstallerElements = getEnabledInstallerElements(); + expect(enabledInstallerElements.length).toEqual(initiallyEnabledInstallers.length); + }); + + }); + + describe(`click on "Save" button >`, () => { + + it(`should save changes >`, () => { + $httpBackend.expectPUT('/api/stack/blank-default', function (stackJson: string) { + const stack = angular.fromJson(stackJson); + return angular.equals( + getInstallersFromStack(controller.stack), + getInstallersFromStack(stack) + ); + }).respond(200); + + saveButton.click(); + $httpBackend.flush(); + $timeout.flush(); + }); + + it(`should hide edit-mode overlay >`, () => { + $httpBackend.expectPUT('/api/stack/blank-default').respond(200); + $httpBackend.expectGET('/api/stack?maxItems=50').respond(200, angular.toJson([controller.stack])); + saveButton.click(); + $httpBackend.flush(); + $timeout.flush(); + + expect(editModeOverlay.attr('aria-hidden')).toEqual('true'); + }); + + }); + + }); + + }); + + }); + + }); + +}); + +function getInitData() { + const stackId = 'blank-default'; + return { stackId: stackId, stack: getTestStack() }; +} + +function getTestStack(): che.IStack { + return { + 'description': 'Default Blank Stack.', + 'scope': 'general', + 'tags': ['Blank', 'Ubuntu', 'Git'], + 'creator': 'ide', + 'workspaceConfig': { + 'environments': { + 'default': { + 'recipe': { + 'type': 'dockerimage', + 'content': 'eclipse/ubuntu_jdk8' + }, + 'machines': { + 'dev-machine': { + 'env': { 'CHE_MACHINE_NAME': 'dev-machine' }, + 'volumes': {}, + 'installers': ['org.eclipse.che.ws-agent'], + 'servers': { + 'tomcat8-debug': { + 'protocol': 'http', + 'port': '8000' + }, + 'tomcat8': { + 'protocol': 'http', + 'port': '8080' + } + }, + 'attributes': { 'memoryLimitBytes': '2147483648' } + } + } + } + }, + 'projects': [], + 'commands': [], + 'defaultEnv': 'default', + 'name': 'default' + }, + 'components': [ + { 'version': '16.04', 'name': 'Ubuntu' }, + { 'version': '1.8.0_162', 'name': 'JDK' }, + { 'version': '3.3.9', 'name': 'Maven' }, + { 'version': '8.0.24', 'name': 'Tomcat' } + ], + 'name': 'Blank', + 'id': 'blank-default', + 'links': [ + { + 'rel': 'remove stack', + 'href': 'http://localhost:8080/api/stack/blank-default', + 'method': 'DELETE', + 'parameters': [] + }, + { + 'rel': 'get stack by id', + 'produces': 'application/json', + 'href': 'http://localhost:8080/api/stack/blank-default', + 'method': 'GET', + 'parameters': [] + } + ] + }; +} + +function mockInstallers(backend: CheHttpBackend): Array { + const installers = >[ + { 'version': '1.0.0', 'name': 'Terminal', 'id': 'org.eclipse.che.terminal' }, + { 'version': '1.0.1', 'name': 'Workspace API', 'id': 'org.eclipse.che.ws-agent' }, + { 'version': '1.0.1', 'name': 'Terminal', 'id': 'org.eclipse.che.terminal' }, + { 'version': '1.0.1', 'name': 'TypeScript language server', 'id': 'org.eclipse.che.ls.js-ts' }, + { 'version': '1.0.0', 'name': 'Workspace API', 'id': 'org.eclipse.che.ws-agent' }, + { 'version': '5.4.0', 'name': 'PHP language server', 'id': 'org.eclipse.che.ls.php' }, + { 'version': '5.3.7', 'name': 'PHP language server', 'id': 'org.eclipse.che.ls.php' }, + { 'version': '1.0.0', 'name': 'SSH', 'id': 'org.eclipse.che.ssh' }, + { 'version': '2.0.1', 'name': 'PHP language server', 'id': 'org.eclipse.che.ls.php' }, + { 'version': '1.0.0', 'name': 'Exec', 'id': 'org.eclipse.che.exec' }, + { 'version': '1.0.1', 'name': 'C# language server', 'id': 'org.eclipse.che.ls.csharp' }, + { 'version': '1.0.1', 'name': 'Exec', 'id': 'org.eclipse.che.exec' }, + { 'version': '1.0.0', 'name': 'Apache Camel language server', 'id': 'org.eclipse.che.ls.camel' }, + { 'version': '1.0.0', 'name': 'Git credentials', 'id': 'org.eclipse.che.git-credentials' }, + { 'version': '1.0.0', 'name': 'Clangd language server', 'id': 'org.eclipse.che.ls.clangd' }, + { 'version': '1.0.3', 'name': 'Python language server', 'id': 'org.eclipse.che.ls.python' }, + { 'version': '0.1.7', 'name': 'Golang language server', 'id': 'org.eclipse.che.ls.golang' }, + { 'version': '1.0.4', 'name': 'Python language server', 'id': 'org.eclipse.che.ls.python' }, + { 'version': '1.0.3', 'name': 'Workspace API', 'id': 'org.eclipse.che.ws-agent' }, + { 'version': '1.0.2', 'name': 'Workspace API', 'id': 'org.eclipse.che.ws-agent' }, + { 'version': '1.0.0', 'name': 'File sync', 'id': 'org.eclipse.che.unison' }, + { 'version': '1.0.1', 'name': 'JSON language server', 'id': 'org.eclipse.che.ls.json' }, + { 'version': '1.0.0', 'name': 'Yaml language server', 'id': 'org.eclipse.che.ls.yaml' }, + { 'version': '1.0.0', 'name': 'Simple Test language server', 'id': 'org.eclipse.che.test.ls' } + ]; + for (const installer of installers) { + backend.addInstaller(installer as che.IAgent); + } + backend.installersBackendSetup(); + + return installers; +} diff --git a/src/app/workspaces/workspace-details/environments/environments.html b/src/app/workspaces/workspace-details/environments/environments.html index 8cc61f512e6..20d66d8aa53 100755 --- a/src/app/workspaces/workspace-details/environments/environments.html +++ b/src/app/workspaces/workspace-details/environments/environments.html @@ -58,8 +58,8 @@ che-label-name="Machines" class="che-label-container-last"> - { + return this.machine; + }, (newMachine: IEnvironmentManagerMachine, oldMachine: IEnvironmentManagerMachine) => { + if (angular.equals(newMachine, oldMachine)) { + return; + } + this.init(); + }, true); + $scope.$on('$destroy', () => { + deRegistrationFn(); if (this.timeoutPromise) { $timeout.cancel(this.timeoutPromise); } @@ -80,19 +92,15 @@ export class WorkspaceMachineConfigController { * Sets initial values */ init(): void { - this.machine = this.lodash.find(this.machinesList, (machine: any) => { - return machine.name === this.machineName; - }); - - this.machineConfig = { - source: this.environmentManager.getSource(this.machine), - isDev: this.environmentManager.isDev(this.machine), - memoryLimitBytes: this.environmentManager.getMemoryLimit(this.machine), - servers: this.environmentManager.getServers(this.machine), - installers: this.environmentManager.getAgents(this.machine), - canEditEnvVariables: this.environmentManager.canEditEnvVariables(this.machine), - envVariables: this.environmentManager.getEnvVariables(this.machine) - }; + this.machineName = this.machine.name; + + this.machineConfig.source = this.environmentManager.getSource(this.machine); + this.machineConfig.isDev = this.environmentManager.isDev(this.machine); + this.machineConfig.memoryLimitBytes = this.environmentManager.getMemoryLimit(this.machine); + this.machineConfig.servers = this.environmentManager.getServers(this.machine); + this.machineConfig.installers = this.environmentManager.getAgents(this.machine); + this.machineConfig.canEditEnvVariables = this.environmentManager.canEditEnvVariables(this.machine); + this.machineConfig.envVariables = this.environmentManager.getEnvVariables(this.machine); this.newDev = this.machineConfig.isDev; diff --git a/src/app/workspaces/workspace-details/environments/machine-config/machine-config.directive.ts b/src/app/workspaces/workspace-details/environments/machine-config/machine-config.directive.ts index b419aac3b84..4754c7097f6 100644 --- a/src/app/workspaces/workspace-details/environments/machine-config/machine-config.directive.ts +++ b/src/app/workspaces/workspace-details/environments/machine-config/machine-config.directive.ts @@ -33,7 +33,7 @@ export class WorkspaceMachineConfig implements ng.IDirective { bindToController = true; scope = { - machineName: '=', + machine: '=', machinesList: '=', environmentManager: '=', machineDevOnChange: '&', diff --git a/src/app/workspaces/workspace-details/environments/machine-config/machine-config.html b/src/app/workspaces/workspace-details/environments/machine-config/machine-config.html index c0aad68bc62..e726082b67a 100644 --- a/src/app/workspaces/workspace-details/environments/machine-config/machine-config.html +++ b/src/app/workspaces/workspace-details/environments/machine-config/machine-config.html @@ -68,8 +68,8 @@
- +
diff --git a/src/app/workspaces/workspace-details/machine-selector/machine-selector.controller.ts b/src/app/workspaces/workspace-details/machine-selector/machine-selector.controller.ts index 43b79b81e5f..2f962ed11fd 100644 --- a/src/app/workspaces/workspace-details/machine-selector/machine-selector.controller.ts +++ b/src/app/workspaces/workspace-details/machine-selector/machine-selector.controller.ts @@ -153,9 +153,7 @@ export class MachineSelectorController { } let name = this.selectedMachine && names.indexOf(this.selectedMachine.name) >= 0 ? this.selectedMachine.name : this.machinesList[0].name; - this.$timeout(() => { this.updateData(name); - }, 500); } /** diff --git a/src/app/workspaces/workspace-details/workspace-details.html b/src/app/workspaces/workspace-details/workspace-details.html index c51df2ff350..6909af482f8 100644 --- a/src/app/workspaces/workspace-details/workspace-details.html +++ b/src/app/workspaces/workspace-details/workspace-details.html @@ -77,8 +77,7 @@ - diff --git a/src/app/workspaces/workspace-details/workspace-machine-agents/machine-agents.controller.ts b/src/app/workspaces/workspace-details/workspace-machine-agents/machine-agents.controller.ts index 1592a070e14..4d7991d8de2 100644 --- a/src/app/workspaces/workspace-details/workspace-machine-agents/machine-agents.controller.ts +++ b/src/app/workspaces/workspace-details/workspace-machine-agents/machine-agents.controller.ts @@ -9,9 +9,8 @@ * Red Hat, Inc. - initial API and implementation */ 'use strict'; -import {IEnvironmentManagerMachine} from '../../../../components/api/environment/environment-manager-machine'; -import {EnvironmentManager} from '../../../../components/api/environment/environment-manager'; import {CheAgent} from '../../../../components/api/che-agent.factory'; +import { IEnvironmentManagerMachine } from '../../../../components/api/environment/environment-manager-machine'; export interface IAgentItem extends che.IAgent { isEnabled: boolean; @@ -29,46 +28,68 @@ const LATEST: string = 'latest'; */ export class MachineAgentsController { - static $inject = ['$scope', 'cheAgent', '$timeout']; + static $inject = ['$scope', 'cheAgent', '$timeout', '$q']; onChange: Function; agentOrderBy = 'name'; - agentsList: Array; + agentItemsList: Array; private cheAgent: CheAgent; private $timeout: ng.ITimeoutService; private timeoutPromise: ng.IPromise; - private selectedMachine: IEnvironmentManagerMachine; - private environmentManager: EnvironmentManager; - private agents: Array; + private machine: IEnvironmentManagerMachine; + private machineAgentsList: Array; + private availableAgents: Array; + private agentsToUpdate: Array = []; /** * Default constructor that is using resource */ - constructor($scope: ng.IScope, cheAgent: CheAgent, $timeout: ng.ITimeoutService) { + constructor($scope: ng.IScope, cheAgent: CheAgent, $timeout: ng.ITimeoutService, $q: ng.IQService) { this.cheAgent = cheAgent; this.$timeout = $timeout; - cheAgent.fetchAgents().then(() => { - this.agents = this.selectedMachine ? this.environmentManager.getAgents(this.selectedMachine) : []; - this.buildAgentsList(); - }); + this.availableAgents = cheAgent.getAgents(); + const availableAgentsDefer = $q.defer(); + if (this.availableAgents && this.availableAgents.length) { + availableAgentsDefer.resolve(); + } else { + cheAgent.fetchAgents().then(() => { + this.availableAgents = cheAgent.getAgents(); + availableAgentsDefer.resolve(); + }); + } + let resolved = false; + const machineAgentsDefer = $q.defer(); const deRegistrationFn = $scope.$watch(() => { - if (!this.environmentManager || !this.selectedMachine) { - return false; + return this.machine && this.machine.installers; + }, (newAgentsList: string[]) => { + if (!resolved && newAgentsList && newAgentsList.length) { + machineAgentsDefer.resolve(); + resolved = true; + + this.machineAgentsList = angular.copy(newAgentsList); + return; } - return !angular.equals(this.agents, this.environmentManager.getAgents(this.selectedMachine)); - }, (newVal: boolean) => { - if (!newVal) { + + if (angular.equals(newAgentsList, this.machineAgentsList)) { return; } - this.agents = this.environmentManager.getAgents(this.selectedMachine); + + this.machineAgentsList = angular.copy(newAgentsList); this.buildAgentsList(); - }, true); + }); + + $q.all([availableAgentsDefer.promise, machineAgentsDefer.promise]).then(() => { + this.buildAgentsList(); + }) ; $scope.$on('$destroy', () => { deRegistrationFn(); + if (this.timeoutPromise) { + this.$timeout.cancel(this.timeoutPromise); + } }); } @@ -76,8 +97,11 @@ export class MachineAgentsController { * Builds agents list. */ buildAgentsList(): void { - const agents = this.cheAgent.getAgents(); - this.agentsList = agents.map((agentItem: IAgentItem) => { + if (!this.machineAgentsList) { + return; + } + + this.agentItemsList = this.availableAgents.map((agentItem: IAgentItem) => { this.checkAgentLatestVersion(agentItem); return agentItem; }); @@ -90,16 +114,23 @@ export class MachineAgentsController { updateAgent(agent: IAgentItem): void { this.$timeout.cancel(this.timeoutPromise); + this.agentsToUpdate.push(agent); + this.timeoutPromise = this.$timeout(() => { - const index = this.agents.indexOf(agent.id); - if (agent.isEnabled) { - if (index === -1) { - this.agents.push(agent.id); + this.agentsToUpdate.forEach((agent: IAgentItem) => { + const index = this.machineAgentsList.indexOf(agent.id); + if (agent.isEnabled) { + if (index === -1) { + this.machineAgentsList.push(agent.id); + } + } else if (index >= 0) { + this.machineAgentsList.splice(index, 1); } - } else if (index >= 0) { - this.agents.splice(index, 1); - } - this.environmentManager.setAgents(this.selectedMachine, this.agents); + }); + this.agentsToUpdate.length = 0; + this.machine.installers = angular.copy(this.machineAgentsList); + + this.buildAgentsList(); this.onChange(); }, 500); } @@ -119,8 +150,8 @@ export class MachineAgentsController { * @param agentItem {IAgentItem} */ checkEnabled(agentItem: IAgentItem): void { - for (let i = 0; i < this.agents.length; i++) { - let agent = this.agents[i]; + for (let i = 0; i < this.machineAgentsList.length; i++) { + let agent = this.machineAgentsList[i]; // try to extract agent's version in format id:version: let groups = agent.match(/[^:]+(:(.+)){0,1}/); let id; diff --git a/src/app/workspaces/workspace-details/workspace-machine-agents/machine-agents.directive.ts b/src/app/workspaces/workspace-details/workspace-machine-agents/machine-agents.directive.ts index 097b70a6bce..2674a48ec54 100644 --- a/src/app/workspaces/workspace-details/workspace-machine-agents/machine-agents.directive.ts +++ b/src/app/workspaces/workspace-details/workspace-machine-agents/machine-agents.directive.ts @@ -40,8 +40,7 @@ export class MachineAgents implements ng.IDirective { constructor() { // scope values this.scope = { - environmentManager: '=', - selectedMachine: '=', + machine: '=selectedMachine', onChange: '&' }; } diff --git a/src/app/workspaces/workspace-details/workspace-machine-agents/machine-agents.html b/src/app/workspaces/workspace-details/workspace-machine-agents/machine-agents.html index 82a9a317277..d9896777d8b 100644 --- a/src/app/workspaces/workspace-details/workspace-machine-agents/machine-agents.html +++ b/src/app/workspaces/workspace-details/workspace-machine-agents/machine-agents.html @@ -2,7 +2,7 @@
- +
+ ng-if="machineAgentsController.agentItemsList && machineAgentsController.agentItemsList.length > 0">
+ ng-repeat="agentItem in machineAgentsController.agentItemsList | orderBy:[machineAgentsController.agentOrderBy, '-version' ]">
{ + this.agentsMap.set(agent.id, agent); this.agents.push(agent); }); defer.resolve(this.agents); diff --git a/src/components/api/test/che-http-backend.ts b/src/components/api/test/che-http-backend.ts index 68926198d7f..cb27dab41ab 100644 --- a/src/components/api/test/che-http-backend.ts +++ b/src/components/api/test/che-http-backend.ts @@ -45,6 +45,9 @@ export class CheHttpBackend { private permissionsMap: Map>; private resourcesMap: Map>; + private installersMap: Map = new Map(); + private installersList: Array = []; + /** * Constructor to use */ @@ -657,4 +660,27 @@ export class CheHttpBackend { organizationResourcesMap.set(scope, [resource]); } } + + /** + * Setup backend for installers. + */ + installersBackendSetup(): void { + for (const [installerId, installer] of this.installersMap) { + this.$httpBackend.when('GET', `/api/installer/${installerId}`).respond(installer); + } + this.$httpBackend.when('GET', '/api/installer').respond(this.installersList); + } + + /** + * Add installers. + * @param {che.IAgent} installer an installer to add + */ + addInstaller(installer: che.IAgent): void { + const latest = this.installersMap.get(installer.id); + if (!latest || installer.version > latest.version) { + this.installersMap.set(installer.id, installer); + } + this.installersList.push(installer); + } + }