Factory migration (#4413)

6.19.x
Max Shaposhnik 2017-04-03 15:11:45 +03:00 committed by GitHub
parent 2fcb0fbc1f
commit 62d32687c2
266 changed files with 21375 additions and 207 deletions

View File

@ -155,6 +155,10 @@
<groupId>org.eclipse.che.plugin</groupId>
<artifactId>che-plugin-github-oauth2</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.che.plugin</groupId>
<artifactId>che-plugin-github-pullrequest</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.che.plugin</groupId>
<artifactId>che-plugin-github-shared</artifactId>
@ -243,6 +247,14 @@
<groupId>org.eclipse.che.plugin</groupId>
<artifactId>che-plugin-product-info</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.che.plugin</groupId>
<artifactId>che-plugin-pullrequest-ide</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.che.plugin</groupId>
<artifactId>che-plugin-pullrequest-shared</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.che.plugin</groupId>
<artifactId>che-plugin-python-lang-ide</artifactId>

View File

@ -1,2 +1,6 @@
RewriteRule ^/api/(.*)$ /wsmaster/api/$1 [L]
RewriteRule ^/factory\\?(.*)$ /dashboard/#/load-factory/$1 [R,NE]
RewriteRule ^/factory/(.*)$ /dashboard/#/load-factory/$1 [R,NE]
RewriteRule ^/f/?(.*)$ /dashboard/#/load-factory/$1 [R,NE]
RewriteRule ^/f\\?(.*)$ /dashboard/#/load-factory/$1 [R,NE]

View File

@ -106,6 +106,10 @@
<groupId>org.eclipse.che.core</groupId>
<artifactId>che-core-api-core</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.che.core</groupId>
<artifactId>che-core-api-factory</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.che.core</groupId>
<artifactId>che-core-api-machine</artifactId>
@ -178,6 +182,10 @@
<groupId>org.eclipse.che.plugin</groupId>
<artifactId>che-plugin-docker-machine</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.che.plugin</groupId>
<artifactId>che-plugin-github-factory-resolver</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.che.plugin</groupId>
<artifactId>che-plugin-github-oauth2</artifactId>
@ -198,10 +206,18 @@
<groupId>org.eclipse.che.plugin</groupId>
<artifactId>che-plugin-openshift-client</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.che.plugin</groupId>
<artifactId>che-plugin-pullrequest-server</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.che.plugin</groupId>
<artifactId>che-plugin-ssh-machine</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.che.plugin</groupId>
<artifactId>che-plugin-url-factory</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.persistence</groupId>
<artifactId>eclipselink</artifactId>
@ -237,11 +253,6 @@
<artifactId>logback-classic</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.eclipse.che.core</groupId>
<artifactId>che-core-api-factory</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.eclipse.che.core</groupId>
<artifactId>che-core-commons-test</artifactId>

View File

@ -29,6 +29,10 @@ import org.eclipse.che.api.agent.shared.model.Agent;
import org.eclipse.che.api.core.rest.CheJsonProvider;
import org.eclipse.che.api.core.rest.MessageBodyAdapter;
import org.eclipse.che.api.core.rest.MessageBodyAdapterInterceptor;
import org.eclipse.che.api.factory.server.FactoryAcceptValidator;
import org.eclipse.che.api.factory.server.FactoryCreateValidator;
import org.eclipse.che.api.factory.server.FactoryEditValidator;
import org.eclipse.che.api.factory.server.FactoryParametersResolver;
import org.eclipse.che.api.machine.shared.Constants;
import org.eclipse.che.api.user.server.TokenValidator;
import org.eclipse.che.api.workspace.server.WorkspaceConfigMessageBodyAdapter;
@ -36,6 +40,7 @@ import org.eclipse.che.api.workspace.server.WorkspaceMessageBodyAdapter;
import org.eclipse.che.api.workspace.server.stack.StackMessageBodyAdapter;
import org.eclipse.che.core.db.schema.SchemaInitializer;
import org.eclipse.che.inject.DynaModule;
import org.eclipse.che.plugin.github.factory.resolver.GithubFactoryParametersResolver;
import org.flywaydb.core.internal.util.PlaceholderReplacer;
import javax.sql.DataSource;
@ -62,6 +67,18 @@ public class WsMasterModule extends AbstractModule {
bind(org.eclipse.che.core.db.DBInitializer.class).asEagerSingleton();
bind(PlaceholderReplacer.class).toProvider(org.eclipse.che.core.db.schema.impl.flyway.PlaceholderReplacerProvider.class);
//factory
bind(FactoryAcceptValidator.class).to(org.eclipse.che.api.factory.server.impl.FactoryAcceptValidatorImpl.class);
bind(FactoryCreateValidator.class).to(org.eclipse.che.api.factory.server.impl.FactoryCreateValidatorImpl.class);
bind(FactoryEditValidator.class).to(org.eclipse.che.api.factory.server.impl.FactoryEditValidatorImpl.class);
bind(org.eclipse.che.api.factory.server.FactoryService.class);
install(new org.eclipse.che.api.factory.server.jpa.FactoryJpaModule());
Multibinder<FactoryParametersResolver> factoryParametersResolverMultibinder =
Multibinder.newSetBinder(binder(), FactoryParametersResolver.class);
factoryParametersResolverMultibinder.addBinding()
.to(GithubFactoryParametersResolver.class);
install(new org.eclipse.che.plugin.docker.compose.ComposeModule());
bind(org.eclipse.che.api.user.server.CheUserCreator.class);

View File

@ -38,6 +38,18 @@
<class>org.eclipse.che.api.machine.server.model.impl.SnapshotImpl</class>
<class>org.eclipse.che.api.machine.server.recipe.RecipeImpl</class>
<class>org.eclipse.che.api.factory.server.model.impl.FactoryImpl</class>
<class>org.eclipse.che.api.factory.server.model.impl.OnAppClosedImpl</class>
<class>org.eclipse.che.api.factory.server.model.impl.OnProjectsLoadedImpl</class>
<class>org.eclipse.che.api.factory.server.model.impl.OnAppLoadedImpl</class>
<class>org.eclipse.che.api.factory.server.model.impl.PoliciesImpl</class>
<class>org.eclipse.che.api.factory.server.model.impl.ActionImpl</class>
<class>org.eclipse.che.api.factory.server.model.impl.AuthorImpl</class>
<class>org.eclipse.che.api.factory.server.model.impl.ButtonAttributesImpl</class>
<class>org.eclipse.che.api.factory.server.model.impl.ButtonImpl</class>
<class>org.eclipse.che.api.factory.server.model.impl.IdeImpl</class>
<class>org.eclipse.che.api.factory.server.FactoryImage</class>
<class>org.eclipse.che.api.ssh.server.model.impl.SshPairImpl</class>
<exclude-unlisted-classes>true</exclude-unlisted-classes>

View File

@ -32,11 +32,6 @@ public interface Policies {
*/
Long getUntil();
/**
* Re-open projects on factory 2-nd click
*/
String getMatch();
/**
* Workspace creation strategy
*/

View File

@ -33,6 +33,7 @@ public class CheCacheDisablingFilter extends CacheDisablingFilter {
private Set<Pattern> actionPatterns = new HashSet<>();
@Override
public void init(FilterConfig filterConfig) {
Enumeration<String> names = filterConfig.getInitParameterNames();
while (names.hasMoreElements()) {

View File

@ -33,6 +33,7 @@ public class CheCacheForcingFilter extends CacheForcingFilter {
private Set<Pattern> actionPatterns = new HashSet<>();
@Override
public void init(FilterConfig filterConfig) {
Enumeration<String> names = filterConfig.getInitParameterNames();
while (names.hasMoreElements()) {

View File

@ -107,23 +107,27 @@ public class CheCacheDisablingFilterTest {
private class MockFilterConfig implements FilterConfig {
private final Map<String, String> filterParams = new HashMap<>();
public MockFilterConfig() {
MockFilterConfig() {
this.filterParams.put("pattern1", "^.*\\.nocache\\..*$");
this.filterParams.put("pattern2", "^.*/_app/.*$");
}
@Override
public String getFilterName() {
return this.getClass().getName();
}
@Override
public ServletContext getServletContext() {
throw new UnsupportedOperationException("The method does not supported in " + this.getClass());
}
@Override
public String getInitParameter(String key) {
return this.filterParams.get(key);
}
@Override
public Enumeration<String> getInitParameterNames() {
return Collections.enumeration(filterParams.keySet());
}

View File

@ -108,23 +108,27 @@ public class CheCacheForcingFilterTest {
private class MockFilterConfig implements FilterConfig {
private final Map<String, String> filterParams = new HashMap<>();
public MockFilterConfig() {
MockFilterConfig() {
this.filterParams.put("pattern1", "^.*\\.cache\\..*$");
this.filterParams.put("pattern2", "^.*/_app/.*$");
}
@Override
public String getFilterName() {
return this.getClass().getName();
}
@Override
public ServletContext getServletContext() {
throw new UnsupportedOperationException("The method does not supported in " + this.getClass());
}
@Override
public String getInitParameter(String key) {
return this.filterParams.get(key);
}
@Override
public Enumeration<String> getInitParameterNames() {
return Collections.enumeration(filterParams.keySet());
}

View File

@ -0,0 +1,116 @@
/*
* Copyright (c) 2015-2017 Codenvy, S.A.
* 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:
* Codenvy, S.A. - initial API and implementation
*/
'use strict';
/**
* Defines controller of directive for displaying action box.
* @ngdoc controller
* @name factory.directive:FactoryActionBoxController
* @author Florent Benoit
*/
export class FactoryActionBoxController {
private $mdDialog: ng.material.IDialogService;
private actions: Array<any>;
private selectedAction: string;
private factoryObject: any;
private lifecycle: any;
private onChange: Function;
/**
* Default constructor that is using resource injection
* @ngInject for Dependency injection
*/
constructor($mdDialog: ng.material.IDialogService) {
this.$mdDialog = $mdDialog;
this.actions = [];
this.actions.push({name : 'RunCommand', id: 'runcommand'});
this.actions.push({name : 'openFile', id: 'openfile'});
this.selectedAction = this.actions[0].id;
}
/**
* Edit the action based on the provided index
* @param $event the mouse event
* @param index the index in the array of factory actions
*/
editAction($event: any, index: number): void {
let action = this.factoryObject.ide[this.lifecycle].actions[index];
this.$mdDialog.show({
targetEvent: $event,
controller: 'FactoryActionDialogEditController',
controllerAs: 'factoryActionDialogEditCtrl',
bindToController: true,
clickOutsideToClose: true,
locals: {
callbackController: this,
index: index,
// selectedAction: action
selectedValue: action.properties
},
templateUrl: 'app/factories/create-factory/action/factory-action-edit.html'
});
}
/**
* Edit action callback.
*
* @param index the index in the array of factory actions
* @param newValue new value
*/
callbackEditAction(index: number, newValue: any): void {
this.factoryObject.ide[this.lifecycle].actions[index].properties = newValue;
this.onChange();
}
addAction(): void {
if (!this.factoryObject.ide) {
this.factoryObject.ide = {};
}
if (!this.factoryObject.ide[this.lifecycle]) {
this.factoryObject.ide[this.lifecycle] = {};
this.factoryObject.ide[this.lifecycle].actions = [];
}
let actionToAdd;
if ('openfile' === this.selectedAction) {
actionToAdd = {
"properties": {
"file": this.selectedParam
},
"id": "openFile"
};
} else if ('runcommand' === this.selectedAction) {
actionToAdd = {
"properties": {
"name": this.selectedParam
},
"id": "runCommand"
};
}
if (actionToAdd) {
this.factoryObject.ide[this.lifecycle].actions.push(actionToAdd);
}
this.onChange();
}
/**
* Remove action based on the provided index
* @param index the index in the array of factory actions
*/
removeAction(index: number): void {
this.factoryObject.ide[this.lifecycle].actions.splice(index, 1);
this.onChange();
}
}

View File

@ -0,0 +1,55 @@
/*
* Copyright (c) 2015-2017 Codenvy, S.A.
* 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:
* Codenvy, S.A. - initial API and implementation
*/
'use strict';
/**
* Defines a directive for displaying action box.
* @author Florent Benoit
*/
export class FactoryActionBox {
private restrict: string;
private templateUrl: string;
private replace: boolean;
private controller: string;
private controllerAs: string;
private bindToController: boolean;
private scope: {
[propName: string]: string;
};
/**
* Default constructor that is using resource
* @ngInject for Dependency injection
*/
constructor() {
this.restrict = 'E';
this.templateUrl = 'app/factories/create-factory/action/factory-action-box.html';
this.replace = false;
this.controller = 'FactoryActionBoxController';
this.controllerAs = 'factoryActionBoxCtrl';
this.bindToController = true;
// scope values
this.scope = {
lifecycle: '@cdvyLifecycle',
actionTitle: '@?cdvyActionTitle',
callbackController: '=cdvyCallbackController',
factoryObject: '=cdvyFactoryObject',
onChange: '&cdvyOnChange'
};
}
}

View File

@ -0,0 +1,56 @@
<form name="actionsForm">
<div class="factory-actions-panel" layout="column">
<div layout="column" layout-align="start start">
<div layout="row" layout-align="start start"
class="factory-actions-input">
<che-select che-option-values="factoryActionBoxCtrl.actions"
che-place-holder="Select action"
aria-label="Select action"
che-size="3"
che-value="factoryActionBoxCtrl.selectedAction">
</che-select>
<che-input che-name="value"
che-form="actionsForm"
che-place-holder="Enter value"
aria-label="Enter value"
required
ng-model="factoryActionBoxCtrl.selectedParam">
<div ng-message="required">Param is required.</div>
</che-input>
</div>
<che-button-primary che-button-title="Add"
ng-click="factoryActionBoxCtrl.addAction()"
ng-disabled="actionsForm.$invalid"></che-button-primary>
</div>
<div ng-if="factoryActionBoxCtrl.factoryObject.ide.onProjectsLoaded.actions.length > 0"
class="factory-actions-list">
<che-list>
<che-list-item ng-repeat="action in factoryActionBoxCtrl.factoryObject.ide.onProjectsLoaded.actions"
flex-gt-sm="100" flex-sm="33">
<div layout="row" flex>
<div layout="column"
layout-align="start start"
class="factory-actions-row-action-name">
<span>{{action.id}}</span>
</div>
<div flex layout-align="start start"
class="factory-actions-row-action-param"
ng-click="factoryActionBoxCtrl.editAction($event, $index)">
{{action.properties.name ? action.properties.name : action.properties.file}}
</div>
<div flex="10" layout="row" layout-align="center start"
class="che-list-actions">
<div ng-click="factoryActionBoxCtrl.removeAction($index)" class="factory-commands-widget-actions">
<span class="fa fa-times-circle"></span>
</div>
<div ng-click="factoryActionBoxCtrl.editAction($event, $index)" class="factory-commands-widget-actions">
<span class="fa fa-edit"></span>
</div>
</div>
</div>
</che-list-item>
</che-list>
</div>
</div>
</form>

View File

@ -0,0 +1,57 @@
.factory-actions-panel
margin-top -6px
& > div
margin-bottom 10px
.factory-actions-input
min-height 50px
height 50px
.che-select
margin-top 4px
margin-right 20px
.factory-actions-list
max-width 600px
md-list
margin 0
div
outline none
md-icon
font-size 18px
height auto
width auto
color $clear-foggy-sky-color
line-height 18px
.factory-commands-widget-actions
display inline-block
line-height inherit
min-height 100%
cursor pointer
outline none
span
line-height inherit
.fa-times-circle::before,
.fa-edit::before
position relative
top 3px
.factory-actions-row-action-name
min-width 220px
width 220px
span
padding 2px
.factory-actions-row-action-param
cursor pointer
.factory-edit-action-input
margin-top -6px

View File

@ -0,0 +1,55 @@
/*
* Copyright (c) 2015-2017 Codenvy, S.A.
* 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:
* Codenvy, S.A. - initial API and implementation
*/
'use strict';
import {FactoryActionBoxController} from './factory-action-box.controller';
/**
* @ngdoc controller
* @name factory.directive:FactoryActionDialogEditController
* @description This class is handling the controller for editing action of a factory
* @author Florent Benoit
*/
export class FactoryActionDialogEditController {
isName: boolean;
isFile: boolean;
selectedValue: { name: string; file: string };
private $mdDialog: ng.material.IDialogService;
private index: number;
private callbackController: FactoryActionBoxController;
/**
* Default constructor that is using resource
* @ngInject for Dependency injection
*/
constructor($mdDialog: ng.material.IDialogService) {
this.$mdDialog = $mdDialog;
this.isName = angular.isDefined(this.selectedValue.name);
this.isFile = angular.isDefined(this.selectedValue.file);
}
/**
* Callback of the edit button of the dialog.
*/
edit() {
this.$mdDialog.hide();
this.callbackController.callbackEditAction(this.index, this.selectedValue);
}
/**
* Callback of the cancel button of the dialog.
*/
abort() {
this.$mdDialog.hide();
}
}

View File

@ -0,0 +1,31 @@
<che-popup title="Edit action" on-close="factoryActionDialogEditCtrl.abort()">
<form name="editActionForm" flex>
<div ng-if="factoryActionDialogEditCtrl.isName">
<che-input focusable
che-name="Editing"
che-form="editActionForm"
che-label-name="Param"
required
ng-model="factoryActionDialogEditCtrl.selectedValue.name">
<div ng-message="required">Param is required</div>
</che-input>
</div>
<div ng-if="factoryActionDialogEditCtrl.isFile">
<che-input focusable
che-name="Editing"
che-form="editActionForm"
che-label-name="Param"
required
ng-model="factoryActionDialogEditCtrl.selectedValue.file">
<div ng-message="required">Param is required</div>
</che-input>
</div>
<che-button-notice che-button-title="Close"
ng-click="factoryActionDialogEditCtrl.abort()">
</che-button-notice>
<che-button-primary
che-button-title="Edit" ng-disabled="editActionForm.$invalid"
ng-click="factoryActionDialogEditCtrl.edit()"></che-button-primary>
</form>
</che-popup>

View File

@ -0,0 +1,48 @@
/*
* Copyright (c) 2015-2017 Codenvy, S.A.
* 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:
* Codenvy, S.A. - initial API and implementation
*/
'use strict';
/**
* @ngdoc controller
* @name factory.directive:FactoryCommandDialogEditController
* @description This class is handling the controller for editing command of a factory
* @author Florent Benoit
*/
export class FactoryCommandDialogEditController {
private $mdDialog: ng.material.IDialogService;
private callbackController: any;
private index: number;
private selectedValue: any;
/**
* Default constructor that is using resource
* @ngInject for Dependency injection
*/
constructor($mdDialog: ng.material.IDialogService) {
this.$mdDialog = $mdDialog;
}
/**
* Callback of the edit button of the dialog.
*/
edit(): void {
this.$mdDialog.hide();
this.callbackController.callbackEditAction(this.index, this.selectedValue);
}
/**
* Callback of the cancel button of the dialog.
*/
abort(): void {
this.$mdDialog.hide();
}
}

View File

@ -0,0 +1,18 @@
<che-popup title="Edit command" on-close="factoryCommandDialogEditCtrl.abort()">
<form name="editActionForm" flex>
<che-input focusable
che-name="Editing"
che-form="editActionForm"
che-label-name="Param"
required
ng-model="factoryCommandDialogEditCtrl.selectedValue">
<div ng-message="required">Param is required</div>
</che-input>
<che-button-notice che-button-title="Close"
ng-click="factoryCommandDialogEditCtrl.abort()">
</che-button-notice>
<che-button-primary
che-button-title="Edit" ng-disabled="editActionForm.$invalid"
ng-click="factoryCommandDialogEditCtrl.edit()"></che-button-primary>
</form>
</che-popup>

View File

@ -0,0 +1,104 @@
/*
* Copyright (c) 2015-2017 Codenvy, S.A.
* 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:
* Codenvy, S.A. - initial API and implementation
*/
'use strict';
/**
* Defines controller of directive for displaying factory command.
* @ngdoc controller
* @name factory.directive:FactoryCommandController
* @author Florent Benoit
*/
export class FactoryCommandController {
private $mdDialog: ng.material.IDialogService;
private factoryObject: any;
private onChange: Function;
/**
* Default constructor that is using resource injection
* @ngInject for Dependency injection
*/
constructor($mdDialog: ng.material.IDialogService) {
this.$mdDialog = $mdDialog;
}
/**
* User clicked on the add button to add a new command
* @param $event
*/
addCommand(): void {
if (!this.factoryObject) {
this.factoryObject = {};
}
if (!this.factoryObject.workspace) {
this.factoryObject.workspace = {};
}
if (!this.factoryObject.workspace.commands) {
this.factoryObject.workspace.commands = [];
}
let command = {
"commandLine": this.commandLine,
"name": this.commandLineName,
"attributes": {
"previewUrl": ""
},
"type": "custom"
};
this.factoryObject.workspace.commands.push(command);
this.onChange();
}
/**
* Remove command based on the provided index
* @param index the index in the array of workspace commands
*/
removeCommand(index: number): void {
this.factoryObject.workspace.commands.splice(index, 1);
this.onChange();
}
/**
* Edit the command based on the provided index
* @param $event the mouse event
* @param index the index in the array of workspace commands
*/
editCommand($event: any, index: number): void {
this.$mdDialog.show({
targetEvent: $event,
controller: 'FactoryCommandDialogEditController',
controllerAs: 'factoryCommandDialogEditCtrl',
bindToController: true,
clickOutsideToClose: true,
locals: {
callbackController: this,
index: index,
selectedValue: this.factoryObject.workspace.commands[index].commandLine
},
templateUrl: 'app/factories/create-factory/command/factory-command-edit.html'
});
}
/**
* Callback on edit action.
*
* @param index commands index
* @param newValue value to update with
*/
callbackEditAction(index: number, newValue: string) {
this.factoryObject.workspace.commands[index].commandLine = newValue;
this.onChange();
}
}

View File

@ -0,0 +1,50 @@
/*
* Copyright (c) 2015-2017 Codenvy, S.A.
* 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:
* Codenvy, S.A. - initial API and implementation
*/
'use strict';
/**
* Defines a directive for displaying factory commands.
* @author Florent Benoit
*/
export class FactoryCommand {
private restrict: string;
private templateUrl: string;
private replace: boolean;
private controller: string;
private controllerAs: string;
private bindToController: boolean;
private scope: {
[propName: string]: string;
};
/**
* Default constructor that is using resource
* @ngInject for Dependency injection
*/
constructor() {
this.restrict = 'E';
this.templateUrl = 'app/factories/create-factory/command/factory-command.html';
this.replace = false;
this.controller = 'FactoryCommandController';
this.controllerAs = 'factoryCommandCtrl';
this.bindToController = true;
// scope values
this.scope = {
factoryObject: '=cdvyFactoryObject',
onChange: '&cdvyOnChange'
};
}
}

View File

@ -0,0 +1,74 @@
<form name="commandsForm">
<div class="factory-commands-panel" layout="column">
<div layout="column" layout-align="start start">
<div layout="row" layout-align="start start"
class="factory-commands-input">
<che-input che-form="commandsForm"
che-name="name"
che-place-holder="Name of the command"
aria-label="Name of the command"
ng-model="factoryCommandCtrl.commandLineName"
required
ng-minlength="1"
ng-maxlength="20"
ng-pattern="/^[A-Za-z0-9_\-\.]+$/">
<div ng-message="required">A name is required.</div>
<div ng-message="pattern">Workspace name may contain digits, latin letters, _ , . , - and should start only
with digits, latin
letters or underscores
</div>
<div ng-message="minlength">The name has to be more than 1 character long.</div>
<div ng-message="maxlength">The name has to be less than 20 characters long.</div>
</che-input>
<che-input che-form="commandsForm"
che-name="commandLine"
aria-label="Command"
che-place-holder="Command Line: example: mvn clean install -f ${current.project.path}"
che-width="auto"
ng-model="factoryCommandCtrl.commandLine"
required
ng-minlength="1"
ng-maxlength="500">
<div ng-message="required">A name is required.</div>
<div ng-message="minlength">The name has to be more than 1 character long.</div>
<div ng-message="maxlength">The name has to be less than 500 characters long.</div>
</che-input>
</div>
<che-button-primary che-button-title="Add"
ng-click="factoryCommandCtrl.addCommand()"
ng-disabled="commandsForm.$invalid"></che-button-primary>
</div>
<div ng-if="factoryCommandCtrl.factoryObject.workspace.commands.length > 0"
class="factory-commands-list">
<che-list>
<che-list-item ng-repeat="command in factoryCommandCtrl.factoryObject.workspace.commands"
flex="100">
<div layout="row" flex>
<div layout="column"
layout-align="start start"
class="factory-commands-row-command-name">
<span>{{command.name}}</span>
</div>
<div flex
layout="column"
layout-align="start start"
ng-click="factoryCommandCtrl.editCommand($event, $index)"
class="factory-commands-row-command">
{{command.commandLine}}
</div>
<div flex="10" layout="row" layout-align="center start"
class="che-list-actions">
<div ng-click="factoryCommandCtrl.removeCommand($index)" class="factory-commands-widget-actions">
<span class="fa fa-times-circle"></span>
</div>
<div ng-click="factoryCommandCtrl.editCommand($event, $index)" class="factory-commands-widget-actions">
<span class="fa fa-edit"></span>
</div>
</div>
</div>
</che-list-item>
</che-list>
</div>
</div>
</form>

View File

@ -0,0 +1,53 @@
.factory-commands-panel
margin-top -6px
& > div
margin-bottom 10px
.factory-commands-input
min-height 50px
height 50px
.che-input
margin-right 20px
.factory-commands-list
max-width 600px
md-list
margin 0
div
outline none
md-icon
font-size 18px
height auto
width auto
color $clear-foggy-sky-color
line-height 18px
.factory-commands-widget-actions
display inline-block
line-height inherit
min-height 100%
cursor pointer
outline none
span
line-height inherit
.fa-times-circle::before,
.fa-edit::before
position relative
top 3px
.factory-commands-row-command-name
min-width 220px
width 220px
span
padding 2px
.factory-commands-row-command
cursor pointer

View File

@ -0,0 +1,106 @@
/*
* Copyright (c) 2015-2017 Codenvy, S.A.
* 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:
* Codenvy, S.A. - initial API and implementation
*/
'use strict';
import {CheNotification} from '../../../../components/notification/che-notification.factory';
import {CheAPI} from '../../../../components/api/che-api.factory';
/* global FileReader */
/**
* Controller for upload factory from the file.
* @author Oleksii Orel
*/
export class FactoryFromFileCtrl {
private cheAPI: CheAPI;
private cheNotification: CheNotification;
private uploader: any;
private isImporting: boolean;
private factoryContent: any;
/**
* Default constructor that is using resource injection
* @ngInject for Dependency injection
*/
constructor($filter: ng.IFilterService, cheAPI: CheAPI, cheNotification: CheNotification, FileUploader: any) {
'ngInject';
this.cheAPI = cheAPI;
this.cheNotification = cheNotification;
// if you want select just one file, you won't need to clear the input
FileUploader.FileSelect.prototype.isEmptyAfterSelection = function () {
return true;
};
this.uploader = new FileUploader();
// settings
this.uploader.queueLimit = 1; // maximum count of files
this.uploader.autoUpload = true; // automatically upload files after adding them to the queue
this.uploader.removeAfterUpload = true; // automatically remove files from the queue after uploading
this.isImporting = this.uploader.isUploading;
var ctrl = this;
// filters
this.uploader.filters.push({
name: 'sizeFilter',
fn: (item: any) => {
// file must not be smaller then some size
let isValidSize = item.size > 0 && item.size < 500000;
if (!isValidSize) {
ctrl.cheNotification.showError('File size error.');
}
return isValidSize;
}
});
this.uploader.filters.push({
name: 'typeFilter',
fn: (item: any) => {
// file must be json
let isValidItem = item.type === 'application/json' || item.type === '';
if (!isValidItem) {
ctrl.cheNotification.showError('File type error.');
}
return isValidItem;
}
});
// callback
this.uploader.onAfterAddingFile = function (fileItem) {
let uploadedFileName = fileItem._file.name;
let reader = new FileReader();
reader.readAsText(fileItem._file);
reader.onload = function () {
try {
ctrl.factoryContent = $filter('json')(angular.fromJson(reader.result), 2);
ctrl.cheNotification.showInfo('Successfully loaded file\'s configuration ' + uploadedFileName + '.');
} catch (e) {
// invalid JSON
ctrl.factoryContent = null;
ctrl.cheNotification.showError('Invalid JSON.');
}
};
reader.onerror = function (error: any) {
ctrl.cheNotification.showError(error.data.message ? error.data.message : 'Error reading file.');
console.log('Error reading file');
};
};
this.factoryContent = null;
}
}

View File

@ -0,0 +1,59 @@
/*
* Copyright (c) 2015-2017 Codenvy, S.A.
* 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:
* Codenvy, S.A. - initial API and implementation
*/
'use strict';
/**
* Defines a directive for configuring factory from file.
* @author Oleksii Orel
*/
export class FactoryFromFile {
private restrict: string;
private templateUrl: string;
private replace: boolean;
private controller: string;
private controllerAs: string;
private bindToController: boolean;
private scope: {
[propName: string]: string;
};
/**
* Default constructor that is using resource
* @ngInject for Dependency injection
*/
constructor() {
this.restrict = 'E';
this.templateUrl = 'app/factories/create-factory/config-file-tab/factory-from-file.html';
this.replace = false;
this.controller = 'FactoryFromFileCtrl';
this.controllerAs = 'factoryFromFileCtrl';
this.bindToController = true;
// scope values
this.scope = {
isImporting: '=cdvyIsImporting',
factoryContent: '=cdvyFactoryContent'
};
}
link($scope: ng.IScope, element: ng.IAugmentedJQuery) {
$scope.clickUpload = () => {
// search the input fields
let inputElements = element.find('input');
inputElements.eq(0).click();
};
}
}

View File

@ -0,0 +1,31 @@
<!--
CODENVY CONFIDENTIAL
__________________
[2015] - [2016] Codenvy, S.A.
All Rights Reserved.
NOTICE: All information contained herein is, and remains
the property of Codenvy S.A. and its suppliers,
if any. The intellectual and technical concepts contained
herein are proprietary to Codenvy S.A.
and its suppliers and may be covered by U.S. and Foreign Patents,
patents in process, and are protected by trade secret or copyright law.
Dissemination of this information or reproduction of this material
is strictly forbidden unless prior written permission is obtained
from Codenvy S.A..
-->
<div class="factory-from-file" layout="row" flex layout-align="start center">
<che-label-container che-label-name="File">
<div layout="row">
<button ng-disabled="factoryFromFileCtrl.uploader.queue.length > 0 || factoryFromFileCtrl.uploader.isUploading" tabindex="0"
md-theme="default" ng-click="clickUpload()"
class="md-accent md-raised md-hue-2 md-button md-default-theme">
<span class="ng-scope">Upload file</span>
</button>
<input type="file" accept="application/json" uploader="factoryFromFileCtrl.uploader" nv-file-select/>
</div>
</che-label-container>
</div>

View File

@ -0,0 +1,15 @@
.factory-from-file
min-height 80px
.factory-from-file label
font-size inherit
.factory-from-file input
display none
.factory-from-file button
box-shadow 0 2px 5px 0 $disabled-color
border-radius 2px !important
padding-right 20px
padding-left 20px
font-size 1.5em

View File

@ -0,0 +1,72 @@
/*
* Copyright (c) 2015-2017 Codenvy, S.A.
* 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:
* Codenvy, S.A. - initial API and implementation
*/
'use strict';
import {CreateFactoryCtrl} from '../create-factory/create-factory.controller';
import {FactoryFromWorkspaceCtrl} from '../create-factory/workspaces-tab/factory-from-workpsace.controller';
import {FactoryFromWorkspace} from '../create-factory/workspaces-tab/factory-from-workspace.directive';
import {FactoryFromFileCtrl} from '../create-factory/config-file-tab/factory-from-file.controller';
import {FactoryFromFile} from '../create-factory/config-file-tab/factory-from-file.directive';
import {FactoryFromTemplateCtrl} from '../create-factory/template-tab/factory-from-template.controller';
import {FactoryFromTemplate} from '../create-factory/template-tab/factory-from-template.directive';
import {FactoryActionBoxController} from './action/factory-action-box.controller';
import {FactoryActionBox} from './action/factory-action-box.directive';
import {FactoryActionDialogEditController} from './action/factory-action-edit.controller';
import {FactoryCommandController} from './command/factory-command.controller';
import {FactoryCommand} from './command/factory-command.directive';
import {FactoryCommandDialogEditController} from './command/factory-command-edit.controller';
import {CreateFactoryGitController} from './git/create-factory-git.controller';
import {CreateFactoryGit} from './git/create-factory-git.directive';
export class CreateFactoryConfig {
constructor(register: che.IRegisterService) {
register.controller('CreateFactoryCtrl', CreateFactoryCtrl);
register.controller('FactoryFromWorkspaceCtrl', FactoryFromWorkspaceCtrl);
register.directive('cdvyFactoryFromWorkspace', FactoryFromWorkspace);
register.controller('FactoryFromFileCtrl', FactoryFromFileCtrl);
register.directive('cdvyFactoryFromFile', FactoryFromFile);
register.controller('FactoryFromTemplateCtrl', FactoryFromTemplateCtrl);
register.directive('cdvyFactoryFromTemplate', FactoryFromTemplate);
register.controller('FactoryActionBoxController', FactoryActionBoxController);
register.directive('cdvyFactoryActionBox', FactoryActionBox);
register.controller('FactoryCommandController', FactoryCommandController);
register.directive('cdvyFactoryCommand', FactoryCommand);
register.controller('CreateFactoryGitController', CreateFactoryGitController);
register.directive('cdvyCreateFactoryGit', CreateFactoryGit);
register.controller('FactoryActionDialogEditController', FactoryActionDialogEditController);
register.controller('FactoryCommandDialogEditController', FactoryCommandDialogEditController);
// config routes
register.app.config(($routeProvider: any) => {
$routeProvider.accessWhen('/factories/create-factory', {
title: 'New Factory',
templateUrl: 'app/factories/create-factory/create-factory.html',
controller: 'CreateFactoryCtrl',
controllerAs: 'createFactoryCtrl'
});
});
}
}

View File

@ -0,0 +1,164 @@
/*
* Copyright (c) 2015-2017 Codenvy, S.A.
* 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:
* Codenvy, S.A. - initial API and implementation
*/
'use strict';
import {CheAPI} from '../../../components/api/che-api.factory';
import {CheNotification} from '../../../components/notification/che-notification.factory';
/**
* Controller for a create factory.
* @author Oleksii Orel
* @author Florent Benoit
*/
export class CreateFactoryCtrl {
private $location: ng.ILocationService;
private $log: ng.ILogService;
private cheAPI: CheAPI;
private cheNotification: CheNotification;
private lodash: _.LoDashStatic;
private $filter: ng.IFilterService;
private $document: ng.IDocumentService;
private isLoading: boolean;
private isImporting: boolean;
private stackRecipeMode: string;
private factoryContent: any;
private factoryObject: any;
private form: any;
private name: string;
private factoryId: string;
private factoryLink: string;
private factoryBadgeUrl: string;
private markdown: string;
/**
* Default constructor that is using resource injection
* @ngInject for Dependency injection
*/
constructor($location: ng.ILocationService, cheAPI: CheAPI, $log: ng.ILogService, cheNotification: CheNotification, $scope: ng.IScope,
$filter: ng.IFilterService, lodash: _.LoDashStatic, $document: ng.IDocumentService) {
this.$location = $location;
this.cheAPI = cheAPI;
this.$log = $log;
this.cheNotification = cheNotification;
this.$filter = $filter;
this.lodash = lodash;
this.$document = $document;
this.isLoading = false;
this.isImporting = false;
this.stackRecipeMode = 'current-recipe';
this.factoryContent = null;
$scope.$watch('createFactoryCtrl.factoryObject', () => {
this.factoryContent = this.$filter('json')(angular.fromJson(this.factoryObject));
}, true);
$scope.$watch('createFactoryCtrl.gitLocation', (newValue: string) => {
// update underlying model
// updating first project item
if (!this.factoryObject) {
let templateName = 'git';
let promise = this.cheAPI.getFactoryTemplate().fetchFactoryTemplate(templateName);
promise.then(() => {
let factoryContent = this.cheAPI.getFactoryTemplate().getFactoryTemplate(templateName);
this.factoryObject = angular.fromJson(factoryContent);
this.updateGitProjectLocation(newValue);
});
} else {
this.updateGitProjectLocation(newValue);
}
}, true);
}
/**
* Clear factory content
*/
clearFactoryContent(): void {
this.factoryContent = null;
}
setForm(form: any): void {
this.form = form;
}
isFormInvalid(): boolean {
return this.form ? this.form.$invalid: false;
}
/**
* Update the source project location for git
* @param location the new location
*/
updateGitProjectLocation(location: string): void {
let project = this.factoryObject.workspace.projects[0];
project.source.type = 'git';
project.source.location = location;
}
/**
* Create a new factory by factory content
* @param factoryContent
*/
createFactoryByContent(factoryContent: any): void {
if (!factoryContent) {
return;
}
// try to set factory name
try {
let factoryObject = angular.fromJson(factoryContent);
factoryObject.name = this.name;
factoryContent = angular.toJson(factoryObject);
} catch (e) {
this.$log.error(e);
}
this.isImporting = true;
let promise = this.cheAPI.getFactory().createFactoryByContent(factoryContent);
promise.then((factory: che.IFactory) => {
this.isImporting = false;
this.lodash.find(factory.links, (link: any) => {
if (link.rel === 'accept' || link.rel === 'accept-named') {
this.factoryLink = link.href;
}
});
var parser = this.$document[0].createElement('a');
parser.href = this.factoryLink;
this.factoryId = factory.id;
this.factoryBadgeUrl = parser.protocol + '//' + parser.hostname + '/factory/resources/codenvy-contribute.svg';
this.markdown = '[![Contribute](' + this.factoryBadgeUrl + ')](' + this.factoryLink + ')';
}, (error: any) => {
this.isImporting = false;
this.cheNotification.showError(error.data.message ? error.data.message : 'Create factory failed.');
this.$log.error(error);
}).then(() => {
this.finishFlow();
});
}
/*
* Flow of creating a factory is finished, we can redirect to details of factory
*/
finishFlow(): void {
this.clearFactoryContent();
this.$location.path('/factory/' + this.factoryId);
}
}

View File

@ -0,0 +1,117 @@
<!--
CODENVY CONFIDENTIAL
__________________
[2015] - [2016] Codenvy, S.A.
All Rights Reserved.
NOTICE: All information contained herein is, and remains
the property of Codenvy S.A. and its suppliers,
if any. The intellectual and technical concepts contained
herein are proprietary to Codenvy S.A.
and its suppliers and may be covered by U.S. and Foreign Patents,
patents in process, and are protected by trade secret or copyright law.
Dissemination of this information or reproduction of this material
is strictly forbidden unless prior written permission is obtained
from Codenvy S.A..
-->
<che-toolbar che-title="New Factory From"></che-toolbar>
<md-content md-scroll-y flex md-theme="default">
<md-progress-linear md-mode="indeterminate" class="create-factory-progress"
ng-show="createFactoryCtrl.isLoading"></md-progress-linear>
<div class="create-factory" ng-hide="createFactoryCtrl.isLoading">
<!-- Name -->
<che-label-container che-label-name="Name">
<ng-form name="createFactoryForm">
<div layout="column" class="create-factory-input"
ng-init="createFactoryCtrl.setForm(createFactoryForm)">
<che-input-box che-form="createFactoryForm"
che-name="name"
che-place-holder="Name of the factory"
aria-label="Name of the factory"
ng-model="createFactoryCtrl.name"
ng-trim
ng-minlength="3"
ng-maxlength="20"
ng-pattern="/^[ A-Za-z0-9_\-\.]+$/">
<div ng-message="required">A name is required.</div>
<div ng-message="pattern">Factory name may contain digits, latin letters, spaces, _ , . , - and should start
only
with digits, latin letters or underscores
</div>
<div ng-message="minlength">The name has to be more than 3 characters long.</div>
<div ng-message="maxlength">The name has to be less than 20 characters long.</div>
</che-input-box>
</div>
</ng-form>
</che-label-container>
<!--Factory source-->
<che-label-container che-label-name="Source"
class="che-label-container-last">
<md-tabs md-dynamic-height="true" md-stretch-tabs="auto" md-center-tabs="false" md-selected="selectedIndex"
md-border-bottom="true"
class="factory-select-source-details">
<md-tab md-on-select="createFactoryCtrl.clearFactoryContent()">
<md-tab-label>
<md-icon md-font-icon="fa fa-server" class="che-tab-label-icon"></md-icon>
<span class="che-tab-label-title">Workspace</span>
</md-tab-label>
<md-tab-body>
<cdvy-factory-from-workspace cdvy-factory-content="createFactoryCtrl.factoryContent"
cdvy-is-loading="createFactoryCtrl.isLoading"
cdvy-is-importing="createFactoryCtrl.isImporting"></cdvy-factory-from-workspace>
</md-tab-body>
</md-tab>
<md-tab md-on-select="createFactoryCtrl.clearFactoryContent()">
<md-tab-label>
<md-icon md-font-icon="fa-git" class="fa che-tab-label-icon"></md-icon>
<span class="che-tab-label-title">Git</span>
</md-tab-label>
<md-tab-body>
<div layout="row" layout-align="start center" class="create-factory-git-content">
<cdvy-create-factory-git cdvy-git-location="createFactoryCtrl.gitLocation" layout-fill
layout-align="start start"></cdvy-create-factory-git>
</div>
</md-tab-body>
</md-tab>
<md-tab md-on-select="createFactoryCtrl.clearFactoryContent()">
<md-tab-label>
<md-icon md-font-icon="fa fa-download" class="che-tab-label-icon"></md-icon>
<span class="che-tab-label-title">Config</span>
</md-tab-label>
<md-tab-body>
<div layout-fill layout="row" layout-align="start center">
<cdvy-factory-from-file cdvy-factory-content="createFactoryCtrl.factoryContent"
cdvy-is-importing="createFactoryCtrl.isImporting" layout="column"
flex></cdvy-factory-from-file>
</div>
</md-tab-body>
</md-tab>
<md-tab md-on-select="createFactoryCtrl.clearFactoryContent(); templateSelected = true;" md-on-deselect="templateSelected = false;">
<md-tab-label>
<md-icon md-font-icon="fa fa-magic" class="che-tab-label-icon"></md-icon>
<span class="che-tab-label-title">Template</span>
</md-tab-label>
<md-tab-body>
<div layout-fill layout="row" layout-align="start center" ng-if="templateSelected">
<cdvy-factory-from-template cdvy-factory-content="createFactoryCtrl.factoryContent"
cdvy-is-importing="createFactoryCtrl.isImporting" layout="column"
flex></cdvy-factory-from-template>
</div>
</md-tab-body>
</md-tab>
</md-tabs>
</che-label-container>
<!--button 'Create'-->
<che-button-primary id="create-factory-next-button"
che-button-title="Create"
ng-click="createFactoryCtrl.createFactoryByContent(createFactoryCtrl.factoryContent)"
ng-disabled="createFactoryCtrl.isFormInvalid() || !createFactoryCtrl.factoryContent || createFactoryCtrl.isImporting || createFactoryCtrl.isLoading"></che-button-primary>
</div>
</md-content>

View File

@ -0,0 +1,94 @@
md-content .create-factory
overflow auto
min-height calc(100vh - 152px)
background-color $white-color !important
padding 0 14px
button
margin-left 0
margin-right 0
margin-bottom 16px
.che-label-container-content .create-factory-input
margin -6px 0
md-progress-linear.create-factory-progress
z-index 1
height 10px
position absolute
.create-factory .factory-configuration-panel p
font-weight bold
margin-bottom 20px
color $red-lipstick-color
.create-factory md-tabs-content-wrapper
display inline
che-button-primary#create-factory-next-button button
margin 10px 0 100px
font-size 1.2em
width 100% !important
.create-factory-share-header-widget
border-top 1px solid $primary-color
box-shadow-simple()
padding-left 24px
padding-right 24px
padding-bottom 0px
min-height 80px !important
max-height 80px !important
background-color $background-color
z-index 4
.create-factory-share-header-widget-badge
margin-left 20px
margin-right 20px
.create-factory-share-header-widget-markdown
text-align center
height 18px
font-size 0.9em
margin-right 15px
padding-left 10px
padding-right 10px
border 1px solid $label-info-color
border-radius 2px
.create-factory-share-header-widget-clipboard
font-size 1.3em
.create-factory-share-header-widget-icon
font-size 3em
color $default-dark-color
margin-right 20px
md-card.create-factory-factory-information-card
margin 24px
background-color $white-color
font-size 1.1em
font-family sans-serif
md-tabs.factory-select-source-details
margin -15px 0
md-tabs.factory-select-source-details:not(.md-no-tab-content):not(.md-dynamic-height)
min-height 448px
md-tabs.factory-select-source-details md-tab-content > div:first-child
height 100% !important
.create-factory-share-header-small-button
margin-right 15px
padding 0 15px
font-size 9pt
cursor pointer
height 18px
background-color $primary-color
color $white-color
border-radius 2px
.create-factory-share-header-small-button:hover
color $white-color
text-decoration none

View File

@ -0,0 +1,23 @@
/*
* Copyright (c) 2015-2017 Codenvy, S.A.
* 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:
* Codenvy, S.A. - initial API and implementation
*/
'use strict';
/**
* This class is handling the controller for the git part of Factory
* @ngdoc controller
* @name factory.directive:CreateFactoryGitController
* @author Florent Benoit
*/
export class CreateFactoryGitController {
}

View File

@ -0,0 +1,47 @@
/*
* Copyright (c) 2015-2017 Codenvy, S.A.
* 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:
* Codenvy, S.A. - initial API and implementation
*/
'use strict';
/**
* Defines a directive for creating factory from git.
* @author Florent Benoit
*/
export class CreateFactoryGit {
private restrict: string;
private templateUrl: string;
private controller: string;
private controllerAs: string;
private bindToController: boolean;
private scope: {
[propName: string]: string;
};
/**
* Default constructor that is using resource
* @ngInject for Dependency injection
*/
constructor() {
this.controller = 'CreateFactoryGitController';
this.controllerAs = 'createFactoryGitCtrl';
this.bindToController = true;
this.restrict = 'E';
this.templateUrl = 'app/factories/create-factory/git/create-factory-git.html';
// scope values
this.scope = {
location: '=cdvyGitLocation'
};
}
}

View File

@ -0,0 +1,28 @@
<!--
Copyright (c) 2015 Codenvy, S.A.
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:
Codenvy, S.A. - initial API and implementation
-->
<che-label-container che-label-name="Git URL">
<form name="createFactoryGitForm">
<div class="create-factory-git-input">
<che-input-box che-form="createFactoryGitForm"
che-name="remoteGitURL"
che-place-holder="Repository URL"
che-width="auto"
ng-model="createFactoryGitCtrl.location"
git-url
required>
<div ng-message="gitUrl">Invalid Git URL</div>
<div ng-message="required">A repository URL is required.</div>
</che-input-box>
</div>
</form>
</che-label-container>

View File

@ -0,0 +1,2 @@
.create-factory-git-input
margin -6px 0

View File

@ -0,0 +1,75 @@
/*
* Copyright (c) 2015-2017 Codenvy, S.A.
* 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:
* Codenvy, S.A. - initial API and implementation
*/
'use strict';
import {CheAPI} from '../../../../components/api/che-api.factory';
import {CheNotification} from '../../../../components/notification/che-notification.factory';
/**
* Controller for creating factory from a template.
* @author Oleksii Orel
*/
export class FactoryFromTemplateCtrl {
private $filter: ng.IFilterService;
private cheAPI: CheAPI;
private cheNotification: CheNotification;
private isImporting: boolean;
private factoryContent: any;
private editorOptions: any;
private templateName: string;
/**
* Default constructor that is using resource injection
* @ngInject for Dependency injection
*/
constructor($filter: ng.IFilterService, cheAPI: CheAPI, cheNotification: CheNotification, $timeout: ng.ITimeoutService) {
this.$filter = $filter;
this.cheAPI = cheAPI;
this.cheNotification = cheNotification;
this.isImporting = false;
this.factoryContent = null;
this.templateName = 'minimal';
this.getFactoryTemplate(this.templateName);
this.editorOptions = {
mode: 'application/json',
onLoad: (editor: any) => {
$timeout(() => {
editor.refresh();
}, 1000);
}
};
}
// gets factory template.
getFactoryTemplate(templateName: string) {
let factoryContent = this.cheAPI.getFactoryTemplate().getFactoryTemplate(templateName);
if (factoryContent) {
this.factoryContent = this.$filter('json')(factoryContent, 2);
return;
}
this.isImporting = true;
// fetch it:
let promise = this.cheAPI.getFactoryTemplate().fetchFactoryTemplate(templateName);
promise.then((factoryContent: any) => {
this.isImporting = false;
this.factoryContent = this.$filter('json')(factoryContent, 2);
}, (error: any) => {
this.isImporting = false;
this.cheNotification.showError(error.data.message ? error.data.message : 'Fail to get factory template.');
});
}
}

View File

@ -0,0 +1,52 @@
/*
* Copyright (c) 2015-2017 Codenvy, S.A.
* 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:
* Codenvy, S.A. - initial API and implementation
*/
'use strict';
/**
* Defines a directive for displaying factory from template widget.
* @author Oleksii Orel
*/
export class FactoryFromTemplate {
private restrict: string;
private templateUrl: string;
private controller: string;
private controllerAs: string;
private bindToController: boolean;
private replace: boolean;
private scope: {
[propName: string]: string;
};
/**
* Default constructor that is using resource
* @ngInject for Dependency injection
*/
constructor() {
this.restrict = 'E';
this.templateUrl = 'app/factories/create-factory/template-tab/factory-from-template.html';
this.replace = false;
this.controller = 'FactoryFromTemplateCtrl';
this.controllerAs = 'factoryFromTemplateCtrl';
this.bindToController = true;
// scope values
this.scope = {
factoryContent: '=cdvyFactoryContent',
isImporting: '=cdvyIsImporting'
};
}
}

View File

@ -0,0 +1,38 @@
<!--
CODENVY CONFIDENTIAL
__________________
[2015] - [2016] Codenvy, S.A.
All Rights Reserved.
NOTICE: All information contained herein is, and remains
the property of Codenvy S.A. and its suppliers,
if any. The intellectual and technical concepts contained
herein are proprietary to Codenvy S.A.
and its suppliers and may be covered by U.S. and Foreign Patents,
patents in process, and are protected by trade secret or copyright law.
Dissemination of this information or reproduction of this material
is strictly forbidden unless prior written permission is obtained
from Codenvy S.A..
-->
<div class="factory-from-template" layout="row" flex layout-align="start center">
<che-label-container che-label-name="Select Template">
<div layout="column">
<che-toggle ng-model="factoryFromTemplateCtrl.templateName">
<che-toggle-button ng-click="factoryFromTemplateCtrl.getFactoryTemplate(factoryFromTemplateCtrl.templateName)"
che-font-icon="material-design icon-ic_unfold_less_24px"
che-title="minimal"></che-toggle-button>
<che-toggle-button ng-click="factoryFromTemplateCtrl.getFactoryTemplate(factoryFromTemplateCtrl.templateName)"
che-font-icon="material-design icon-ic_unfold_more_24px"
che-title="complete"></che-toggle-button>
</che-toggle>
<textarea ui-codemirror="factoryFromTemplateCtrl.editorOptions"
ng-model="factoryFromTemplateCtrl.factoryContent"
aria-label="Factory configuration editor">
</textarea>
</div>
</che-label-container>
</div>

View File

@ -0,0 +1,6 @@
.factory-from-template
min-height 80px
.factory-from-template .CodeMirror
border 1px solid $list-separator-color
min-height 500px

View File

@ -0,0 +1,121 @@
/*
* Copyright (c) 2015-2017 Codenvy, S.A.
* 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:
* Codenvy, S.A. - initial API and implementation
*/
'use strict';
import {CheAPI} from '../../../../components/api/che-api.factory';
import {CheNotification} from '../../../../components/notification/che-notification.factory';
/**
* Controller for creating factory from a workspace.
* @author Oleksii Orel
* @author Michail Kuznyetsov
*/
export class FactoryFromWorkspaceCtrl {
private $filter: ng.IFilterService;
private cheAPI: CheAPI;
private cheNotification: CheNotification;
private workspaces: Array<che.IWorkspace>;
private workspacesById: Map<string, che.IWorkspace>;
private filtersWorkspaceSelected: any;
private workspaceFilter: any;
private isLoading: boolean;
private isImporting: boolean;
private factoryContent: any;
/**
* Default constructor that is using resource injection
* @ngInject for Dependency injection
*/
constructor($filter: ng.IFilterService, cheAPI: CheAPI, cheNotification: CheNotification) {
this.$filter = $filter;
this.cheAPI = cheAPI;
this.cheNotification = cheNotification;
this.workspaces = cheAPI.getWorkspace().getWorkspaces();
this.workspacesById = cheAPI.getWorkspace().getWorkspacesById();
this.filtersWorkspaceSelected = {};
this.workspaceFilter = {config: {name: ''}};
this.isLoading = true;
// fetch workspaces when initializing
let promise = cheAPI.getWorkspace().fetchWorkspaces();
promise.then(() => {
this.isLoading = false;
this.updateData();
}, (error: any) => {
this.isLoading = false;
if (error.status === 304) {
this.updateData();
}
});
}
updateData(): void {
this.setAllFiltersWorkspaces(true);
}
/**
* Get factory content from workspace
* @param workspace is selected workspace
*/
getFactoryContentFromWorkspace(workspace: che.IWorkspace) {
let factoryContent = this.cheAPI.getFactory().getFactoryContentFromWorkspace(workspace);
if (factoryContent) {
this.factoryContent = this.$filter('json')(factoryContent, 2);
return;
}
this.isImporting = true;
let promise = this.cheAPI.getFactory().fetchFactoryContentFromWorkspace(workspace);
promise.then((factoryContent: any) => {
this.isImporting = false;
this.factoryContent = this.$filter('json')(factoryContent, 2);
}, (error: any) => {
let message = (error.data && error.data.message) ? error.data.message : 'Get factory configuration failed.'
if (error.status === 400) {
message = 'Factory can\'t be created. The selected workspace has no projects defined. Project sources must be available from an external storage.';
}
this.isImporting = false;
this.factoryContent = null;
this.cheNotification.showError(message);
});
}
/**
* Set all workspaces in the filters of workspaces
* @param isChecked is setting value
*/
setAllFiltersWorkspaces(isChecked: boolean) {
this.workspaces.forEach((workspace: che.IWorkspace) => {
this.filtersWorkspaceSelected[workspace.id] = isChecked;
});
}
/**
* Get the workspace name by ID
* @param workspaceId
* @returns {String} workspace name
*/
getWorkspaceName(workspaceId: string) {
let workspace = this.workspacesById.get(workspaceId);
if (workspace && workspace.config.name) {
return workspace.config.name;
}
return '';
}
}

View File

@ -0,0 +1,42 @@
/*
* Copyright (c) 2015-2017 Codenvy, S.A.
* 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:
* Codenvy, S.A. - initial API and implementation
*/
'use strict';
/**
* Defines a directive for configuring factory form workspace.
* @author Oleksii Orel
*/
export class FactoryFromWorkspace {
/**
* Default constructor that is using resource
* @ngInject for Dependency injection
*/
constructor() {
this.restrict = 'E';
this.templateUrl = 'app/factories/create-factory/workspaces-tab/factory-from-workspace.html';
this.replace = false;
this.controller = 'FactoryFromWorkspaceCtrl';
this.controllerAs = 'factoryFromWorkspaceCtrl';
this.bindToController = true;
// scope values
this.scope = {
isLoading: '=cdvyIsLoading',
isImporting: '=cdvyIsImporting',
factoryContent: '=cdvyFactoryContent'
};
}
}

View File

@ -0,0 +1,50 @@
<!--
CODENVY CONFIDENTIAL
__________________
[2015] - [2016] Codenvy, S.A.
All Rights Reserved.
NOTICE: All information contained herein is, and remains
the property of Codenvy S.A. and its suppliers,
if any. The intellectual and technical concepts contained
herein are proprietary to Codenvy S.A.
and its suppliers and may be covered by U.S. and Foreign Patents,
patents in process, and are protected by trade secret or copyright law.
Dissemination of this information or reproduction of this material
is strictly forbidden unless prior written permission is obtained
from Codenvy S.A..
-->
<md-content class="factory-from-workspace" flex>
<div class="factory-from-workspace-search">
<che-search che-placeholder="Search for workspaces"
ng-model="factoryFromWorkspaceCtrl.workspaceFilter.config.name"></che-search>
</div>
<span class="projects-list-workspace-name">{{factoryFromWorkspaceCtrl.getWorkspaceName(workspaceId)}}</span>
<span ng-show="(workspaces | filter:factoryFromWorkspaceCtrl.workspaceFilter).length == 0 && workspaces.length > 0"
class="workspace-list-empty">No workspaces found</span>
</che-list-title>
<che-list>
<che-list-item ng-show="(factoryFromWorkspaceCtrl.workspaces | filter:factoryFromWorkspaceCtrl.workspaceFilter).length > 0"
ng-repeat="workspace in factoryFromWorkspaceCtrl.workspaces | filter:factoryFromWorkspaceCtrl.workspaceFilter"
flex-gt-sm="100" flex="33" ng-mouseover="hover=true" ng-mouseout="hover=false">
<div layout-gt-sm="row" flex="100" layout-align="start center" class="project-list-row"
ng-click="factoryFromWorkspaceCtrl.getFactoryContentFromWorkspace(workspace)">
<div class="workspace-list-static-icon" layout-align="center center">
<div class="workspace-icon">
<md-icon md-font-icon="fa fa-server"></md-icon>
</div>
</div>
<div flex layout="column"
layout-align-gt="start center"
layout-align-sm="center center">
<div class="project-name">
<a class="codenvy-hover">{{workspace.config.name}}</a>
</div>
</div>
</div>
</che-list-item>
</che-list>
</md-content>

View File

@ -0,0 +1,88 @@
.factory-from-workspace-search
margin-top 20px
.factory-from-workspace che-search
color $label-primary-color
.factory-from-workspace che-search .search-component
border-bottom 1px solid $disabled-color
.factory-from-workspace che-search .search-input
color $label-primary-color
.factory-from-workspace che-search md-icon
font-size 14px
.factory-from-workspace che-search .search-input::-webkit-input-placeholder
color $label-primary-color
font-style italic
font-size 14px
opacity 0.5
.factory-from-workspace che-search .search-input:-moz-placeholder
/* Mozilla Firefox 4 to 18 */
color $label-primary-color
font-style italic
font-size 14px
opacity 0.5
.factory-from-workspace che-search .search-input::-moz-placeholder
/* Mozilla Firefox 19+ */
color $label-primary-color
font-style italic
font-size 14px
opacity 0.5
.factory-from-project-workspace-filter .workspace-selector md-checkbox:after
che-separator()
margin-top 17px
margin-left 10px
position relative
.factory-from-project-workspace-filter .workspace-selector:last-child md-checkbox:after
display none
.factory-from-project-workspace-filter .greyed .md-icon
background-color $disabled-color !important
.factory-from-workspace .workspace-list-static-icon
flex 0 0 86px
text-align center
.factory-from-workspace .workspace-list-static-icon project-type-icon
margin 0 24px
padding 10px 0
width 36px
height 36px
line-height 36px
font-size 36px
color $label-secondary-color
.factory-from-workspace md-icon
width auto
height auto
font-size 24px
line-height 24px
color $label-info-color
.factory-from-workspace .workspace-icon,
.factory-from-workspace .workspace-icon:hover,
.factory-from-workspace .workspace-icon md-icon,
.factory-from-workspace .workspace-icon md-icon:hover
color $label-secondary-color
.factory-from-workspace .project-list-row
outline none
margin-left 0
margin-right 0
cursor pointer
min-height 56px
.factory-from-workspace .project-name
font-size 16px
line-height 19px
margin-bottom 3px
color $label-primary-color
.factory-from-workspace .project-list-row:focus
background-color $focus-on-list-color

View File

@ -0,0 +1,63 @@
/*
* Copyright (c) 2015-2017 Codenvy, S.A.
* 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:
* Codenvy, S.A. - initial API and implementation
*/
'use strict';
import {FactoryDetailsConfig} from './factory-details/factory-details-config';
import {CreateFactoryConfig} from './create-factory/create-factory-config';
import {LastFactoriesConfig} from './last-factories/last-factories-config';
import {ListFactoriesController} from './list-factories/list-factories.controller';
import {FactoryItemController} from './list-factories/factory-item/factory-item.controller';
import {CheFactoryItem} from './list-factories/factory-item/factory-item.directive';
import {LoadFactoryController} from './load-factory/load-factory.controller';
import {LoadFactoryService} from './load-factory/load-factory.service';
export class FactoryConfig {
constructor(register: che.IRegisterService) {
register.controller('ListFactoriesController', ListFactoriesController);
register.controller('FactoryItemController', FactoryItemController);
register.directive('cdvyFactoryItem', CheFactoryItem);
register.controller('LoadFactoryController', LoadFactoryController);
register.service('loadFactoryService', LoadFactoryService);
// config routes
register.app.config(function ($routeProvider) {
$routeProvider.accessWhen('/factories', {
title: 'Factories',
templateUrl: 'app/factories/list-factories/list-factories.html',
controller: 'ListFactoriesController',
controllerAs: 'listFactoriesCtrl'
})
.accessWhen('/load-factory', {
title: 'Load Factory',
templateUrl: 'app/factories/load-factory/load-factory.html',
controller: 'LoadFactoryController',
controllerAs: 'loadFactoryController'
})
.accessWhen('/load-factory/:id', {
title: 'Load Factory',
templateUrl: 'app/factories/load-factory/load-factory.html',
controller: 'LoadFactoryController',
controllerAs: 'loadFactoryController'
});
});
// config files
new FactoryDetailsConfig(register);
new CreateFactoryConfig(register);
new LastFactoriesConfig(register);
}
}

View File

@ -0,0 +1,39 @@
/*
* Copyright (c) 2015-2017 Codenvy, S.A.
* 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:
* Codenvy, S.A. - initial API and implementation
*/
'use strict';
import {FactoryDetailsController} from '../factory-details/factory-details.controller';
import {InformationTabConfig} from './information-tab/information-tab-config';
export class FactoryDetailsConfig {
constructor(register: che.IRegisterService) {
register.controller('FactoryDetailsController', FactoryDetailsController);
// config routes
register.app.config(($routeProvider: any) => {
let locationProvider = {
title: 'Factory',
templateUrl: 'app/factories/factory-details/factory-details.html',
controller: 'FactoryDetailsController',
controllerAs: 'factoryDetailsController'
};
$routeProvider.accessWhen('/factory/:id', locationProvider)
.accessWhen('/factory/:id/:tabName', locationProvider);
});
// config files
new InformationTabConfig(register);
}
}

View File

@ -0,0 +1,52 @@
/*
* Copyright (c) 2015-2017 Codenvy, S.A.
* 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:
* Codenvy, S.A. - initial API and implementation
*/
'use strict';
import {CheNotification} from '../../../components/notification/che-notification.factory';
import {CheFactory} from '../../../components/api/che-factory.factory';
/**
* Controller for a factory details.
* @author Florent Benoit
*/
export class FactoryDetailsController {
private cheFactory: CheFactory;
private factory: che.IFactory;
/**
* Default constructor that is using resource injection
* @ngInject for Dependency injection
*/
constructor($route: ng.route.IRouteService, cheFactory: CheFactory, cheNotification: CheNotification) {
'ngInject';
this.cheFactory = cheFactory;
let factoryId = $route.current.params.id;
this.factory = this.cheFactory.getFactoryById(factoryId);
cheFactory.fetchFactoryById(factoryId).then((factory: che.IFactory) => {
this.factory = factory;
}, (error: any) => {
cheNotification.showError(error.data.message ? error.data.message : 'Get factory failed.');
});
}
/**
* Returns the factory url based on id.
* @returns {link.href|*} link value
*/
getFactoryIdUrl(): string {
if (!this.factory) {
return null;
}
return this.cheFactory.getFactoryIdUrl(this.factory);
}
}

View File

@ -0,0 +1,31 @@
<!--
CODENVY CONFIDENTIAL
__________________
[2015] - [2016] Codenvy, S.A.
All Rights Reserved.
NOTICE: All information contained herein is, and remains
the property of Codenvy S.A. and its suppliers,
if any. The intellectual and technical concepts contained
herein are proprietary to Codenvy S.A.
and its suppliers and may be covered by U.S. and Foreign Patents,
patents in process, and are protected by trade secret or copyright law.
Dissemination of this information or reproduction of this material
is strictly forbidden unless prior written permission is obtained
from Codenvy S.A..
-->
<che-toolbar
che-title="{{factoryDetailsController.factory.name ? factoryDetailsController.factory.name : factoryDetailsController.factory.id}}"
che-title-icons-controller="factoryDetailsController"
che-button-name="Open"
che-button-href="{{factoryDetailsController.getFactoryIdUrl()}}"
che-button-href-target="_blank"
che-breadcrumb-title="All factories"
che-breadcrumb-href="#/factories">
</che-toolbar>
<md-content md-scroll-y flex class="factory-details factory-details-content">
<cdvy-factory-information cdvy-factory="factoryDetailsController.factory"></cdvy-factory-information>
</md-content>

View File

@ -0,0 +1,20 @@
md-content.factory-details
overflow auto
min-height calc(100vh - 152px)
md-progress-linear.factory-details-progress
z-index 1
height 10px
position absolute
.factory-details-content
background-color $white-color !important
padding 0 14px
button
margin-left 0
margin-right 0
margin-bottom 16px
.factory-delete-label label
color $che-delete-label-color

View File

@ -0,0 +1,277 @@
/*
* Copyright (c) 2015-2017 Codenvy, S.A.
* 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:
* Codenvy, S.A. - initial API and implementation
*/
'use strict';
import {CheAPI} from '../../../../../components/api/che-api.factory';
import {CheNotification} from '../../../../../components/notification/che-notification.factory';
/**
* Controller for a factory information.
* @author Oleksii Orel
*/
export class FactoryInformationController {
private confirmDialogService: any;
private cheAPI: CheAPI;
private cheNotification: CheNotification;
private $location: ng.ILocationService;
private $log: ng.ILogService;
private $timeout: ng.ITimeoutService;
private lodash: _.LoDashStatic;
private $filter: ng.IFilterService;
private timeoutPromise: ng.IPromise<any>;
private editorLoadedPromise: ng.IPromise<any>;
private editorOptions: any;
private factoryInformationForm: any;
private stackRecipeMode: string;
private factory: che.IFactory;
private copyOriginFactory: che.IFactory;
private factoryContent: any;
private workspaceImportedRecipe: any;
private environmentName: string;
private workspaceName: string;
private stackId: string;
private workspaceConfig: any;
/**
* Default constructor that is using resource injection
* @ngInject for Dependency injection
*/
constructor($scope: ng.IScope, cheAPI: CheAPI, cheNotification: CheNotification, $location: ng.ILocationService, $log: ng.ILogService,
$timeout: ng.ITimeoutService, lodash: _.LoDashStatic, $filter: ng.IFilterService, $q: ng.IQService, confirmDialogService: any) {
this.cheAPI = cheAPI;
this.cheNotification = cheNotification;
this.$location = $location;
this.$log = $log;
this.$timeout = $timeout;
this.lodash = lodash;
this.$filter = $filter;
this.confirmDialogService = confirmDialogService;
this.timeoutPromise = null;
$scope.$on('$destroy', () => {
if (this.timeoutPromise) {
$timeout.cancel(this.timeoutPromise);
}
});
let editorLoadedDefer = $q.defer();
this.editorLoadedPromise = editorLoadedDefer.promise;
this.editorOptions = {
onLoad: ((instance: any) => {
editorLoadedDefer.resolve(instance);
})
};
this.stackRecipeMode = 'current-recipe';
this.updateData();
$scope.$watch(() => {
return this.factory;
}, () => {
this.updateData();
});
}
/**
* Update factory content data for editor
*/
updateData(): void {
if (!this.factory) {
return;
}
this.workspaceName = this.factory.workspace.name;
this.environmentName = this.factory.workspace.defaultEnv;
this.copyOriginFactory = angular.copy(this.factory);
if (this.copyOriginFactory.links) {
delete this.copyOriginFactory.links;
}
let factoryContent = this.$filter('json')(this.copyOriginFactory);
if (factoryContent !== this.factoryContent) {
if (!this.factoryContent) {
this.editorLoadedPromise.then((instance) => {
this.$timeout(() => {
instance.refresh();
}, 500);
});
}
this.factoryContent = factoryContent;
}
}
/**
* Returns object's attributes.
*
* @param targetObject object to process
* @returns {string[]}
*/
getObjectKeys(targetObject: any): Array<string> {
return Object.keys(targetObject);
}
/**
* Returns the factory's data changed state.
*
* @returns {boolean}
*/
isFactoryChanged(): boolean {
if (!this.copyOriginFactory) {
return false;
}
let testFactory = angular.copy(this.factory);
if (testFactory.links) {
delete testFactory.links;
}
return angular.equals(this.copyOriginFactory, testFactory) !== true;
}
/**
* Update factory data.
*/
updateFactory(): void {
this.factoryContent = this.$filter('json')(this.copyOriginFactory);
if (this.factoryInformationForm.$invalid || !this.isFactoryChanged()) {
return;
}
this.$timeout.cancel(this.timeoutPromise);
this.timeoutPromise = this.$timeout(() => {
this.doUpdateFactory(this.copyOriginFactory);
}, 500);
}
/**
* Returns the factory url based on id.
*
* @returns {link.href|*} link value
*/
getFactoryIdUrl(): string {
return this.cheAPI.getFactory().getFactoryIdUrl(this.factory);
}
/**
* Returns the factory url based on name.
*
* @returns {link.href|*} link value
*/
getFactoryNamedUrl(): string {
return this.cheAPI.getFactory().getFactoryNamedUrl(this.factory);
}
/**
* Callback to update factory
*/
doUpdateFactory(factory: che.IFactory): void {
let promise = this.cheAPI.getFactory().setFactory(factory);
promise.then((factory: che.IFactory) => {
this.factory = factory;
this.cheNotification.showInfo('Factory information successfully updated.');
}, (error: any) => {
this.cheNotification.showError(error.data.message ? error.data.message : 'Update factory failed.');
this.$log.log(error);
});
}
/**
* Handler for factory editor focus event.
*/
factoryEditorOnFocus(): void {
if (this.timeoutPromise) {
this.$timeout.cancel(this.timeoutPromise);
this.doUpdateFactory(this.copyOriginFactory);
}
}
/**
* Resets factory editor.
*/
factoryEditorReset(): void {
this.factoryContent = this.$filter('json')(this.copyOriginFactory, 2);
}
/**
* Updates factory's content.
*/
updateFactoryContent(): void {
let promise = this.cheAPI.getFactory().setFactoryContent(this.factory.id, this.factoryContent);
promise.then((factory: che.IFactory) => {
this.factory = factory;
this.cheNotification.showInfo('Factory information successfully updated.');
}, (error: any) => {
this.factoryContent = this.$filter('json')(this.copyOriginFactory, 2);
this.cheNotification.showError(error.data.message ? error.data.message : 'Update factory failed.');
this.$log.error(error);
});
}
/**
* Deletes factory with confirmation.
*/
deleteFactory(): void {
let content = 'Please confirm removal for the factory \'' + (this.factory.name ? this.factory.name : this.factory.id) + '\'.';
let promise = this.confirmDialogService.showConfirmDialog('Remove the factory', content, 'Delete');
promise.then(() => {
// remove it !
let promise = this.cheAPI.getFactory().deleteFactoryById(this.factory.id);
promise.then(() => {
this.$location.path('/factories');
}, (error: any) => {
this.cheNotification.showError(error.data.message ? error.data.message : 'Delete failed.');
this.$log.log(error);
});
});
}
/**
* Returns the recipe value of the environment.
*
* @returns {any}
*/
getRecipe(): string {
if (this.copyOriginFactory && this.copyOriginFactory.workspace) {
let environement = this.copyOriginFactory.workspace.environments[this.copyOriginFactory.workspace.defaultEnv];
return environement.recipe.location || environement.recipe.content;
}
return null;
}
/**
* Handles stack and workspace config changes.
*
* @param config workspace config
* @param stackId stack id
*/
onWorkspaceStackChanged(config: any, stackId: string): void {
this.stackId = stackId;
this.workspaceConfig = config;
}
/**
* Saves stacks changes in workspace config inside factory.
*/
saveStack(): void {
if (!this.copyOriginFactory) {
return;
}
this.copyOriginFactory.workspace.environments[this.factory.workspace.defaultEnv] = this.workspaceConfig.environments[this.workspaceConfig.defaultEnv];
this.updateFactory();
}
}

View File

@ -0,0 +1,50 @@
/*
* Copyright (c) 2015-2017 Codenvy, S.A.
* 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:
* Codenvy, S.A. - initial API and implementation
*/
'use strict';
/**
* Defines a directive for displaying factory-information widget.
* @author Oleksii Orel
*/
export class FactoryInformation {
private restrict: string;
private templateUrl: string;
private replace: boolean;
private controller: string;
private controllerAs: string;
private bindToController: boolean;
private scope: {
[propName: string]: string;
};
/**
* Default constructor that is using resource
* @ngInject for Dependency injection
*/
constructor() {
this.restrict = 'E';
this.templateUrl = 'app/factories/factory-details/information-tab/factory-information/factory-information.html';
this.replace = false;
this.controller = 'FactoryInformationController';
this.controllerAs = 'factoryInformationController';
this.bindToController = true;
// scope values
this.scope = {
factory: '=cdvyFactory'
};
}
}

View File

@ -0,0 +1,204 @@
<!--
CODENVY CONFIDENTIAL
__________________
[2015] - [2016] Codenvy, S.A.
All Rights Reserved.
NOTICE: All information contained herein is, and remains
the property of Codenvy S.A. and its suppliers,
if any. The intellectual and technical concepts contained
herein are proprietary to Codenvy S.A.
and its suppliers and may be covered by U.S. and Foreign Patents,
patents in process, and are protected by trade secret or copyright law.
Dissemination of this information or reproduction of this material
is strictly forbidden unless prior written permission is obtained
from Codenvy S.A..
-->
<div class="factory-information" ng-if="factoryInformationController.copyOriginFactory">
<!-- Name -->
<che-label-container che-label-name="Name">
<div layout="column" class="factory-information-input">
<ng-form name="factoryInformationForm">
<che-input che-form="factoryInformationForm"
ng-init="factoryInformationController.factoryInformationForm = factoryInformationForm"
che-name="name"
che-place-holder="Name of the factory"
aria-label="Name of the factory"
ng-model="factoryInformationController.copyOriginFactory.name"
ng-change="factoryInformationController.updateFactory()"
ng-trim
ng-minlength="3"
ng-maxlength="20"
ng-pattern="/^[ A-Za-z0-9_\-\.]+$/">
<div ng-message="required">A name is required.</div>
<div ng-message="pattern">Factory name may contain digits, latin letters, spaces, _ , . , - and should start
only
with digits, latin letters or underscores
</div>
<div ng-message="minlength">The name has to be more than 3 characters long.</div>
<div ng-message="maxlength">The name has to be less than 20 characters long.</div>
</che-input>
</ng-form>
</div>
</che-label-container>
<!-- URL -->
<che-label-container che-label-name="URL">
<che-text-info ng-show="factoryInformationController.getFactoryNamedUrl()"
che-text="factoryInformationController.getFactoryNamedUrl()"
che-href="factoryInformationController.getFactoryNamedUrl()"
che-copy-clipboard="true"
class="factory-information-panel-item"></che-text-info>
<che-text-info che-text="factoryInformationController.getFactoryIdUrl()"
che-href="factoryInformationController.getFactoryIdUrl()"
che-copy-clipboard="true"
class="factory-information-panel-item"></che-text-info>
</che-label-container>
<!-- Creation Date -->
<che-label-container che-label-name="Creation Date">
<che-text-info
che-text="factoryInformationController.factory.creator.created | amDateFormat:'Do MMMM YYYY'">
</che-text-info>
</che-label-container>
<!--Stack-->
<che-label-container che-label-name="Configure Stack"
che-label-description="Stacks are recipes or images used to define your environment runtime. Workspace environments are used to build and run your project.">
<md-radio-group ng-model="factoryInformationController.stackRecipeMode">
<md-radio-button value="current-recipe">Use current recipe</md-radio-button>
<div ng-if="factoryInformationController.stackRecipeMode === 'current-recipe'">
<div ng-if="factoryInformationController.getRecipe()">
{{factoryInformationController.getRecipe()}}
</div>
<div ng-if="!factoryInformationController.getRecipe()">
none
</div>
</div>
<md-radio-button value="from-stack">Configure recipe from a stack</md-radio-button>
</md-radio-group>
<div ng-if="factoryInformationController.stackRecipeMode === 'from-stack'">
<workspace-select-stack workspace-stack-on-change="factoryInformationController.onWorkspaceStackChanged(config, stackId)"
workspace-name="factoryInformationController.workspaceName"
environment-name="factoryInformationController.environmentName"
workspace-imported-recipe="factoryInformationController.workspaceImportedRecipe"></workspace-select-stack>
<che-button-primary che-button-title="Save" class="save-stack-button"
ng-click="factoryInformationController.saveStack()"></che-button-primary>
</div>
</che-label-container>
<che-label-container che-label-name="Workspace"
che-label-description="A workspace contains projects and runtime environments.">
<!--Workspace name-->
<che-label-container che-label-name="Name">
<ng-form name="factoryInformationForm">
<div class="factory-information-input">
<che-input che-form="factoryInformationForm"
che-name="name"
che-place-holder="Name of the workspace"
aria-label="Name of the workspace"
ng-model="factoryInformationController.copyOriginFactory.workspace.name"
ng-change="factoryInformationController.updateFactory()"
required
ng-minlength="3"
ng-maxlength="20"
ng-pattern="/^[A-Za-z0-9_\-\.]+$/">
<div ng-message="required">A name is required.</div>
<div ng-message="pattern">Workspace name may contain digits, latin letters, _ , . , - and should start
only with digits, latin
letters or underscores
</div>
<div ng-message="minlength">The name has to be more than 3 characters long.</div>
<div ng-message="maxlength">The name has to be less than 20 characters long.</div>
</che-input>
</div>
</ng-form>
</che-label-container>
<!--Workspace RAM-->
<che-label-container che-label-name="RAM">
<ng-form name="factoryInformationForm">
<div data-ng-repeat="(environmentKey, environmentValue) in factoryInformationController.copyOriginFactory.workspace.environments">
<span ng-if="factoryInformationController.getObjectKeys(factoryInformationController.copyOriginFactory.workspace.environments).length > 1"
class="workspace-environment-name">ENVIRONMENT: {{environmentKey}}</span>
<div>
<div data-ng-repeat="(machineKey, machineValue) in environmentValue.machines">
<div class="workspace-machine" ng-if="machineValue.attributes && machineValue.attributes.memoryLimitBytes">
<span ng-if="factoryInformationController.getObjectKeys(environmentValue.machines).length > 1">MACHINE: {{machineKey}}</span>
<che-workspace-ram-allocation-slider
ng-model="machineValue.attributes.memoryLimitBytes"
che-on-change="factoryInformationController.updateFactory()"></che-workspace-ram-allocation-slider>
</div>
</div>
</div>
</div>
</ng-form>
</che-label-container>
</che-label-container>
<!-- Configure commands -->
<che-label-container che-label-name="Configure Commands"
che-label-description="Commands are processes that are invoked by users from a dropdown in the IDE.">
<cdvy-factory-command cdvy-factory-object="factoryInformationController.copyOriginFactory"
cdvy-on-change="factoryInformationController.updateFactory()"></cdvy-factory-command>
</che-label-container>
<!-- Configure actions -->
<che-label-container che-label-name="Configure Actions"
che-label-description="Tell the IDE to perform additional actions after the workspace is loaded.">
<!-- almost no action is possible at this lifecycle
<cdvy-factory-action-box cdvy-lifecycle="onAppLoaded"
cdvy-action-title="Actions executed after IDE is loaded"
cdvy-callback-controller="factoryInformationController"
cdvy-factory-object="factoryInformationController.factoryObject"></cdvy-factory-action-box>
-->
<cdvy-factory-action-box cdvy-lifecycle="onProjectsLoaded"
cdvy-callback-controller="factoryInformationController"
cdvy-factory-object="factoryInformationController.copyOriginFactory"
cdvy-on-change="factoryInformationController.updateFactory()"></cdvy-factory-action-box>
</che-label-container>
<!-- Configuration -->
<che-label-container che-label-name="Configuration"
che-label-description="JSON definition of the factory."
ng-class="{ 'disabled-state': !factoryInformationController.factoryContent }">
<div class="factory-configuration-panel">
<md-content layout="column" layout-fill>
<div class="json-editor" ng-if="factoryInformationController.factoryContent !== null">
<textarea ui-codemirror="factoryInformationController.editorOptions"
ng-model="factoryInformationController.factoryContent"
aria-label="Factory configuration editor"
ng-focus="factoryInformationController.factoryEditorOnFocus()"></textarea>
</div>
</md-content>
<div layout="row" flex>
<div>
<a href="/docs/integration-guide/workspace-automation/index.html" target="_blank">Factory configuration docs</a>
</div>
<div layout="row" layout-align="end start" flex>
<div>
<che-button-default che-button-title="Reload"
ng-click="factoryInformationController.factoryEditorReset()"></che-button-default>
</div>
<div class="factory-information-update-button">
<che-button-primary che-button-title="Update"
ng-disabled="factoryInformationController.factoryContent === factoryInformationController.$filter('json')(factoryInformationController.copyOriginFactory, 2);"
ng-click="factoryInformationController.updateFactoryContent()"></che-button-primary>
</div>
</div>
</div>
</div>
</che-label-container>
<!-- Delete button -->
<che-label-container class="factory-delete-label"
che-label-name="Delete Factory"
che-label-description="This is irreversible.">
<che-button-danger che-button-title="Delete"
ng-click="factoryInformationController.deleteFactory()"></che-button-danger>
</che-label-container>
</div>

View File

@ -0,0 +1,40 @@
.factory-information-panel-item:not(:last-child)
margin-bottom 10px
.factory-information .che-label-container-content .factory-information-input
margin-top -6px
.factory-information .disabled-state
cursor not-allowed
background-color transparent
.factory-information
overflow auto
min-height calc(100vh - 152px)
background-color $white-color !important
padding 0 14px
button
margin 0 30px 0 0
.che-label-container-content .factory-information-input
margin-top -6px
.factory-information-update-button button
margin-right 0
.json-editor
font-size 12px
margin-bottom 20px
.workspace-environment-name + div
margin-top 8px
.workspace-machine
margin-left 45px
.che-ram-allocation-slider .slider-wrapper
margin-bottom -15px
.save-stack-button button
margin-top 20px

View File

@ -0,0 +1,23 @@
/*
* Copyright (c) 2015-2017 Codenvy, S.A.
* 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:
* Codenvy, S.A. - initial API and implementation
*/
'use strict';
import {FactoryInformationController} from '../information-tab/factory-information/factory-information.controller';
import {FactoryInformation} from '../information-tab/factory-information/factory-information.directive';
export class InformationTabConfig {
constructor(register: che.IRegisterService) {
register.controller('FactoryInformationController', FactoryInformationController);
register.directive('cdvyFactoryInformation', FactoryInformation);
}
}

View File

@ -0,0 +1,22 @@
/*
* Copyright (c) 2015-2017 Codenvy, S.A.
* 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:
* Codenvy, S.A. - initial API and implementation
*/
'use strict';
import {LastFactoriesController} from './last-factories.controller';
import {LastFactories} from './last-factories.directive';
export class LastFactoriesConfig {
constructor(register: che.IRegisterService) {
register.controller('LastFactoriesController', LastFactoriesController);
register.directive('cdvyLastFactories', LastFactories);
}
}

View File

@ -0,0 +1,67 @@
/*
* Copyright (c) 2015-2017 Codenvy, S.A.
* 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:
* Codenvy, S.A. - initial API and implementation
*/
'use strict';
import {CheFactory} from '../../../components/api/che-factory.factory';
/**
* @ngdoc controller
* @name factories.controller:LastFactoriesController
* @description This class is handling the controller of the last factories to display in the dashboard
* @author Oleksii Orel
*/
export class LastFactoriesController {
private cheFactory: CheFactory;
private factories: Array<che.IFactory>;
private factoriesOrderBy: string;
private maxItems: number;
private isLoading: boolean;
/**
* Default constructor
* @ngInject for Dependency injection
*/
constructor(cheFactory: CheFactory) {
this.cheFactory = cheFactory;
this.factories = this.cheFactory.getPageFactories();
// todo we should change to modificationDate after model's change
this.factoriesOrderBy = '-creator.created';
this.maxItems = 5;
// todo add OrderBy to condition in fetch API
let promise = this.cheFactory.fetchFactories(this.maxItems, 0);
this.isLoading = true;
promise.finally(() => {
this.isLoading = false;
this.updateFactories();
});
}
/**
* Update factories array
*/
updateFactories(): void {
this.factories = this.cheFactory.getPageFactories();
}
/**
* Returns the list of factories.
*
* @returns {Array<che.IFactory>}
*/
getFactories(): Array<che.IFactory> {
return this.factories;
}
}

View File

@ -0,0 +1,39 @@
/*
* Copyright (c) 2015-2017 Codenvy, S.A.
* 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:
* Codenvy, S.A. - initial API and implementation
*/
'use strict';
/**
* @ngdoc directive
* @name factories.directive:LastFactories
* @description This class is handling the directive of the listing last opened factories
* @author Oleksii Orel
*/
export class LastFactories {
private restrict: string;
private templateUrl: string;
private replace: boolean;
private controller: string;
private controllerAs: string;
private bindToController: boolean;
/**
* Default constructor that is using resource
* @ngInject for Dependency injection
*/
constructor() {
this.restrict = 'E';
this.templateUrl = 'app/factories/last-factories/last-factories.html';
this.replace = false;
this.controller = 'LastFactoriesController';
this.controllerAs = 'lastFactoriesController';
this.bindToController = true;
}
}

View File

@ -0,0 +1,40 @@
<!--
CODENVY CONFIDENTIAL
__________________
[2015] - [2016] Codenvy, S.A.
All Rights Reserved.
NOTICE: All information contained herein is, and remains
the property of Codenvy S.A. and its suppliers,
if any. The intellectual and technical concepts contained
herein are proprietary to Codenvy S.A.
and its suppliers and may be covered by U.S. and Foreign Patents,
patents in process, and are protected by trade secret or copyright law.
Dissemination of this information or reproduction of this material
is strictly forbidden unless prior written permission is obtained
from Codenvy S.A..
-->
<dashboard-panel panel-title="Recent Factories">
<md-progress-linear md-mode="indeterminate"
ng-show="lastFactoriesController.isLoading"></md-progress-linear>
<div class="dashboard-last-factories" ng-show="!lastFactoriesController.isLoading">
<div class="dashboard-add-button">
<che-button-primary che-button-title="Create Factory"
ng-href="#/factories/create-factory"></che-button-primary>
</div>
<span ng-show="lastFactoriesController.getFactories().length === 0" class="last-factories-empty-label">No factories found</span>
<che-list ng-show="lastFactoriesController.getFactories().length > 0">
<cdvy-factory-item layout="row"
ng-repeat="factory in lastFactoriesController.getFactories() | orderBy:lastFactoriesController.factoriesOrderBy | limitTo:lastFactoriesController.maxItems"
cdvy-factory="factory"
cdvy-is-selectable="false"/>
</che-list>
</div>
</dashboard-panel>

View File

@ -0,0 +1,3 @@
.last-factories-empty-label
margin-left 20px
color $label-info-color

View File

@ -0,0 +1,81 @@
/*
* Copyright (c) 2015-2017 Codenvy, S.A.
* 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:
* Codenvy, S.A. - initial API and implementation
*/
'use strict';
import {CheFactory} from '../../../../components/api/che-factory.factory';
import {CheEnvironmentRegistry} from '../../../../components/api/environment/che-environment-registry.factory';
/**
* Controller for a factory item.
* @author Oleksii Orel
*/
export class FactoryItemController {
private $location: ng.ILocationService;
private cheFactory: CheFactory;
private cheEnvironmentRegistry: CheEnvironmentRegistry;
private lodash: _.LoDashStatic;
private factory: che.IFactory;
/**
* Default constructor that is using resource injection
* @ngInject for Dependency injection
*/
constructor($location: ng.ILocationService, cheFactory: CheFactory, cheEnvironmentRegistry: CheEnvironmentRegistry, lodash: _.LoDashStatic) {
this.$location = $location;
this.cheFactory = cheFactory;
this.cheEnvironmentRegistry = cheEnvironmentRegistry;
this.lodash = lodash;
}
/**
* Returns the list of factory links.
*
* @returns {Array<any>}
*/
getFactoryLinks(): Array<any> {
return this.cheFactory.detectLinks(this.factory);
}
/**
* Redirect to factory details.
*/
redirectToFactoryDetails(): void {
this.$location.path('/factory/' + this.factory.id);
}
/**
* Returns display value of memory limit.
*
* @returns {string} display value of memory limit
*/
getMemoryLimit(): string {
if (!this.factory.workspace) {
return '-';
}
let defaultEnvName = this.factory.workspace.defaultEnv;
let environment = this.factory.workspace.environments[defaultEnvName];
let recipeType = environment.recipe.type;
let environmentManager = this.cheEnvironmentRegistry.getEnvironmentManager(recipeType);
let machines = environmentManager.getMachines(environment);
let limits = this.lodash.pluck(machines, 'attributes.memoryLimitBytes');
let total = 0;
limits.forEach((limit: number) => {
if (limit) {
total += limit / (1024 * 1024);
}
});
return (total > 0) ? total + ' MB' : '-';
}
}

View File

@ -0,0 +1,48 @@
/*
* Copyright (c) 2015-2017 Codenvy, S.A.
* 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:
* Codenvy, S.A. - initial API and implementation
*/
'use strict';
/**
* Defines a directive for factory item in list.
* @author Oleksii Orel
*/
export class CheFactoryItem {
restrict: string = 'E';
templateUrl: string = 'app/factories/list-factories/factory-item/factory-item.html';
replace = false;
controller: string = 'FactoryItemController';
controllerAs: string = 'factoryItemController';
bindToController: boolean = true;
// we require ngModel as we want to use it inside our directive
require: Array<string> = ['ngModel'];
scope: {
[propName: string]: string;
};
/**
* Default constructor.
*/
constructor() {
this.scope = {
factory: '=cdvyFactory',
isChecked: '=cdvyChecked',
isSelectable: '=cdvyIsSelectable',
isSelect: '=?ngModel',
onCheckboxClick: '&?cdvyOnCheckboxClick'
};
}
}

View File

@ -0,0 +1,59 @@
<!--
CODENVY CONFIDENTIAL
__________________
[2015] - [2016] Codenvy, S.A.
All Rights Reserved.
NOTICE: All information contained herein is, and remains
the property of Codenvy S.A. and its suppliers,
if any. The intellectual and technical concepts contained
herein are proprietary to Codenvy S.A.
and its suppliers and may be covered by U.S. and Foreign Patents,
patents in process, and are protected by trade secret or copyright law.
Dissemination of this information or reproduction of this material
is strictly forbidden unless prior written permission is obtained
from Codenvy S.A..
-->
<che-list-item flex ng-mouseover="hover=true" ng-mouseout="hover=false">
<div flex="100"
layout="row"
layout-align="start stretch"
class="che-list-item-row">
<div layout="row"
layout-align="start center"
class="che-checkbox-area"
ng-if="factoryItemController.isSelectable === true">
<che-list-item-checked ng-model="factoryItemController.isSelect"
che-aria-label-checkbox="Factory {{factoryItemController.factory.name}}"
ng-click="factoryItemController.onCheckboxClick()"></che-list-item-checked>
</div>
<div flex
layout-xs="column" layout-gt-xs="row"
layout-align-gt-xs="start center"
layout-align-xs="start start"
class="che-list-item-details">
<div flex-gt-xs="25"
class="che-list-item-name"
ng-click="factoryItemController.redirectToFactoryDetails();">
<span class="che-xs-header noselect" hide-gt-xs>Factory</span>
<span tooltip="Created: {{factoryItemController.factory.creator.created | amDateFormat:'dddd, MMMM Do, YYYY'}}"
class="che-hover">{{factoryItemController.factory.name ? factoryItemController.factory.name : factoryItemController.getFactoryLinks()[0]}}</span>
</div>
<div flex-gt-xs="60" ng-click="factoryItemController.redirectToFactoryDetails();">
<span class="che-xs-header noselect" hide-gt-xs>RAM</span>
<span class="factory-consumed-value">{{factoryItemController.getMemoryLimit()}}</span>
</div>
<div flex-gt-xs="15">
<span class="che-xs-header noselect" hide-gt-xs>Actions</span>
<span class="che-list-actions">
<a tooltip="Open in IDE" ng-href="#/load-factory/{{factoryItemController.factory.id}}">
<span class="fa fa-chevron-circle-right factory-action"></span>
</a>
</span>
</div>
</div>
</div>
</che-list-item>

View File

@ -0,0 +1,8 @@
.che-list-item-row .factory-consumed-value
line-height initial
font-size 12px
font-weight bold
color $che-medium-blue-color
.che-list-actions .factory-action
min-width 16px

View File

@ -0,0 +1,279 @@
/*
* Copyright (c) 2015-2017 Codenvy, S.A.
* 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:
* Codenvy, S.A. - initial API and implementation
*/
'use strict';
import {ConfirmDialogService} from '../../../components/service/confirm-dialog/confirm-dialog.service';
import {CheAPI} from "../../../components/api/che-api.factory";
import {CheNotification} from "../../../components/notification/che-notification.factory";
/**
* Controller for the factories.
* @author Florent Benoit
* @author Oleksii Orel
*/
export class ListFactoriesController {
private confirmDialogService: ConfirmDialogService;
private cheAPI: CheAPI;
private cheNotification: CheNotification;
private $q: ng.IQService;
private $log: ng.ILogService;
private maxItems: number;
private skipCount: number;
private factoriesOrderBy: string;
private factoriesFilter: any;
private factoriesSelectedStatus: any;
private isNoSelected: boolean;
private isAllSelected: boolean;
private isBulkChecked: boolean;
private isLoading: boolean;
private factories: any;
private pagesInfo: any;
/**
* Default constructor that is using resource injection
* @ngInject for Dependency injection
*/
constructor($q: ng.IQService, $log: ng.ILogService, cheAPI: CheAPI, cheNotification: CheNotification, $rootScope: che.IRootScopeService,
confirmDialogService: ConfirmDialogService) {
this.$q = $q;
this.$log = $log;
this.cheAPI = cheAPI;
this.cheNotification = cheNotification;
this.confirmDialogService = confirmDialogService;
this.maxItems = 15;
this.skipCount = 0;
this.factoriesOrderBy = '';
this.factoriesFilter = {name: ''};
this.factoriesSelectedStatus = {};
this.isNoSelected = true;
this.isAllSelected = false;
this.isBulkChecked = false;
this.isLoading = true;
this.factories = cheAPI.getFactory().getPageFactories();
let promise = cheAPI.getFactory().fetchFactories(this.maxItems, this.skipCount);
promise.then(() => {
this.isLoading = false;
}, (error: any) => {
this.isLoading = false;
if (error.status !== 304) {
this.cheNotification.showError(error.data && error.data.message ? error.data.message : 'Failed to retrieve the list of factories.');
}
});
this.pagesInfo = cheAPI.getFactory().getPagesInfo();
$rootScope.showIDE = false;
}
/**
* Make all factories in list checked.
*/
selectAllFactories(): void {
this.factories.forEach((factory: che.IFactory) => {
this.factoriesSelectedStatus[factory.id] = true;
});
}
/**
* Make all factories in list unchecked.
*/
deselectAllFactories(): any {
this.factories.forEach((factory: che.IFactory) => {
this.factoriesSelectedStatus[factory.id] = false;
});
}
/**
* Change bulk selection value.
*/
changeBulkSelection(): void {
if (this.isBulkChecked) {
this.deselectAllFactories();
this.isBulkChecked = false;
} else {
this.selectAllFactories();
this.isBulkChecked = true;
}
this.updateSelectedStatus();
}
/**
* Update factories selected status.
*/
updateSelectedStatus(): void {
this.isNoSelected = true;
this.isAllSelected = true;
this.factories.forEach((factory: che.IFactory) => {
if (this.factoriesSelectedStatus[factory.id]) {
this.isNoSelected = false;
} else {
this.isAllSelected = false;
}
});
if (this.isNoSelected) {
this.isBulkChecked = false;
return;
}
if (this.isAllSelected) {
this.isBulkChecked = true;
}
}
/**
* Delete all selected factories
*/
deleteSelectedFactories(): void {
let factoriesSelectedStatusKeys = Object.keys(this.factoriesSelectedStatus);
let checkedFactoriesKeys = [];
if (!factoriesSelectedStatusKeys.length) {
this.cheNotification.showError('No such factories.');
return;
}
factoriesSelectedStatusKeys.forEach((key: string) => {
if (this.factoriesSelectedStatus[key] === true) {
checkedFactoriesKeys.push(key);
}
});
let numberToDelete = checkedFactoriesKeys.length;
if (!numberToDelete) {
this.cheNotification.showError('No such factory.');
return;
}
let confirmationPromise = this.showDeleteFactoriesConfirmation(numberToDelete);
confirmationPromise.then(() => {
let isError = false;
let deleteFactoryPromises = [];
checkedFactoriesKeys.forEach((factoryId: string) => {
this.factoriesSelectedStatus[factoryId] = false;
let promise = this.cheAPI.getFactory().deleteFactoryById(factoryId);
promise.then(() => {
}, (error: any) => {
isError = true;
this.$log.error('Cannot delete factory: ', error);
});
deleteFactoryPromises.push(promise);
});
this.$q.all(deleteFactoryPromises).finally(() => {
this.isLoading = true;
let promise = this.cheAPI.getFactory().fetchFactories(this.maxItems, this.skipCount);
promise.then(() => {
this.isLoading = false;
}, (error) => {
this.isLoading = false;
if (error.status !== 304) {
this.cheNotification.showError(error.data.message ? error.data.message : 'Update information failed.');
}
});
if (isError) {
this.cheNotification.showError('Delete failed.');
} else {
this.cheNotification.showInfo('Selected ' + (numberToDelete === 1 ? 'factory' : 'factories') + ' has been removed.');
}
});
});
}
/**
* Ask for loading the users page in asynchronous way
* @param pageKey - the key of page
*/
fetchFactoriesPage(pageKey: string): void {
this.isLoading = true;
let promise = this.cheAPI.getFactory().fetchFactoryPage(pageKey);
promise.then(() => {
this.isLoading = false;
}, (error) => {
this.isLoading = false;
if (error.status !== 304) {
this.cheNotification.showError(error.data && error.data.message ? error.data.message : 'Update information failed.');
}
});
}
/**
* Returns true if the next page is exist.
* @returns {boolean}
*/
hasNextPage(): boolean {
if (this.pagesInfo.countOfPages) {
return this.pagesInfo.currentPageNumber < this.pagesInfo.countOfPages;
}
return this.factories.length === this.maxItems;
}
/**
* Returns true if the last page is exist.
* @returns {boolean}
*/
hasLastPage(): boolean {
if (this.pagesInfo.countOfPages) {
return this.pagesInfo.currentPageNumber < this.pagesInfo.countOfPages;
}
return false;
}
/**
* Returns true if the previous page is exist.
* @returns {boolean}
*/
hasPreviousPage(): boolean {
return this.pagesInfo.currentPageNumber > 1;
}
/**
* Returns true if we have more then one page.
* @returns {boolean}
*/
isPagination(): boolean {
if (this.pagesInfo.countOfPages) {
return this.pagesInfo.countOfPages > 1;
}
return this.factories.length === this.maxItems || this.pagesInfo.currentPageNumber > 1;
}
/**
* Show confirmation popup before delete
* @param numberToDelete {number}
* @returns {ng.IPromise<any>}
*/
showDeleteFactoriesConfirmation(numberToDelete: number): ng.IPromise<any> {
let content = 'Would you like to delete ';
if (numberToDelete > 1) {
content += 'these ' + numberToDelete + ' factories?';
} else {
content += 'this selected factory?';
}
return this.confirmDialogService.showConfirmDialog('Remove factories', content, 'Delete');
}
}

View File

@ -0,0 +1,108 @@
<!--
CODENVY CONFIDENTIAL
__________________
[2015] - [2016] Codenvy, S.A.
All Rights Reserved.
NOTICE: All information contained herein is, and remains
the property of Codenvy S.A. and its suppliers,
if any. The intellectual and technical concepts contained
herein are proprietary to Codenvy S.A.
and its suppliers and may be covered by U.S. and Foreign Patents,
patents in process, and are protected by trade secret or copyright law.
Dissemination of this information or reproduction of this material
is strictly forbidden unless prior written permission is obtained
from Codenvy S.A..
-->
<che-toolbar che-title="All factories" border-none></che-toolbar>
<che-description che-link-title="Learn more." che-link="/docs/integration-guide/workspace-automation/index.html">
Factories enable workspace automation and are packaged as a consumer-friendly URL. Create new Factories to onboard your team, or integrate
with your toolchain.
</che-description>
<md-content md-scroll-y flex layout="column" md-theme="maincontent-theme" class="factories-list-factories">
<md-progress-linear md-mode="indeterminate" class="factories-list-factories-progress"
ng-if="listFactoriesCtrl.isLoading"></md-progress-linear>
<md-content flex class="md-maincontent-theme-theme" ng-show="!listFactoriesCtrl.isLoading">
<che-list-header che-input-placeholder="Search"
che-search-model="listFactoriesCtrl.factoriesFilter.name"
che-hide-search="listFactoriesCtrl.factories.length === 0"
che-add-button-title="Create Factory"
che-add-button-href="#/factories/create-factory"
che-delete-button-title="Delete"
che-on-delete="listFactoriesCtrl.deleteSelectedFactories()"
che-hide-delete="listFactoriesCtrl.isNoSelected"
che-hide-header="(listFactoriesCtrl.factories | filter:listFactoriesCtrl.factoriesFilter).length === 0">
<div flex="100"
layout="row"
layout-align="start stretch"
class="che-list-item-row">
<div layout="column" layout-gt-xs="row"
layout-align="start center"
class="che-checkbox-area">
<div layout="row" layout-align="center center" class="che-list-item-checkbox-main">
<md-checkbox class="che-list-item-checkbox"
aria-label="Factory list"
ng-checked="listFactoriesCtrl.isBulkChecked"
ng-click="listFactoriesCtrl.changeBulkSelection()"></md-checkbox>
</div>
</div>
<div flex hide-xs layout-gt-xs="row"
layout-align="start center"
class="che-list-item-details">
<che-list-header-column flex-gt-xs="25"
che-sort-value='listFactoriesCtrl.factoriesOrderBy'
che-sort-item='[name, id]'
che-column-title='Factory'></che-list-header-column>
<che-list-header-column flex-gt-xs="60"
che-sort-value='listFactoriesCtrl.factoriesOrderBy'
che-sort-item='workspace.environments[0].machines[0].limits.ram'
che-column-title='RAM'></che-list-header-column>
<che-list-header-column flex-gt-xs="15"
che-column-title='Actions'></che-list-header-column>
</div>
</div>
</che-list-header>
<che-list ng-show="(listFactoriesCtrl.factories | filter:listFactoriesCtrl.factoriesFilter).length > 0">
<cdvy-factory-item
ng-repeat="factory in listFactoriesCtrl.factories | filter:listFactoriesCtrl.factoriesFilter | orderBy:listFactoriesCtrl.factoriesOrderBy"
cdvy-factory="factory"
cdvy-is-selectable="true"
ng-model="listFactoriesCtrl.factoriesSelectedStatus[factory.id]"
cdvy-on-checkbox-click="listFactoriesCtrl.updateSelectedStatus()"/>
</che-list>
<div class="paging-buttons-area" ng-if="listFactoriesCtrl.isPagination()">
<md-button
ng-disabled="!listFactoriesCtrl.hasPreviousPage()"
ng-click="listFactoriesCtrl.fetchFactoriesPage('first');">
<span><<</span>
</md-button>
<md-button
ng-disabled="!listFactoriesCtrl.hasPreviousPage()"
ng-click="listFactoriesCtrl.fetchFactoriesPage('prev');">
<span><</span>
</md-button>
<md-button disabled>
<span>{{listFactoriesCtrl.pagesInfo.currentPageNumber}}</span>
</md-button>
<md-button
ng-disabled="!listFactoriesCtrl.hasNextPage()"
ng-click="listFactoriesCtrl.fetchFactoriesPage('next');">
<span>></span>
</md-button>
<md-button
ng-disabled="!listFactoriesCtrl.hasLastPage()"
ng-click="listFactoriesCtrl.fetchFactoriesPage('last');">
<span>>></span>
</md-button>
</div>
<div class="che-list-empty">
<span ng-show="listFactoriesCtrl.factories.length > 0 && (listFactoriesCtrl.factories | filter:listFactoriesCtrl.factoriesFilter).length === 0">
No factories found.
</span>
<span ng-show="listFactoriesCtrl.factories.length === 0">There are no factories.</span>
</div>
</md-content>
</md-content>

View File

@ -0,0 +1,689 @@
/*
* Copyright (c) 2015-2017 Codenvy, S.A.
* 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:
* Codenvy, S.A. - initial API and implementation
*/
'use strict';
import {CheAPI} from '../../../components/api/che-api.factory';
import {LoadFactoryService} from './load-factory.service';
import {CheNotification} from '../../../components/notification/che-notification.factory';
import {RouteHistory} from '../../../components/routing/route-history.service';
/**
* This class is handling the controller for the factory loading.
* @author Ann Shumilova
*/
export class LoadFactoryController {
private cheAPI: CheAPI;
private $websocket: ng.websocket.IWebSocketProvider;
private $timeout: ng.ITimeoutService;
private $mdDialog: ng.material.IDialogService;
private loadFactoryService: LoadFactoryService;
private lodash: _.LoDashStatic;
private cheNotification: CheNotification;
private $location: ng.ILocationService;
private routeHistory: RouteHistory;
private $window: ng.IWindowService;
private routeParams: any;
private workspaces: Array<che.IWorkspace>;
private workspace: che.IWorkspace;
private projectsToImport: number;
private websocketReconnect: number;
private factory: che.IFactory;
/**
* Default constructor that is using resource
* @ngInject for Dependency injection
*/
constructor(cheAPI: CheAPI, $websocket: ng.websocket.IWebSocketProvider, $route: ng.route.IRouteService, $timeout: ng.ITimeoutService,
$mdDialog: ng.material.IDialogService, loadFactoryService: LoadFactoryService, lodash: _.LoDashStatic, cheNotification: CheNotification,
$location: ng.ILocationService, routeHistory: RouteHistory, $window: ng.IWindowService) {
this.cheAPI = cheAPI;
this.$websocket = $websocket;
this.$timeout = $timeout;
this.$mdDialog = $mdDialog;
this.loadFactoryService = loadFactoryService;
this.lodash = lodash;
this.cheNotification = cheNotification;
this.$location = $location;
this.routeHistory = routeHistory;
this.$window = $window;
this.workspaces = [];
this.workspace = {};
this.websocketReconnect = 50;
this.hideMenuAndFooter();
this.loadFactoryService.resetLoadProgress();
this.loadFactoryService.setLoadFactoryInProgress(true);
this.routeParams = $route.current.params;
this.getFactoryData();
}
/**
* Hides menu and footer to maximize view.
*/
hideMenuAndFooter(): void {
angular.element(document.querySelectorAll('[id*=navmenu]')).hide();
angular.element(document.querySelectorAll('.che-footer')).hide();
}
/**
* Restores the menu and footer.
*/
restoreMenuAndFooter(): void {
angular.element(document.querySelectorAll('[id*=navmenu]')).show();
angular.element(document.querySelectorAll('.che-footer')).show();
}
/**
* Retrieves factory data.
*/
getFactoryData(): void {
let promise;
if (this.routeParams.id) {
this.factory = this.cheAPI.getFactory().getFactoryById(this.routeParams.id);
promise = this.cheAPI.getFactory().fetchFactoryById(this.routeParams.id);
} else if (this.routeParams) {
promise = this.processFactoryParameters(this.routeParams);
} else {
this.getLoadingSteps()[this.getCurrentProgressStep()].hasError = true;
this.getLoadingSteps()[this.getCurrentProgressStep()].logs = 'Required parameters for loading factory are not there.';
}
if (promise) {
promise.then((factory: che.IFactory) => {
this.factory = factory;
// check factory polices:
if (!this.checkPolicies(this.factory)) {
return;
}
// check factory contains workspace config:
if (!this.factory.workspace) {
this.getLoadingSteps()[this.getCurrentProgressStep()].hasError = true;
this.getLoadingSteps()[this.getCurrentProgressStep()].logs = 'Factory has no workspace config.';
} else {
this.fetchWorkspaces();
}
}, (error: any) => {
this.handleError(error);
});
}
}
/**
* Processes factory parameters.
*
* @param parameters
* @returns {any}
*/
processFactoryParameters(parameters: any): ng.IPromise<any> {
// user name and factory name should be handled differently:
if (parameters.name || parameters.user) {
if (Object.keys(parameters).length === 2) {
return this.processUser(parameters.user, parameters.name);
} else {
let paramName = parameters.name ? 'Factory name' : 'User name';
this.getLoadingSteps()[this.getCurrentProgressStep()].logs = 'Invalid factory URL. ' + paramName + ' is missed or misspelled.';
this.getLoadingSteps()[this.getCurrentProgressStep()].hasError = true;
return null;
}
}
return this.cheAPI.getFactory().fetchParameterFactory(parameters);
}
/**
* Processes factory's user. Checks user with such name exists.
*
* @param name user name
* @param factoryName
* @returns {IPromise<IHttpPromiseCallbackArg<any>>}
*/
processUser(name: string, factoryName: string): ng.IPromise<any> {
return this.cheAPI.getUser().fetchUserByName(name).then((user: che.IUser) => {
return this.cheAPI.getFactory().fetchFactoryByName(factoryName, user.id);
}, (error: any) => {
this.getLoadingSteps()[this.getCurrentProgressStep()].logs = 'Invalid factory URL. User with name ' + name + ' does not exist.';
this.getLoadingSteps()[this.getCurrentProgressStep()].hasError = true;
return null;
});
}
/**
* Checks factory's policies.
*
* @param factory factory to be checked
* @returns {boolean} <code>true</code> if factory policies validation has passed
*/
checkPolicies(factory: che.IFactory): boolean {
if (!factory.policies || !factory.policies.referer) {
return true;
}
// process referrer:
let factoryReferrer = factory.policies.referer;
let referrer = document.referrer;
if (referrer && (referrer.indexOf(factoryReferrer) >= 0)) {
return true;
} else {
this.getLoadingSteps()[this.getCurrentProgressStep()].logs = 'Factory referrer policy does not match the current one.';
this.getLoadingSteps()[this.getCurrentProgressStep()].hasError = true;
return false;
}
}
/**
* Handles pointed error - prints it on the proper screen.
*
* @param error error to be handled
*/
handleError(error: any): void {
if (error.data.message) {
this.getLoadingSteps()[this.getCurrentProgressStep()].logs = error.data.message;
this.cheNotification.showError(error.data.message);
}
this.getLoadingSteps()[this.getCurrentProgressStep()].hasError = true;
}
/**
* Detect workspace to start: create new one or get created one.
*/
getWorkspaceToStart(): void {
let createPolicy = (this.factory.policies) ? this.factory.policies.create : 'perClick';
var workspace = null;
switch (createPolicy) {
case 'perUser' :
workspace = this.lodash.find(this.workspaces, (w: che.IWorkspace) => {
return this.factory.id === w.attributes.factoryId;
});
break;
case 'perAccount' :
// todo when account is ready
workspace = this.lodash.find(this.workspaces, (w: che.IWorkspace) => {
return this.factory.workspace.name === w.config.name;
});
break;
case 'perClick' :
break;
}
if (workspace) {
this.startWorkspace(workspace);
} else {
this.createWorkspace();
}
}
/**
* Fetches workspaces.
*/
fetchWorkspaces(): any {
this.loadFactoryService.goToNextStep();
let promise = this.cheAPI.getWorkspace().fetchWorkspaces();
promise.then(() => {
this.workspaces = this.cheAPI.getWorkspace().getWorkspaces();
this.getWorkspaceToStart();
}, () => {
this.workspaces = this.cheAPI.getWorkspace().getWorkspaces();
this.getWorkspaceToStart();
});
}
/**
* Create workspace from factory config.
*/
createWorkspace(): any {
let config = this.factory.workspace;
// set factory attribute:
let attrs = {factoryId: this.factory.id};
config.name = this.getWorkspaceName(config.name);
// todo: fix account when ready:
let creationPromise = this.cheAPI.getWorkspace().createWorkspaceFromConfig(null, config, attrs);
creationPromise.then((data: any) => {
this.$timeout(() => {
this.startWorkspace(data);
}, 1000);
}, (error: any) => {
this.handleError(error);
});
}
/**
* Get workspace name by detecting the existing names
* and generate new name if necessary.
*
* @param name workspace name
* @returns {string} generated name
*/
getWorkspaceName(name: string): string {
if (this.workspaces.length === 0) {
return name;
}
let existingNames = this.lodash.pluck(this.workspaces, 'config.name');
if (existingNames.indexOf(name) < 0) {
return name;
}
let generatedName = name;
let counter = 1;
while (existingNames.indexOf(generatedName) >= 0) {
generatedName = name + '_' + counter++;
}
return generatedName;
}
/**
* Checks workspace status and starts it if necessary,
*
* @param workspace workspace to process
*/
startWorkspace(workspace: che.IWorkspace): void {
this.workspace = workspace;
var bus = this.cheAPI.getWebsocket().getBus();
if (workspace.status === 'RUNNING') {
this.loadFactoryService.setCurrentProgressStep(4);
this.importProjects(bus);
return;
}
this.subscribeOnEvents(workspace, bus);
this.$timeout(() => {
this.doStartWorkspace(workspace);
}, 2000);
}
/**
* Performs workspace start.
*
* @param workspace
*/
doStartWorkspace(workspace: che.IWorkspace): void {
let startWorkspacePromise = this.cheAPI.getWorkspace().startWorkspace(workspace.id, workspace.config.defaultEnv);
this.loadFactoryService.goToNextStep();
startWorkspacePromise.then((data: any) => {
console.log('Workspace started', data);
}, (error) => {
let errorMessage;
if (!error || !error.data) {
errorMessage = 'This factory is unable to start a new workspace.';
} else if (error.data.errorCode === 10000 && error.data.attributes) {
let attributes = error.data.attributes;
errorMessage = 'This factory is unable to start a new workspace.' +
' Your running workspaces are consuming ' +
attributes.used_ram + attributes.ram_unit + ' RAM.' +
' Your current RAM limit is ' + attributes.limit_ram + attributes.ram_unit +
'. This factory requested an additional ' +
attributes.required_ram + attributes.ram_unit + '.' +
' You can stop other workspaces to free resources.';
} else {
errorMessage = error.data.message;
}
this.handleError({data: {message: errorMessage}});
});
}
subscribeOnEvents(data: any, bus: any): void {
// get channels
let statusLink = this.lodash.find(data.links, (link: any) => {
return link.rel === 'environment.status_channel';
});
let outputLink = this.lodash.find(data.links, (link: any) => {
return link.rel === 'environment.output_channel';
});
let workspaceId = data.id;
let agentChannel = 'workspace:' + data.id + ':ext-server:output';
let statusChannel = statusLink ? statusLink.parameters[0].defaultValue : null;
let outputChannel = outputLink ? outputLink.parameters[0].defaultValue : null;
bus.subscribe(outputChannel, (message: any) => {
message = this.getDisplayMachineLog(message);
if (this.getLoadingSteps()[this.getCurrentProgressStep()].logs.length > 0) {
this.getLoadingSteps()[this.getCurrentProgressStep()].logs = this.getLoadingSteps()[this.getCurrentProgressStep()].logs + '\n' + message;
} else {
this.getLoadingSteps()[this.getCurrentProgressStep()].logs = message;
}
});
// for now, display log of status channel in case of errors
bus.subscribe(statusChannel, (message: any) => {
if (message.eventType === 'DESTROYED' && message.workspaceId === data.id) {
this.getLoadingSteps()[this.getCurrentProgressStep()].hasError = true;
// need to show the error
this.$mdDialog.show(
this.$mdDialog.alert()
.title('Unable to start workspace')
.content('Unable to start workspace. It may be linked to OutOfMemory or the container has been destroyed')
.ariaLabel('Workspace start')
.ok('OK')
);
}
if (message.eventType === 'ERROR' && message.workspaceId === data.id) {
this.getLoadingSteps()[this.getCurrentProgressStep()].hasError = true;
// need to show the error
this.$mdDialog.show(
this.$mdDialog.alert()
.title('Error when starting workspace')
.content('Unable to start workspace. Error when trying to start the workspace: ' + message.error)
.ariaLabel('Workspace start')
.ok('OK')
);
}
console.log('Status channel of workspaceID', workspaceId, message);
});
// subscribe to workspace events
bus.subscribe('workspace:' + workspaceId, (message: any) => {
if (message.eventType === 'ERROR' && message.workspaceId === workspaceId) {
// need to show the error
this.$mdDialog.show(
this.$mdDialog.alert()
.title('Error when starting agent')
.content('Unable to start workspace agent. Error when trying to start the workspace agent: ' + message.error)
.ariaLabel('Workspace agent start')
.ok('OK')
);
this.getLoadingSteps()[this.getCurrentProgressStep()].hasError = true;
}
if (message.eventType === 'RUNNING' && message.workspaceId === workspaceId) {
this.finish();
}
});
bus.subscribe(agentChannel, (message: any) => {
let agentStep = 3;
if (this.loadFactoryService.getCurrentProgressStep() < agentStep) {
this.loadFactoryService.setCurrentProgressStep(agentStep);
}
if (this.getLoadingSteps()[agentStep].logs.length > 0) {
this.getLoadingSteps()[agentStep].logs = this.getLoadingSteps()[agentStep].logs + '\n' + message;
} else {
this.getLoadingSteps()[agentStep].logs = message;
}
});
}
/**
* Gets the log to be displayed per machine.
*
* @param log origin log content
* @returns {*} parsed log
*/
getDisplayMachineLog(log: any): string {
log = angular.fromJson(log);
if (angular.isObject(log)) {
return '[' + log.machineName + '] ' + log.content;
} else {
return log;
}
}
/**
* Performs importing projects.
*
* @param bus
*/
importProjects(bus: any): void {
let promise = this.cheAPI.getWorkspace().fetchWorkspaceDetails(this.workspace.id);
promise.then(() => {
let projects = this.cheAPI.getWorkspace().getWorkspacesById().get(this.workspace.id).config.projects;
this.detectProjectsToImport(projects, bus);
}, (error: any) => {
if (error.status !== 304) {
let projects = this.cheAPI.getWorkspace().getWorkspacesById().get(this.workspace.id).config.projects;
this.detectProjectsToImport(projects, bus);
} else {
this.handleError(error);
}
});
}
/**
* Detects the projects to be imported.
*
* @param projects projects list
* @param bus
*/
detectProjectsToImport(projects: Array<che.IProject>, bus: any): void {
this.projectsToImport = 0;
projects.forEach((project: che.IProject) => {
if (!this.isProjectOnFileSystem(project)) {
this.projectsToImport++;
this.importProject(this.workspace.id, project, bus);
}
});
if (this.projectsToImport === 0) {
this.finish();
}
}
/**
* Project is on file system if there is no errors except code=9.
*/
isProjectOnFileSystem(project: che.IProject): boolean {
let problems = project.problems;
if (!problems || problems.length === 0) {
return true;
}
for (var i = 0; i < problems.length; i++) {
if (problems[i].code === 9) {
return true;
}
}
return false;
}
/**
* Performs project import to pointed workspace.
*
* @param workspaceId workspace id, where project should be imported to
* @param project project to be imported
* @param bus
*/
importProject(workspaceId: string, project: che.IProject, bus: any): void {
var promise;
// websocket channel
var channel = 'importProject:output';
// on import
bus.subscribe(channel, (message: any) => {
this.getLoadingSteps()[this.getCurrentProgressStep()].logs = message.line;
});
let projectService = this.cheAPI.getWorkspace().getWorkspaceAgent(workspaceId).getProject();
promise = projectService.importProject(project.name, project.source);
// needs to update configuration of the project
let updatePromise = promise.then(() => {
projectService.updateProject(project.name, project);
}, (error) => {
this.handleError(error);
});
updatePromise.then(() => {
this.projectsToImport--;
if (this.projectsToImport === 0) {
this.finish();
}
bus.unsubscribe(channel);
}, (error: any) => {
bus.unsubscribe(channel);
this.handleError(error);
// need to show the error
this.$mdDialog.show(
this.$mdDialog.alert()
.title('Error while importing project')
.content(error.statusText + ': ' + error.data.message)
.ariaLabel('Import project')
.ok('OK')
);
});
}
/**
* Performs operations at the end of accepting factory.
*/
finish(): void {
this.loadFactoryService.setCurrentProgressStep(4);
// people should go back to the dashboard after factory is initialized
this.routeHistory.pushPath('/');
var ideParams = [];
if (this.routeParams) {
if (this.routeParams.id || (this.routeParams.name && this.routeParams.user)) {
ideParams.push('factory-id:' + this.factory.id);
} else {
// add every factory parameter by prefix
Object.keys(this.routeParams).forEach((key: string) => {
ideParams.push('factory-' + key + ':' + this.$window.encodeURIComponent(this.routeParams[key]));
});
}
// add factory mode
ideParams.push('factory:' + 'true');
}
// add workspace Id
ideParams.push('workspaceId:' + this.workspace.id);
this.$location.path(this.getIDELink()).search('ideParams', ideParams);
// restore elements
this.restoreMenuAndFooter();
}
/**
* Returns workspace name.
*
* @returns {string}
*/
getWorkspace(): string {
return this.workspace.config.name;
}
/**
* Returns the text(logs) of pointed step.
*
* @param stepNumber number of step
* @returns {string} step's text
*/
getStepText(stepNumber: number): string {
return this.loadFactoryService.getStepText(stepNumber);
}
/**
* Returns loading steps of the factory.
*
* @returns {any}
*/
getLoadingSteps(): any {
return this.loadFactoryService.getFactoryLoadingSteps();
}
/**
* Returns the current step, which is in progress.
*
* @returns {any} the info of current step, which is in progress
*/
getCurrentProgressStep(): any {
return this.loadFactoryService.getCurrentProgressStep();
}
/**
* Returns the loading factory in progress state.
*
* @returns {boolean}
*/
isLoadFactoryInProgress(): boolean {
return this.loadFactoryService.isLoadFactoryInProgress();
}
/**
* Set the loading factory process in progress.
*/
setLoadFactoryInProgress(): void {
this.loadFactoryService.setLoadFactoryInProgress(true);
}
/**
* Reset the loading factory process.
*/
resetLoadFactoryInProgress(): void {
this.restoreMenuAndFooter();
let newLocation = this.isResourceProblem() ? '/workspaces' : '/factories';
this.$location.path(newLocation);
this.loadFactoryService.resetLoadProgress();
}
/**
* Returns IDE link.
*
* @returns {string} IDE application link
*/
getIDELink() {
return '/ide/' + this.workspace.namespace + '/' + this.workspace.config.name;
}
/**
* Performs navigation back to dashboard.
*/
backToDashboard(): void {
this.restoreMenuAndFooter();
this.$location.path('/');
}
/**
* Performs downloading of the logs.
*/
downloadLogs(): void {
let logs = '';
this.getLoadingSteps().forEach((step) => {
logs += step.logs + '\n';
});
window.open('data:text/csv,' + encodeURIComponent(logs));
}
/**
* Returns whether there was problem with resources.
*
* @returns {any|boolean}
*/
isResourceProblem(): boolean {
let currentCreationStep = this.getLoadingSteps()[this.getCurrentProgressStep()];
return currentCreationStep.hasError && currentCreationStep.logs.includes('You can stop other workspaces');
}
}

View File

@ -0,0 +1,68 @@
<!--
CODENVY CONFIDENTIAL
__________________
[2015] - [2016] Codenvy, S.A.
All Rights Reserved.
NOTICE: All information contained herein is, and remains
the property of Codenvy S.A. and its suppliers,
if any. The intellectual and technical concepts contained
herein are proprietary to Codenvy S.A.
and its suppliers and may be covered by U.S. and Foreign Patents,
patents in process, and are protected by trade secret or copyright law.
Dissemination of this information or reproduction of this material
is strictly forbidden unless prior written permission is obtained
from Codenvy S.A..
-->
<div class="load-factory-main-container">
<div flex class="ide-loader" layout-align="center center" layout="column">
<div class="load-factory-content">
<che-loader layout="column" layout-aling="center center">
<!--crane and terminals-->
<div layout="row" class="che-loader-animation-panel main-page" flex>
<div layout="column"
hide-xs hide-sm
layout-align="end-start">
<che-loader-crane che-all-steps="loadFactoryController.getLoadingSteps()"
che-exclude-steps="[loadFactoryController.getLoadingSteps().length-1]"
che-step="{{loadFactoryController.getCurrentProgressStep()}}"
che-switch-on-iteration="true">
</che-loader-crane>
</div>
<div layout="column"
layout-align="center center"
flex="auto">
<che-steps-container class="load-factory-working-log"
che-all-steps="loadFactoryController.getLoadingSteps()"
che-current-step="loadFactoryController.getCurrentProgressStep()"></che-steps-container>
</div>
</div>
<!--bottom bar-->
<div class="che-loader-bottom-bar" flex="none">
<div layout="row" layout-align="center stretch" class="main-page load-factory-bottom-bar">
<div flex="50" layout="column" layout-align="end start" class="load-factory-bottom-bar-left">
<che-link ng-click="loadFactoryController.backToDashboard()" che-link-text="&larr; Back to Dashboard"
class="load-factory-back-link"></che-link>
</div>
<div flex="50" layout="column" layout-align="end end" class="load-factory-bottom-bar-right">
<div class="load-factory-retry-block"
layout="column" layout-align="start end">
<che-button-danger che-button-title="{{loadFactoryController.isResourceProblem() ? 'Stop running workspaces' : 'Retry'}}"
ng-click="loadFactoryController.resetLoadFactoryInProgress()"
ng-show="loadFactoryController.getLoadingSteps()[loadFactoryController.getCurrentProgressStep()].hasError"></che-button-danger>
<che-link class="load-factory-download-link"
ng-click="loadFactoryController.downloadLogs()"
che-link-text="Problem? download logs"></che-link>
</div>
</div>
</div>
</div>
</che-loader>
</div>
<div class="load-factory-logo logo-color-white" ng-include="branding.logoURL"></div>
</div>
</div>

View File

@ -0,0 +1,119 @@
/*
* Copyright (c) 2015-2017 Codenvy, S.A.
* 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:
* Codenvy, S.A. - initial API and implementation
*/
'use strict';
/**
* This class is handling the service for the factory loading.
* @author Ann Shumilova
*/
export class LoadFactoryService {
private loadFactoryInProgress: boolean;
private currentProgressStep: number;
private loadingSteps: Array<any>;
/**
* Default constructor that is using resource
* @ngInject for Dependency injection
*/
constructor () {
this.loadFactoryInProgress = false;
this.currentProgressStep = 0;
this.loadingSteps = [
{text: 'Loading factory', inProgressText: '', logs: '', hasError: false},
{text: 'Initializing workspace', inProgressText: 'Provision workspace and associating it with the existing user', logs: '', hasError: false},
{text: 'Starting workspace runtime', inProgressText: 'Retrieving the stack\'s image and launching it', logs: '', hasError: false},
{text: 'Starting workspace agent', inProgressText: 'Agents provide RESTful services like intellisense and SSH', logs: '', hasError: false},
{text: 'Open IDE', inProgressText: '', logs: '', hasError: false}
];
}
/**
* Get the text of the pointed step depending on it's state.
*
* @param stepNumber number of the step.
* @returns {string} steps's text
*/
getStepText(stepNumber: number): string {
let entry = this.loadingSteps[stepNumber];
if (this.currentProgressStep >= stepNumber) {
return entry.inProgressText;
} else {
return entry.text;
}
}
/**
* Returns the information of the factory's loading steps.
*
* @returns {Array<any>} loading steps of the factory
*/
getFactoryLoadingSteps(): Array<any> {
return this.loadingSteps;
}
/**
* Sets the number of the step, which has to be in progress.
*
* @param currentProgressStep step number
*/
setCurrentProgressStep(currentProgressStep: number): void {
this.currentProgressStep = currentProgressStep;
}
/**
* Proceeds the flow to the next step.
*/
goToNextStep(): void {
this.currentProgressStep++;
}
/**
* Returns the number of the current step.
*
* @returns {number} current step's number
*/
getCurrentProgressStep(): number {
return this.currentProgressStep;
}
/**
* Reset the loading progress.
*/
resetLoadProgress(): void {
this.loadingSteps.forEach((step) => {
step.logs = '';
step.hasError = false;
});
this.currentProgressStep = 0;
this.loadFactoryInProgress = false;
}
/**
* Returns the in-progress state of the whole factory loading flow.
*
* @returns {boolean}
*/
isLoadFactoryInProgress(): boolean {
return this.loadFactoryInProgress;
}
/**
* Sets the in-progress state of the whole factory loading flow.
*
* @param value
*/
setLoadFactoryInProgress(value: boolean): void {
this.loadFactoryInProgress = value;
}
}

View File

@ -0,0 +1,47 @@
.load-factory-main-container
position absolute
width 100%
height 100%
.ide-loader
height 100%
width 100%
background-color #1c253f
.load-factory-content
width 100%
.load-factory-working-log
margin 35px 0
.load-factory-bottom-bar
height 90px
.load-factory-bottom-bar-left
margin-left 100px
.load-factory-bottom-bar-right
margin-right 35px
.load-factory-back-link
margin-top 20px
.load-factory-download-link
margin-top 20px
margin-right 8px
@media (max-width:959px)
.load-factory-bottom-bar
.load-factory-bottom-bar-left
margin-left 8px
.load-factory-bottom-bar-right
margin-right 8px
.load-factory-logo
height 35px
position fixed
left 0
right 0
bottom 15px
margin auto
z-index 100

View File

@ -11,6 +11,7 @@
'use strict';
import {Register} from '../components/utils/register';
import {FactoryConfig} from './factories/factories-config';
import {ComponentsConfig} from '../components/components-config';
@ -383,3 +384,4 @@ new WorkspacesConfig(instanceRegister);
new DashboardConfig(instanceRegister);
new StacksConfig(instanceRegister);
new DocsConfig(instanceRegister);
new FactoryConfig(instanceRegister);

View File

@ -22,6 +22,7 @@ export class CheNavBarController {
administration: '#/administration',
// subsections
plugins: '#/admin/plugins',
factories: '#/factories',
account: '#/account',
stacks: '#/stacks'
};
@ -81,6 +82,11 @@ export class CheNavBarController {
getWorkspacesNumber(): number {
return this.cheAPI.getWorkspace().getWorkspaces().length;
}
getFactoriesNumber(): number {
let pagesInfo = this.cheAPI.getFactory().getPagesInfo();
return pagesInfo && pagesInfo.count ? pagesInfo.count : this.cheAPI.getFactory().factoriesById.size;
}
openLinkInNewTab(url: string): void {
this.$window.open(url, '_blank');

View File

@ -49,6 +49,16 @@
</div>
</md-button>
</md-list-item>
<md-list-item flex class="navbar-subsection-item">
<md-button nav-bar-selected flex che-reload-href
href="{{navbarCtrl.menuItemUrl.factories}}" layout-align="left">
<div class="navbar-item" layout="row" layout-align="start center">
<md-icon md-font-icon="navbar-icon chefont cheico-factory"></md-icon>
<span>Factories</span>
<span class="navbar-number" ng-show="navbarCtrl.getFactoriesNumber()">&nbsp;({{navbarCtrl.getFactoriesNumber()}})</span>
</div>
</md-button>
</md-list-item>
<md-list-item flex class="navbar-subsection-item">
<md-button nav-bar-selected flex che-reload-href
href="{{navbarCtrl.menuItemUrl.administration}}" layout-align="left">

View File

@ -12,12 +12,14 @@
import {CheWorkspaceBuilder} from './che-workspace-builder';
import {CheProjectReferenceBuilder} from './che-projectreference-builder';
import {CheFactoryBuilder} from './che-factory-builder';
import {CheProjectDetailsBuilder} from './che-projectdetails-builder';
import {CheProjectTypeBuilder} from './che-projecttype-builder';
import {CheProjectTemplateBuilder} from './che-projecttemplate-builder';
import {CheProjectTypeAttributeDescriptorBuilder} from './che-projecttype-attribute-descriptor-builder';
import {CheProfileBuilder} from './che-profile-builder';
import {CheStackBuilder} from './che-stack-builder';
import {CheUserBuilder} from './che-user-builder';
/**
* This class is providing the entry point for accessing the builders
@ -96,4 +98,20 @@ export class CheAPIBuilder {
getStackBuilder() {
return new CheStackBuilder();
}
/***
* The Che Factory builder
* @returns {CheFactoryBuilder}
*/
getFactoryBuilder() {
return new CheFactoryBuilder();
}
/***
* The Che User builder
* @returns {CheUserBuilder}
*/
getUserBuilder() {
return new CheUserBuilder();
}
}

View File

@ -0,0 +1,87 @@
/*
* Copyright (c) 2015-2017 Codenvy, S.A.
* 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:
* Codenvy, S.A. - initial API and implementation
*/
'use strict';
/**
* This class is providing a builder for factory
* @author Oleksii Orel
*/
export class CheFactoryBuilder {
private factory: che.IFactory;
/**
* Default constructor.
*/
constructor() {
this.factory = {};
this.factory.creator = {};
}
/**
* Sets the creator email
* @param email
* @returns {CheFactoryBuilder}
*/
withCreatorEmail(email) {
this.factory.creator.email = email;
return this;
}
/**
* Sets the creator name
* @param name
* @returns {CheFactoryBuilder}
*/
withCreatorName(name) {
this.factory.creator.name = name;
return this;
}
/**
* Sets the id of the factory
* @param id
* @returns {CheFactoryBuilder}
*/
withId(id) {
this.factory.id = id;
return this;
}
/**
* Sets the name of the factory
* @param name
* @returns {CheFactoryBuilder}
*/
withName(name) {
this.factory.name = name;
return this;
}
/**
* Sets the workspace of the factory
* @param workspace
* @returns {CheFactoryBuilder}
*/
withWorkspace(workspace) {
this.factory.workspace = workspace;
return this;
}
/**
* Build the factory
* @returns {CheFactoryBuilder|*}
*/
build() {
return this.factory;
}
}

View File

@ -0,0 +1,66 @@
/*
* Copyright (c) 2015-2017 Codenvy, S.A.
* 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:
* Codenvy, S.A. - initial API and implementation
*/
'use strict';
/**
* This class is providing a builder for User
* @author Florent Benoit
* @author Oleksii Orel
*/
export class CheUserBuilder {
private user: che.IUser;
/**
* Default constructor.
*/
constructor() {
this.user = {};
}
/**
* Sets the email of the user
* @param email the email to use
* @returns {CodenvyUserBuilder}
*/
withEmail(email) {
this.user.email = email;
return this;
}
/**
* Sets the id of the user
* @param id the id to use
* @returns {CodenvyUserBuilder}
*/
withId(id) {
this.user.id = id;
return this;
}
/**
* Sets the aliases of the user
* @param aliases the aliases to use
* @returns {CodenvyUserBuilder}
*/
withAliases(aliases) {
this.user.aliases = aliases;
return this;
}
/**
* Build the user
* @returns {CodenvyUserBuilder.user|*}
*/
build() {
return this.user;
}
}

View File

@ -15,6 +15,7 @@ import {CheWorkspace} from './che-workspace.factory';
import {CheProjectTemplate} from './che-project-template.factory';
import {CheRecipe} from './che-recipe.factory';
import {CheRecipeTemplate} from './che-recipe-template.factory';
import {CheFactory} from './che-factory.factory';
import {CheStack} from './che-stack.factory';
import {CheWebsocket} from './che-websocket.factory';
import {CheProfile} from './che-profile.factory';
@ -22,6 +23,7 @@ import {ChePreferences} from './che-preferences.factory';
import {CheService} from './che-service.factory';
import {CheHttpBackend} from './test/che-http-backend';
import {CheHttpBackendProviderFactory} from './test/che-http-backend-provider.factory'
import {CheFactoryTemplate} from './che-factory-template.factory';
import {CheHttpBackendFactory} from './test/che-http-backend.factory';
import {CheAPIBuilder} from './builder/che-api-builder.factory';
import {CheAdminPlugins} from './che-admin-plugins.factory';
@ -32,12 +34,14 @@ import {CheEnvironmentRegistry} from './environment/che-environment-registry.fac
import {CheAgent} from './che-agent.factory';
import {CheSsh} from './che-ssh.factory';
import {CheNamespaceRegistry} from './namespace/che-namespace-registry.factory';
import {CheUser} from './che-user.factory';
export class ApiConfig {
constructor(register) {
register.factory('cheWorkspace', CheWorkspace);
register.factory('cheProjectTemplate', CheProjectTemplate);
register.factory('cheFactory', CheFactory);
register.factory('cheProfile', CheProfile);
register.factory('chePreferences', ChePreferences);
register.factory('cheWebsocket', CheWebsocket);
@ -49,6 +53,7 @@ export class ApiConfig {
register.factory('cheAPIBuilder', CheAPIBuilder);
register.factory('cheAdminPlugins', CheAdminPlugins);
register.factory('cheAdminService', CheAdminService);
register.factory('cheFactoryTemplate', CheFactoryTemplate);
register.factory('cheService', CheService);
register.factory('cheAPI', CheAPI);
register.factory('cheRemote', CheRemote);
@ -57,5 +62,6 @@ export class ApiConfig {
register.factory('cheAgent', CheAgent);
register.factory('cheSsh', CheSsh);
register.factory('cheNamespaceRegistry', CheNamespaceRegistry);
register.factory('cheUser', CheUser);
}
}

View File

@ -12,6 +12,8 @@ import {CheSsh} from './che-ssh.factory';
'use strict';
import {CheWorkspace} from './che-workspace.factory';
import {CheProfile} from './che-profile.factory';
import {CheFactory} from './che-factory.factory';
import {CheFactoryTemplate} from './che-factory-template.factory';
import {ChePreferences} from './che-preferences.factory';
import {CheProjectTemplate} from './che-project-template.factory';
import {CheWebsocket} from './che-websocket.factory';
@ -23,6 +25,7 @@ import {CheRecipeTemplate} from './che-recipe-template.factory';
import {CheStack} from './che-stack.factory';
import {CheOAuthProvider} from './che-o-auth-provider.factory';
import {CheAgent} from './che-agent.factory';
import {CheUser} from './che-user.factory';
/**
@ -37,6 +40,8 @@ export class CheAPI {
private chePreferences: ChePreferences;
private cheProjectTemplate: CheProjectTemplate;
private cheWebsocket: CheWebsocket;
private cheFactory: CheFactory;
private cheFactoryTemplate: CheFactoryTemplate;
private cheService: CheService;
private cheAdminPlugins: CheAdminPlugins;
private cheAdminService: CheAdminService;
@ -46,14 +51,20 @@ export class CheAPI {
private cheOAuthProvider: CheOAuthProvider;
private cheAgent: CheAgent;
private cheSsh: CheSsh;
private cheUser: CheUser;
/**
* Default constructor that is using resource
* @ngInject for Dependency injection
*/
constructor(cheWorkspace: CheWorkspace, cheProfile: CheProfile, chePreferences: ChePreferences, cheProjectTemplate: CheProjectTemplate, cheWebsocket: CheWebsocket, cheService: CheService, cheAdminPlugins: CheAdminPlugins, cheAdminService: CheAdminService, cheRecipe: CheRecipe, cheRecipeTemplate: CheRecipeTemplate, cheStack: CheStack, cheOAuthProvider: CheOAuthProvider, cheAgent: CheAgent, cheSsh: CheSsh) {
constructor(cheWorkspace: CheWorkspace, cheFactory: CheFactory, cheFactoryTemplate: CheFactoryTemplate, cheProfile: CheProfile,
chePreferences: ChePreferences, cheProjectTemplate: CheProjectTemplate, cheWebsocket: CheWebsocket, cheService: CheService,
cheAdminPlugins: CheAdminPlugins, cheAdminService: CheAdminService, cheRecipe: CheRecipe, cheRecipeTemplate: CheRecipeTemplate,
cheStack: CheStack, cheOAuthProvider: CheOAuthProvider, cheAgent: CheAgent, cheSsh: CheSsh, cheUser: CheUser) {
this.cheWorkspace = cheWorkspace;
this.cheProfile = cheProfile;
this.cheFactory = cheFactory;
this.cheFactoryTemplate = cheFactoryTemplate;
this.chePreferences = chePreferences;
this.cheProjectTemplate = cheProjectTemplate;
this.cheWebsocket = cheWebsocket;
@ -66,6 +77,7 @@ export class CheAPI {
this.cheOAuthProvider = cheOAuthProvider;
this.cheAgent = cheAgent;
this.cheSsh = cheSsh;
this.cheUser = cheUser;
}
@ -129,7 +141,7 @@ export class CheAPI {
* The Che Admin Services API
* @returns {CheAdminService}
*/
getAdminService() {
getAdminService(): CheAdminService {
return this.cheAdminService;
}
@ -182,4 +194,28 @@ export class CheAPI {
return this.cheSsh;
}
/**
* The Che Factory API
* @returns {CheFactory|*}
*/
getFactory(): CheFactory {
return this.cheFactory;
}
/**
* The Che Factory Template API
* @returns {CheFactoryTemplate|*}
*/
getFactoryTemplate(): CheFactoryTemplate {
return this.cheFactoryTemplate;
}
/**
* The Che use API.
*
* @returns {CheUser}
*/
getUser(): CheUser {
return this.cheUser;
}
}

View File

@ -0,0 +1,77 @@
/*
* Copyright (c) 2015-2017 Codenvy, S.A.
* 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:
* Codenvy, S.A. - initial API and implementation
*/
'use strict';
/**
* This class is handling the factory template retrieval
* It sets to the Map factory templates
* @author Oleksii Orel
*/
export class CheFactoryTemplate {
private $resource: ng.resource.IResourceService;
private $q: ng.IQService;
private factoryTemplatesByName: Map<string, any>;
private remoteFactoryTemplateAPI: ng.resource.IResourceClass<any>;
/**
* Default constructor that is using resource
* @ngInject for Dependency injection
*/
constructor($resource: ng.resource.IResourceService, $q: ng.IQService) {
// keep resource
this.$resource = $resource;
this.$q = $q;
// factory templates map
this.factoryTemplatesByName = new Map();
// remote call
this.remoteFactoryTemplateAPI = this.$resource('https://dockerfiles.codenvycorp.com/templates-4.8/factory/:fileName');
}
/**
* Ask for loading the factory template in asynchronous way
* If there are no changes, it's not updated
* @param templateName the template name
* @returns {*} the promise
*/
fetchFactoryTemplate(templateName: string): ng.IPromise<any> {
var deferred = this.$q.defer();
let templateFileName = templateName + '.json';
let promise = this.remoteFactoryTemplateAPI.get({fileName: templateFileName}).$promise;
promise.then((factoryTemplateContent) => {
//update factory template map
this.factoryTemplatesByName.set(templateName, factoryTemplateContent);
deferred.resolve(factoryTemplateContent);
}, (error) => {
if (error.status === 304) {
let findFactoryTemplateContent = this.factoryTemplatesByName.get(templateName);
deferred.resolve(findFactoryTemplateContent);
} else {
deferred.reject(error);
}
});
return deferred.promise;
}
/**
* Gets factory template by template name
* @param templateName the template name
* @returns factory template content
*/
getFactoryTemplate(templateName: string): any {
return this.factoryTemplatesByName.get(templateName);
}
}

View File

@ -0,0 +1,645 @@
/*
* Copyright (c) 2015-2017 Codenvy, S.A.
* 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:
* Codenvy, S.A. - initial API and implementation
*/
'use strict';
import {CheUser} from './che-user.factory';
/* global FormData */
interface IFactoriesResource<T> extends ng.resource.IResourceClass<T> {
updateFactory: any;
getFactoryContentFromWorkspace: any;
getFactoryParameters: any;
createFactoryByContent: any;
getFactories: any;
getFactoryByName: any;
}
/**
* This class is handling the factory retrieval
* @author Florent Benoit
* @author Oleksii Orel
*/
export class CheFactory {
private $resource: ng.resource.IResourceService;
private $q: ng.IQService;
private lodash: _.LoDashStatic;
private cheUser: CheUser;
private remoteFactoryAPI: IFactoriesResource<any>;
private factoriesById: Map<string, che.IFactory>;
private factoriesByName: Map<string, che.IFactory>;
private parametersFactories: Map<string, che.IFactory>;
private factoryContentsByWorkspaceId: Map<string, any>;
private pageFactories: Array<che.IFactory>;
private factoryPagesMap: Map<number, any>;
private pageInfo: any;
private itemsPerPage: number;
/**
* Default constructor that is using resource
* @ngInject for Dependency injection
*/
constructor($resource: ng.resource.IResourceService, $q: ng.IQService, lodash: _.LoDashStatic, cheUser: CheUser) {
// keep resource
this.$resource = $resource;
this.$q = $q;
this.cheUser = cheUser;
this.lodash = lodash;
// factories details by id
this.factoriesById = new Map();
// factories details by key: userID:factoryName
this.factoriesByName = new Map();
this.parametersFactories = new Map();
this.factoryContentsByWorkspaceId = new Map();
//paging
this.pageFactories = [];// all current page factories
this.factoryPagesMap = new Map();// page factories by relative link
this.pageInfo = {};//pages info
this.remoteFactoryAPI = <IFactoriesResource<any>>this.$resource('/api/factory/:factoryId', {factoryId: '@id'}, {
updateFactory: {method: 'PUT', url: '/api/factory/:factoryId'},
getFactoryContentFromWorkspace: {method: 'GET', url: '/api/factory/workspace/:workspaceId'},
getFactoryParameters: {method: 'POST', url: '/api/factory/resolver/'},
createFactoryByContent: {
method: 'POST',
url: '/api/factory',
isArray: false,
headers: {'Content-Type': undefined},
transformRequest: angular.identity
},
getFactories: {
method: 'GET',
url: '/api/factory/find?creator.userId=:userId&maxItems=:maxItems&skipCount=:skipCount',
isArray: false,
responseType: 'json',
transformResponse: (data, headersGetter) => {
return this._getPageFromResponse(data, headersGetter('link'));
}
},
getFactoryByName: {
method: 'GET',
url: '/api/factory/find?creator.userId=:userId&name=:factoryName',
isArray: true
}
});
}
_getPageFromResponse(data: any, headersLink: any): any {
let links = new Map();
if (!headersLink) {
//TODO remove it after adding headers paging links on server side
let user = this.cheUser.getUser().id;
if (!this.itemsPerPage || !user) {
return {factories: data};
}
this.pageInfo.currentPageNumber = this.pageInfo.currentPageNumber ? this.pageInfo.currentPageNumber : 1;
let link = '/api/factory/find?creator.userId=' + user + '&maxItems=' + this.itemsPerPage;
links.set('first', link + '&skipCount=0');
if (data && data.length > 0) {
links.set('next', link + '&skipCount=' + this.pageInfo.currentPageNumber * this.itemsPerPage);
}
if (this.pageInfo.currentPageNumber > 1) {
links.set('prev', link + '&skipCount=' + (this.pageInfo.currentPageNumber - 2) * this.itemsPerPage);
}
return {
factories: data,
links: links
};
}
let pattern = new RegExp('<([^>]+?)>.+?rel="([^"]+?)"', 'g');
let result;
while (result = pattern.exec(headersLink)) { //look for pattern
links.set(result[2], result[1]);//add link
}
return {
factories: data,
links: links
};
}
_getPageParamByLink(pageLink: string): any {
let pageParamMap = new Map();
let pattern = new RegExp('([_\\w]+)=([\\w]+)', 'g');
let result;
while (result = pattern.exec(pageLink)) {
pageParamMap.set(result[1], result[2]);
}
let skipCount = pageParamMap.get('skipCount');
let maxItems = pageParamMap.get('maxItems');
if (!maxItems || maxItems === 0) {
return null;
}
return {
maxItems: maxItems,
skipCount: skipCount ? skipCount : 0
};
}
_updateCurrentPage(): void {
let pageData = this.factoryPagesMap.get(this.pageInfo.currentPageNumber);
if (!pageData) {
return;
}
this.pageFactories.length = 0;
if (!pageData.factories) {
return;
}
pageData.factories.forEach((factory: che.IFactory) => {
factory.name = factory.name ? factory.name : '';
this.pageFactories.push(factory);
});
}
_updateCurrentPageFactories(factories: Array<any>): void {
this.pageFactories.length = 0;
if (!factories) {
return;
}
factories.forEach((factory: any) => {
factory.name = factory.name ? factory.name : '';
this.pageFactories.push(factory);
});
}
/**
* Update factory page links by relative direction ('first', 'prev', 'next', 'last')
*/
_updatePagesData(data: any): void {
if (!data.links) {
return;
}
let firstPageLink = data.links.get('first');
if (firstPageLink) {
let firstPageData = {link: firstPageLink, factories: null};
if (this.pageInfo.currentPageNumber === 1) {
firstPageData.factories = data.factories;
}
if (!this.factoryPagesMap.get(1) || firstPageData.factories) {
this.factoryPagesMap.set(1, firstPageData);
}
}
let lastPageLink = data.links.get('last');
if (lastPageLink) {
let pageParam = this._getPageParamByLink(lastPageLink);
this.pageInfo.countOfPages = pageParam.skipCount / pageParam.maxItems + 1;
this.pageInfo.count = pageParam.skipCount;
let lastPageData = {link: lastPageLink, factories: null};
if (this.pageInfo.currentPageNumber === this.pageInfo.countOfPages) {
lastPageData.factories = data.factories;
}
if (!this.factoryPagesMap.get(this.pageInfo.countOfPages) || lastPageData.factories) {
this.factoryPagesMap.set(this.pageInfo.countOfPages, lastPageData);
}
}
let prevPageLink = data.links.get('prev');
let prevPageNumber = this.pageInfo.currentPageNumber - 1;
if (prevPageNumber > 0 && prevPageLink) {
let prevPageData = {link: prevPageLink};
if (!this.factoryPagesMap.get(prevPageNumber)) {
this.factoryPagesMap.set(prevPageNumber, prevPageData);
}
}
let nextPageLink = data.links.get('next');
let nextPageNumber = this.pageInfo.currentPageNumber + 1;
if (nextPageNumber) {
let nextPageData = {link: nextPageLink};
if (!this.factoryPagesMap.get(nextPageNumber)) {
this.factoryPagesMap.set(nextPageNumber, nextPageData);
}
}
}
/**
* Returns the page information.
* @returns {Object}
*/
getPagesInfo(): any {
return this.pageInfo;
}
/**
* Ask for loading the factories in asynchronous way
* If there are no changes, it's not updated
* @param maxItems - the max number of items to return
* @param skipCount - the number of items to skip
* @returns {*} the promise
*/
fetchFactories(maxItems, skipCount): ng.IPromise<any> {
this.itemsPerPage = maxItems;
let promise = this._getFactories({maxItems: maxItems, skipCount: skipCount});
return promise.then((data: any) => {
this.pageInfo.currentPageNumber = skipCount / maxItems + 1;
this._updateCurrentPageFactories(data.factories);
this._updatePagesData(data);
});
}
/**
* Ask for loading the factories page in asynchronous way
* If there are no changes, it's not updated
* @param pageKey - the key of page ('first', 'prev', 'next', 'last' or '1', '2', '3' ...)
* @returns {*} the promise
*/
fetchFactoryPage(pageKey: string) {
let deferred = this.$q.defer();
let pageNumber;
if ('first' === pageKey) {
pageNumber = 1;
} else if ('prev' === pageKey) {
pageNumber = this.pageInfo.currentPageNumber - 1;
} else if ('next' === pageKey) {
pageNumber = this.pageInfo.currentPageNumber + 1;
} else if ('last' === pageKey) {
pageNumber = this.pageInfo.countOfPages;
} else {
pageNumber = parseInt(pageKey, 10);
}
if (pageNumber < 1) {
pageNumber = 1;
} else if (pageNumber > this.pageInfo.countOfPages) {
pageNumber = this.pageInfo.countOfPages;
}
let pageData = this.factoryPagesMap.get(pageNumber);
if (pageData.link) {
this.pageInfo.currentPageNumber = pageNumber;
let queryData = this._getPageParamByLink(pageData.link);
if (!queryData) {
deferred.reject({data: {message: 'Error. No necessary link.'}});
return deferred.promise;
}
let promise = this._getFactories(queryData);
promise.then((data: any) => {
this._updatePagesData(data);
pageData.factories = data.factories;
this._updateCurrentPage();
deferred.resolve(data);
}, (error: any) => {
if (error && error.status === 304) {
this._updateCurrentPage();
}
deferred.reject(error);
});
} else {
deferred.reject({data: {message: 'Error. No necessary link.'}});
}
return deferred.promise;
}
_getFactories(queryData): ng.IPromise<any> {
let deferred = this.$q.defer();
let user = this.cheUser.getUser();
if (user) {
queryData.userId = user.id;
this.remoteFactoryAPI.getFactories(queryData).$promise.then((data) => {
this._updateFactoriesDetails(data.factories).then((factoriesDetails) => {
data.factories = factoriesDetails;//update factories
deferred.resolve(data);
}, (error) => {
deferred.reject(error);
});
}, (error) => {
deferred.reject(error);
});
} else {
this.cheUser.fetchUser().then((user) => {
queryData.userId = user.id;
this.remoteFactoryAPI.getFactories(queryData).$promise.then((data) => {
this._updateFactoriesDetails(data.factories).then((factoriesDetails) => {
data.factories = factoriesDetails;//update factories
deferred.resolve(data);
}, (error) => {
deferred.reject(error);
});
}, (error) => {
deferred.reject(error);
});
});
}
return deferred.promise;
}
_updateFactoriesDetails(factories: Array<any>): ng.IPromise<any> {
let deferred = this.$q.defer();
let factoriesDetails = [];
if (!factories || factories.length === 0) {
deferred.resolve(factoriesDetails);
}
let promises = [];
factories.forEach((factory: any) => {
let factoryPromise = this.fetchFactoryById(factory.id);//ask the factory details
factoryPromise.then((factoryDetails: any) => {
factoriesDetails.push(factoryDetails);
});
promises.push(factoryPromise);
});
this.$q.all(promises).then(() => {
deferred.resolve(factoriesDetails);
}, (error) => {
deferred.reject(error);
});
return deferred.promise;
}
/**
* Gets the factory service path.
* @returns {string}
*/
getFactoryServicePath(): string {
return 'factory';
}
/**
* Ask for loading the factory content in asynchronous way
* If there are no changes, it's not updated
* @param workspace workspace
* @returns {*} the promise
*/
fetchFactoryContentFromWorkspace(workspace: che.IWorkspace): ng.IPromise<any> {
let deferred = this.$q.defer();
let factoryContent = this.factoryContentsByWorkspaceId.get(workspace.id);
if (factoryContent) {
deferred.resolve(factoryContent);
}
let promise = this.remoteFactoryAPI.getFactoryContentFromWorkspace({
workspaceId: workspace.id
}).$promise;
promise.then((factoryContent: any) => {
//update factoryContents map
this.factoryContentsByWorkspaceId.set(workspace.id, factoryContent);
deferred.resolve(factoryContent);
}, (error: any) => {
if (error.status === 304) {
let findFactoryContent = this.factoryContentsByWorkspaceId.get(workspace.id);
deferred.resolve(findFactoryContent);
} else {
deferred.reject(error);
}
});
return deferred.promise;
}
/**
* Get factory from workspace
* @param workspace
* @return the factory content
*/
getFactoryContentFromWorkspace(workspace: che.IWorkspace): any {
return this.factoryContentsByWorkspaceId.get(workspace.workspaceId);
}
/**
* Create factory by content
* @param factoryContent the factory content
* @returns {*} the promise
*/
createFactoryByContent(factoryContent: any): ng.IPromise<any> {
let formDataObject = new FormData();
formDataObject.append('factory', factoryContent);
return this.remoteFactoryAPI.createFactoryByContent({}, formDataObject).$promise;
}
/**
* Gets the current page factories
* @returns {Array}
*/
getPageFactories(): Array<che.IFactory> {
return this.pageFactories;
}
/**
* Ask for loading the factory in asynchronous way
* If there are no changes, it's not updated
* @param factoryId the factory ID
* @returns {*} the promise
*/
fetchFactoryById(factoryId: string): ng.IPromise<any> {
let deferred = this.$q.defer();
let promise = this.remoteFactoryAPI.get({factoryId: factoryId}).$promise;
promise.then((factory: any) => {
factory.name = factory.name ? factory.name : '';
this.factoriesById.set(factoryId, factory);
deferred.resolve(factory);
}, (error: any) => {
if (error.status === 304) {
deferred.resolve(this.factoriesById.get(factoryId));
} else {
deferred.reject(error);
}
});
return deferred.promise;
}
/**
* Fetches factory by the user's id and factory's name.
*
* @param factoryName name of the factory to be fetched.
* @param userId user id
* @returns {IPromise<T>}
*/
fetchFactoryByName(factoryName: string, userId: string): ng.IPromise<any> {
let deferred = this.$q.defer();
let key = userId + ':' + factoryName;
let promise = this.remoteFactoryAPI.getFactoryByName({factoryName: factoryName, userId: userId}).$promise;
promise.then((factories: Array<che.IFactory>) => {
let factory = factories.length ? factories[0] : null;
this.factoriesByName.set(key, factory);
deferred.resolve(factory);
}, (error: any) => {
if (error.status === 304) {
deferred.resolve(this.factoriesByName.get(key));
} else {
deferred.reject(error);
}
});
return deferred.promise;
}
/**
* Ask for getting parameter the factory in asynchronous way
* If there are no changes, it's not updated
* @param parameters the factory parameters
* @returns {*} the promise
*/
fetchParameterFactory(parameters: any): ng.IPromise<any> {
let deferred = this.$q.defer();
let promise = this.remoteFactoryAPI.getFactoryParameters({}, parameters).$promise;
promise.then((factory: any) => {
factory.name = factory.name ? factory.name : '';
this.parametersFactories.set(parameters, factory);
deferred.resolve(factory);
}, (error: any) => {
if (error.status === 304) {
deferred.resolve(this.parametersFactories.get(parameters));
} else {
deferred.reject(error);
}
});
return deferred.promise;
}
/**
* Detects links for factory acceptance (with id and named one)
* @param factory factory to detect links
* @returns [*] links acceptance links
*/
detectLinks(factory: che.IFactory): Array<any> {
let links = [];
if (!factory || !factory.links) {
return links;
}
this.lodash.find(factory.links, (link: any) => {
if (link.rel === 'accept' || link.rel === 'accept-named') {
links.push(link.href);
}
});
return links;
}
/**
* Get the factory from factoryMap by factoryId
* @param factoryId the factory ID
* @returns factory
*/
getFactoryById(factoryId: string): any {
return this.factoriesById.get(factoryId);
}
/**
* Set the factory
* @param factory
* @returns {*} the promise
*/
setFactory(factory: che.IFactory): ng.IPromise<any> {
let deferred = this.$q.defer();
// check factory
if (!factory || !factory.id) {
deferred.reject({data: {message: 'Read factory error.'}});
return deferred.promise;
}
let promise = this.remoteFactoryAPI.updateFactory({factoryId: factory.id}, factory).$promise;
// check if was OK or not
promise.then((factory: any) => {
factory.name = factory.name ? factory.name : '';
this.fetchFactoryPage(this.pageInfo.currentPageNumber);
deferred.resolve(factory);
}, (error: any) => {
deferred.reject(error);
});
return deferred.promise;
}
/**
* Set the factory content by factoryId
* @param factoryId the factory ID
* @param factoryContent the factory content
* @returns {*} the promise
*/
setFactoryContent(factoryId: string, factoryContent: any): ng.IPromise<any> {
let deferred = this.$q.defer();
let promise = this.remoteFactoryAPI.updateFactory({factoryId: factoryId}, factoryContent).$promise;
// check if was OK or not
promise.then(() => {
let fetchFactoryPromise = this.fetchFactoryById(factoryId);
//check if was OK or not
fetchFactoryPromise.then((factory: any) => {
this.fetchFactoryPage(this.pageInfo.currentPageNumber);
deferred.resolve(factory);
}, (error: any) => {
deferred.reject(error);
});
}, (error) => {
deferred.reject(error);
});
return deferred.promise;
}
/**
* Performs factory deleting by the given factoryId.
* @param factoryId the factory ID
* @returns {*} the promise
*/
deleteFactoryById(factoryId: string): ng.IPromise<any> {
let promise = this.remoteFactoryAPI.delete({factoryId: factoryId}).$promise;
//check if was OK or not
return promise.then(() => {
this.factoriesById.delete(factoryId);//update factories map
if (this.pageInfo && this.pageInfo.currentPageNumber) {
this.fetchFactoryPage(this.pageInfo.currentPageNumber);
}
});
}
/**
* Helper method that extract the factory ID from a factory URL
* @param factoryURL the factory URL to analyze
* @returns the stringified ID of a factory
*/
getIDFromFactoryAPIURL(factoryURL: string): string {
let index = factoryURL.lastIndexOf('/factory/');
if (index > 0) {
return factoryURL.slice(index + '/factory/'.length, factoryURL.length);
}
}
/**
* Returns the factory url based on id.
* @returns {link.href|*} link value
*/
getFactoryIdUrl(factory: any): string {
let link = this.lodash.find(factory.links, (link: any) => {
return 'accept' === link.rel;
});
return link ? link.href : 'No value';
}
/**
* Returns the factory url based on name.
*
* @returns {link.href|*} link value
*/
getFactoryNamedUrl(factory: any): string {
let link = this.lodash.find(factory.links, (link: any) => {
return 'accept-named' === link.rel;
});
return link ? link.href : null;
}
}

View File

@ -0,0 +1,238 @@
/*
* Copyright (c) 2015-2017 Codenvy, S.A.
* 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:
* Codenvy, S.A. - initial API and implementation
*/
'use strict';
/**
* Test of the Codenvy Factory API
*/
describe('CheFactory', () => {
/**
* User Factory for the test
*/
let factory;
/**
* API builder.
*/
let apiBuilder;
/**
* Backend for handling http operations
*/
let httpBackend;
/**
* Che backend
*/
let cheBackend;
/**
* setup module
*/
beforeEach(angular.mock.module('userDashboard'));
/**
* Inject factory and http backend
*/
beforeEach(inject((cheFactory, cheAPIBuilder, cheHttpBackend) => {
factory = cheFactory;
apiBuilder = cheAPIBuilder;
cheBackend = cheHttpBackend;
httpBackend = cheHttpBackend.getHttpBackend();
}));
/**
* Check assertion after the test
*/
afterEach(() => {
httpBackend.verifyNoOutstandingExpectation();
httpBackend.verifyNoOutstandingRequest();
});
/**
* Check that we're able to fetch factory data
*/
it('Fetch factories', () => {
// setup tests objects
let maxItem = 3;
let skipCount = 0;
let testUser = apiBuilder.getUserBuilder().withId('testUserId').build();
let testFactory1 = apiBuilder.getFactoryBuilder().withId('testId1').withName('testName1').withCreatorEmail('testEmail1').build();
let testFactory2 = apiBuilder.getFactoryBuilder().withId('testId2').withName('testName2').withCreatorEmail('testEmail2').build();
let testFactory3 = apiBuilder.getFactoryBuilder().withId('testId3').withName('testName3').withCreatorEmail('testEmail3').build();
let testFactory4 = apiBuilder.getFactoryBuilder().withId('testId4').withName('testName4').withCreatorEmail('testEmail4').build();
// providing requests
// add test objects on Http backend
cheBackend.setDefaultUser(testUser);
cheBackend.addUserFactory(testFactory1);
cheBackend.addUserFactory(testFactory2);
cheBackend.addUserFactory(testFactory3);
cheBackend.addUserFactory(testFactory4);
cheBackend.setPageMaxItem(maxItem);
cheBackend.setPageSkipCount(skipCount);
// setup backend for factories
cheBackend.factoriesBackendSetup();
// fetch factory
factory.fetchFactories(maxItem, skipCount);
// expecting GETs
httpBackend.expectGET('/api/user');
httpBackend.expectGET('/api/factory/find?creator.userId=' + testUser.id + '&maxItems=' + maxItem + '&skipCount=' + skipCount);
httpBackend.expectGET('/api/factory/' + testFactory1.id);
httpBackend.expectGET('/api/factory/' + testFactory2.id);
httpBackend.expectGET('/api/factory/' + testFactory3.id);
// flush command
httpBackend.flush();
// now, check factories
let pageFactories = factory.getPageFactories();
let factory1 = factory.getFactoryById(testFactory1.id);
let factory2 = factory.getFactoryById(testFactory2.id);
let factory3 = factory.getFactoryById(testFactory3.id);
// check id, name and email of pge factories
expect(pageFactories.length).toEqual(maxItem);
expect(factory1.id).toEqual(testFactory1.id);
expect(factory1.name).toEqual(testFactory1.name);
expect(factory1.creator.email).toEqual(testFactory1.creator.email);
expect(factory2.id).toEqual(testFactory2.id);
expect(factory2.name).toEqual(testFactory2.name);
expect(factory2.creator.email).toEqual(testFactory2.creator.email);
expect(factory3.id).toEqual(testFactory3.id);
expect(factory3.name).toEqual(testFactory3.name);
expect(factory3.creator.email).toEqual(testFactory3.creator.email);
}
);
/**
* Check that we're able to fetch factory data by id
*/
it('Fetch factor by id', () => {
// setup tests objects
let testFactory = apiBuilder.getFactoryBuilder().withId('testId').withName('testName').withCreatorEmail('testEmail').build();
// providing request
// add test factory on Http backend
cheBackend.addUserFactory(testFactory);
// setup backend
cheBackend.factoriesBackendSetup();
// fetch factory
factory.fetchFactoryById(testFactory.id);
// expecting GETs
httpBackend.expectGET('/api/factory/' + testFactory.id);
// flush command
httpBackend.flush();
// now, check factory
let targetFactory = factory.getFactoryById(testFactory.id);
// check id, name and email of factory
expect(targetFactory.id).toEqual(testFactory.id);
expect(targetFactory.name).toEqual(testFactory.name);
expect(targetFactory.creator.email).toEqual(testFactory.creator.email);
}
);
/**
* Check that we're able to delete factor by id
*/
it('Delete factor by id', () => {
// setup tests objects
let testFactory = apiBuilder.getFactoryBuilder().withId('testId').withName('testName').withCreatorEmail('testEmail').build();
// providing request
// add test factory on Http backend
cheBackend.addUserFactory(testFactory);
// setup backend
cheBackend.factoriesBackendSetup();
// delete factory
factory.deleteFactoryById(testFactory.id);
// expecting GETs
httpBackend.expectDELETE('/api/factory/' + testFactory.id);
// flush command
httpBackend.flush();
}
);
/**
* Gets factory page object from response
*/
it('Gets factory page object from response', () => {
let testFactory1 = apiBuilder.getFactoryBuilder().withId('testId1').withName('testName1').withCreatorEmail('testEmail1').build();
let testFactory2 = apiBuilder.getFactoryBuilder().withId('testId2').withName('testName2').withCreatorEmail('testEmail2').build();
let factories = [testFactory1, testFactory2];
let test_link_1 = '/api/factory/find?creator.userId=testUserId&skipCount=0&maxItems=5';
let test_rel_1 = 'first';
let test_link_2 = '/api/factory/find?creator.userId=testUserId&skipCount=20&maxItems=5';
let test_rel_2 = 'last';
let test_link_3 = '/api/factory/find?creator.userId=testUserId&skipCount=5&maxItems=5';
let test_rel_3 = 'next';
let headersLink = '\<' + test_link_1 + '\>' + '; rel="' + test_rel_1 + '",' +
'\<' + test_link_2 + '\>' + '; rel="' + test_rel_2 + '",' +
'\<' + test_link_3 + '\>' + '; rel="' + test_rel_3 + '"';
cheBackend.factoriesBackendSetup();
// gets page
let pageObject = factory._getPageFromResponse(factories, headersLink);
httpBackend.flush();
// check page factories and links
expect(pageObject.factories).toEqual(factories);
expect(pageObject.links.get(test_rel_1)).toEqual(test_link_1);
expect(pageObject.links.get(test_rel_2)).toEqual(test_link_2);
expect(pageObject.links.get(test_rel_3)).toEqual(test_link_3);
}
);
/**
* Gets maxItems and skipCount from link params
*/
it('Gets maxItems and skipCount from link params', () => {
let skipCount = 20;
let maxItems = 5;
let test_link = '/api/factory/find?creator.userId=testUserId&skipCount=' + skipCount + '&maxItems=' + maxItems;
cheBackend.factoriesBackendSetup();
// gets page
let pageParams = factory._getPageParamByLink(test_link);
httpBackend.flush();
// check page factories and links
expect(parseInt(pageParams.maxItems, 10)).toEqual(maxItems);
expect(parseInt(pageParams.skipCount, 10)).toEqual(skipCount);
}
);
});

View File

@ -0,0 +1,432 @@
/*
* Copyright (c) 2015-2017 Codenvy, S.A.
* 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:
* Codenvy, S.A. - initial API and implementation
*/
'use strict';
interface IUsersResource<T> extends ng.resource.IResourceClass<T> {
findByID: any,
findByAlias: any,
findByName: any,
setPassword: any,
createUser: any,
getUsers: any,
removeUserById: any
}
/**
* This class is handling the user API retrieval
* @author Oleksii Orel
*/
export class CheUser {
private $resource: ng.resource.IResourceService;
private $q: ng.IQService;
private $cookies: ng.cookies.ICookiesService;
private remoteUserAPI: IUsersResource<any>;
private logoutAPI: ng.resource.IResourceClass<any>;
private useridMap: Map<string, che.IUser>;
private userAliasMap: Map<string, che.IUser>;
private userNameMap: Map<string, che.IUser>;
private usersMap: Map<string, che.IUser>;
private userPagesMap: Map<number, any>;
private pageInfo: any;
/**
* Current user.
*/
private user: che.IUser;
/**
* Default constructor that is using resource
* @ngInject for Dependency injection
*/
constructor($resource: ng.resource.IResourceService, $q: ng.IQService, $cookies: ng.cookies.ICookiesService) {
this.$q = $q;
this.$resource = $resource;
this.$cookies = $cookies;
// remote call
this.remoteUserAPI = <IUsersResource<any>> this.$resource('/api/user', {}, {
findByID: {method: 'GET', url: '/api/user/:userId'},
findByAlias: {method: 'GET', url: '/api/user/find?email=:alias'},
findByName: {method: 'GET', url: '/api/user/find?name=:name'},
setPassword: {
method: 'POST', url: '/api/user/password', isArray: false,
headers: {
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'
}
},
createUser: {method: 'POST', url: '/api/user'},
getUsers: {
method: 'GET',
url: '/api/admin/user?maxItems=:maxItems&skipCount=:skipCount',
isArray: false,
responseType: 'json',
transformResponse: (data: any, headersGetter: any) => {
return this._getPageFromResponse(data, headersGetter('link'));
}
},
removeUserById: {method: 'DELETE', url: '/api/user/:userId'}
});
this.logoutAPI = this.$resource('/api/auth/logout', {});
// users by ID
this.useridMap = new Map();
// users by alias
this.userAliasMap = new Map();
// users by name
this.userNameMap = new Map();
// all users by ID
this.usersMap = new Map();
// page users by relative link
this.userPagesMap = new Map();
//pages info
this.pageInfo = {};
//Current user has to be for sure fetched:
this.fetchUser();
}
/**
* Create new user
* @param name - new user name
* @param email - new user e-mail
* @param password - new user password
* @returns {*}
*/
createUser(name: string, email: string, password: string): ng.IPromise<any> {
let data: {
password: string;
name: string;
email?: string;
};
data = {
password: password,
name: name
};
if (email) {
data.email = email;
}
let promise = this.remoteUserAPI.createUser(data).$promise;
// check if was OK or not
promise.then((user: che.IUser) => {
//update users map
this.usersMap.set(user.id, user);//add user
});
return promise;
}
_getPageFromResponse(data: any, headersLink: string): any {
let links = new Map();
if (!headersLink) {
return {users: data};
}
let pattern = new RegExp('<([^>]+?)>.+?rel="([^"]+?)"', 'g');
let result;
while (result = pattern.exec(headersLink)) { //look for pattern
links.set(result[2], result[1]);//add link
}
return {
users: data,
links: links
};
}
_getPageParamByLink(pageLink: string): any {
let lastPageParamMap = new Map();
let pattern = new RegExp('([_\\w]+)=([\\w]+)', 'g');
let result;
while (result = pattern.exec(pageLink)) {
lastPageParamMap.set(result[1], result[2]);
}
let skipCount = lastPageParamMap.get('skipCount');
let maxItems = lastPageParamMap.get('maxItems');
if (!maxItems || maxItems === 0) {
return null;
}
return {
maxItems: maxItems,
skipCount: skipCount ? skipCount : 0
};
}
_updateCurrentPage(): void {
let pageData = this.userPagesMap.get(this.pageInfo.currentPageNumber);
if (!pageData) {
return;
}
this.usersMap.clear();
if (!pageData.users) {
return;
}
pageData.users.forEach((user: che.IUser) => {
this.usersMap.set(user.id, user);//add user
});
}
_updateCurrentPageUsers(users: Array<any>): void {
this.usersMap.clear();
if (!users) {
return;
}
users.forEach((user: che.IUser) => {
this.usersMap.set(user.id, user);//add user
});
}
/**
* Update user page links by relative direction ('first', 'prev', 'next', 'last')
*/
_updatePagesData(data: any): any {
if (!data.links) {
return;
}
let firstPageLink = data.links.get('first');
if (firstPageLink) {
let firstPageData: { users?: Array<any>; link?: string; } = {link: firstPageLink};
if (this.pageInfo.currentPageNumber === 1) {
firstPageData.users = data.users;
}
if (!this.userPagesMap.get(1) || firstPageData.users) {
this.userPagesMap.set(1, firstPageData);
}
}
let lastPageLink = data.links.get('last');
if (lastPageLink) {
let pageParam = this._getPageParamByLink(lastPageLink);
this.pageInfo.countOfPages = pageParam.skipCount / pageParam.maxItems + 1;
this.pageInfo.count = pageParam.skipCount;
let lastPageData: { users?: Array<any>; link?: string; } = {link: lastPageLink};
if (this.pageInfo.currentPageNumber === this.pageInfo.countOfPages) {
lastPageData.users = data.users;
}
if (!this.userPagesMap.get(this.pageInfo.countOfPages) || lastPageData.users) {
this.userPagesMap.set(this.pageInfo.countOfPages, lastPageData);
}
}
let prevPageLink = data.links.get('prev');
let prevPageNumber = this.pageInfo.currentPageNumber - 1;
if (prevPageNumber > 0 && prevPageLink) {
let prevPageData = {link: prevPageLink};
if (!this.userPagesMap.get(prevPageNumber)) {
this.userPagesMap.set(prevPageNumber, prevPageData);
}
}
let nextPageLink = data.links.get('next');
let nextPageNumber = this.pageInfo.currentPageNumber + 1;
if (nextPageNumber) {
let lastPageData = {link: nextPageLink};
if (!this.userPagesMap.get(nextPageNumber)) {
this.userPagesMap.set(nextPageNumber, lastPageData);
}
}
}
/**
* Gets the pageInfo
* @returns {Object}
*/
getPagesInfo(): any {
return this.pageInfo;
}
/**
* Ask for loading the users in asynchronous way
* If there are no changes, it's not updated
* @param maxItems - the max number of items to return
* @param skipCount - the number of items to skip
* @returns {*} the promise
*/
fetchUsers(maxItems: number, skipCount: number): ng.IPromise<any> {
let promise = this.remoteUserAPI.getUsers({maxItems: maxItems, skipCount: skipCount}).$promise;
promise.then((data) => {
this.pageInfo.currentPageNumber = skipCount / maxItems + 1;
this._updateCurrentPageUsers(data.users);
this._updatePagesData(data);
});
return promise;
}
/**
* Ask for loading the users page in asynchronous way
* If there are no changes, it's not updated
* @param pageKey - the key of page ('first', 'prev', 'next', 'last' or '1', '2', '3' ...)
* @returns {*} the promise
*/
fetchUsersPage(pageKey): ng.IPromise<any> {
let deferred = this.$q.defer();
let pageNumber;
if ('first' === pageKey) {
pageNumber = 1;
} else if ('prev' === pageKey) {
pageNumber = this.pageInfo.currentPageNumber - 1;
} else if ('next' === pageKey) {
pageNumber = this.pageInfo.currentPageNumber + 1;
} else if ('last' === pageKey) {
pageNumber = this.pageInfo.countOfPages;
} else {
pageNumber = parseInt(pageKey, 10);
}
if (pageNumber < 1) {
pageNumber = 1;
} else if (pageNumber > this.pageInfo.countOfPages) {
pageNumber = this.pageInfo.countOfPages;
}
let pageData = this.userPagesMap.get(pageNumber);
if (pageData.link) {
this.pageInfo.currentPageNumber = pageNumber;
let promise = this.remoteUserAPI.getUsers(this._getPageParamByLink(pageData.link)).$promise;
promise.then((data) => {
this._updatePagesData(data);
pageData.users = data.users;
this._updateCurrentPage();
deferred.resolve(data);
}, (error) => {
if (error && error.status === 304) {
this._updateCurrentPage();
}
deferred.reject(error);
});
} else {
deferred.reject({data: {message: 'Error. No necessary link.'}});
}
return deferred.promise;
}
/**
* Gets the users
* @returns {Map}
*/
getUsersMap(): Map<string, any> {
return this.usersMap;
}
/**
* Performs user deleting by the given user ID.
* @param userId the user id
* @returns {*} the promise
*/
deleteUserById(userId: string): ng.IPromise<any> {
let promise = this.remoteUserAPI.removeUserById({userId: userId}).$promise;
// check if was OK or not
promise.then(() => {
//update users map
this.usersMap.delete(userId);//remove user
});
return promise;
}
/**
* Performs current user deletion.
* @returns {*} the promise
*/
deleteCurrentUser(): ng.IPromise<any> {
let userId = this.user.id;
let promise = this.remoteUserAPI.removeUserById({userId: userId}).$promise;
return promise;
}
/**
* Performs logout of current user.
* @returns {y.ResourceClass.prototype.$promise|*|$promise|T.$promise|S.$promise}
*/
logout(): ng.IPromise<any> {
let data = {token: this.$cookies['session-access-key']};
let promise = this.logoutAPI.save(data).$promise;
return promise;
}
/**
* Gets the user
* @return user
*/
getUser(): any {
return this.user;
}
/**
* Fetch the user
*/
fetchUser(): ng.IPromise<any> {
let promise = this.remoteUserAPI.get().$promise;
// check if if was OK or not
promise.then((user: che.IUser) => {
this.user = user;
});
return promise;
}
fetchUserId(userId: string): ng.IPromise<any> {
let promise = this.remoteUserAPI.findByID({userId: userId}).$promise;
let parsedResultPromise = promise.then((user: che.IUser) => {
this.useridMap.set(userId, user);
});
return parsedResultPromise;
}
getUserFromId(userId: string): any {
return this.useridMap.get(userId);
}
fetchUserByAlias(alias: string): ng.IPromise<any> {
let promise = this.remoteUserAPI.findByAlias({alias: alias}).$promise;
let parsedResultPromise = promise.then((user: che.IUser) => {
this.useridMap.set(user.id, user);
this.userAliasMap.set(alias, user);
});
return parsedResultPromise;
}
getUserByAlias(userAlias: string): any {
return this.userAliasMap.get(userAlias);
}
fetchUserByName(name: string): ng.IPromise<any> {
let promise = this.remoteUserAPI.findByName({name: name}).$promise;
let resultPromise = promise.then((user: che.IUser) => {
this.userNameMap.set(name, user);
return user;
}, (error: any) => {
if (error.status === 304) {
return this.userNameMap.get(name);
}
return this.$q.reject(error);
});
return resultPromise;
}
getUserByName(name: string): any {
return this.userNameMap.get(name);
}
setPassword(password: any): ng.IPromise<any> {
return this.remoteUserAPI.setPassword('password=' + password).$promise;
}
}

View File

@ -0,0 +1,275 @@
/*
* Copyright (c) 2015-2017 Codenvy, S.A.
* 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:
* Codenvy, S.A. - initial API and implementation
*/
'use strict';
import {CheUser} from './che-user.factory';
import {CheAPIBuilder} from './builder/che-api-builder.factory';
import {CheHttpBackend} from './test/che-http-backend';
/**
* Test of the Codenvy User API
*/
describe('CheUser', () => {
/**
* User Factory for the test
*/
let factory;
/**
* API builder.
*/
let apiBuilder;
/**
* Backend for handling http operations
*/
let httpBackend;
/**
* Che backend
*/
let cheBackend;
/**
* setup module
*/
beforeEach(angular.mock.module('userDashboard'));
/**
* Inject factory and http backend
*/
beforeEach(inject((cheUser: CheUser, cheAPIBuilder: CheAPIBuilder, cheHttpBackend: CheHttpBackend) => {
factory = cheUser;
apiBuilder = cheAPIBuilder;
cheBackend = cheHttpBackend;
httpBackend = cheHttpBackend.getHttpBackend();
}));
/**
* Check assertion after the test
*/
afterEach(() => {
httpBackend.verifyNoOutstandingExpectation();
httpBackend.verifyNoOutstandingRequest();
});
/**
* Check that we're able to fetch user data
*/
it('Fetch user', () => {
// setup tests objects
let userId = 'idTestUser';
let email = 'eclipseCodenvy@eclipse.org';
let testUser = apiBuilder.getUserBuilder().withId(userId).withEmail(email).build();
// providing request
// add test user on Http backend
cheBackend.setDefaultUser(testUser);
// setup backend for users
cheBackend.setup();
// fetch user
factory.fetchUser(true);
// expecting GETs
httpBackend.expectGET('/api/user');
// flush command
httpBackend.flush();
// now, check user
let user = factory.getUser();
// check id and email
expect(user.id).toEqual(userId);
expect(user.email).toEqual(email);
}
);
/**
* Check that we're able to fetch user data by id
*/
it('Fetch user by id', () => {
// setup tests objects
let userId = 'newIdTestUser';
let email = 'eclipseCodenvy@eclipse.org';
let testUser = apiBuilder.getUserBuilder().withId(userId).withEmail(email).build();
// providing request
// add test user on Http backend
cheBackend.addUserById(testUser);
// setup backend
cheBackend.setup();
// fetch user
factory.fetchUserId(userId);
// expecting GETs
httpBackend.expectGET('/api/user/' + userId);
// flush command
httpBackend.flush();
// now, check user
let user = factory.getUserFromId(userId);
// check id and email
expect(user.id).toEqual(userId);
expect(user.email).toEqual(email);
}
);
/**
* Check that we're able to fetch user data by email
*/
it('Fetch user by alias', () => {
// setup tests objects
let userId = 'testUser';
let email = 'eclipseCodenvy@eclipse.org';
let testUser = apiBuilder.getUserBuilder().withId(userId).withEmail(email).build();
// providing request
// add test user on Http backend
cheBackend.addUserEmail(testUser);
// setup backend
cheBackend.setup();
// fetch user
factory.fetchUserByAlias(email);
// expecting GETs
httpBackend.expectGET('/api/user/find?email=' + email);
// flush command
httpBackend.flush();
// now, check user
let user = factory.getUserByAlias(email);
// check id and email
expect(user.id).toEqual(userId);
expect(user.email).toEqual(email);
}
);
/**
* Check that we're able to set attributes into profile
*/
it('Set password', () => {
// setup
let testPassword = 'newTestPassword';
// setup backend
cheBackend.setup();
// fetch profile
factory.setPassword(testPassword);
// expecting a POST
httpBackend.expectPOST('/api/user/password', 'password=' + testPassword);
// flush command
httpBackend.flush();
}
);
/**
* Check that we're able to create user
*/
it('Create user', () => {
let user = {
password: 'password12345',
email: 'eclipseCodenvy@eclipse.org',
name: 'testName'
};
// setup backend
cheBackend.setup();
// create user
factory.createUser(user.name, user.email, user.password);
// expecting a POST
httpBackend.expectPOST('/api/user', user);
// flush command
httpBackend.flush();
}
);
/**
* Gets user page object from response
*/
it('Gets user page object from response', () => {
let testUser_1 = apiBuilder.getUserBuilder().withId('testUser1Id').withEmail('testUser1@eclipse.org').build();
let testUser_2 = apiBuilder.getUserBuilder().withId('testUser2Id').withEmail('testUser2@eclipse.org').build();
let users = [testUser_1, testUser_2];
let test_link_1 = 'https://aio.codenvy-dev.com/api/admin/user?skipCount=0&maxItems=5';
let test_rel_1 = 'first';
let test_link_2 = 'https://aio.codenvy-dev.com/api/admin/user?skipCount=20&maxItems=5';
let test_rel_2 = 'last';
let test_link_3 = 'https://aio.codenvy-dev.com/api/admin/user?skipCount=5&maxItems=5';
let test_rel_3 = 'next';
let headersLink = '\<' + test_link_1 + '\>' + '; rel="' + test_rel_1 + '",' +
'\<' + test_link_2 + '\>' + '; rel="' + test_rel_2 + '",' +
'\<' + test_link_3 + '\>' + '; rel="' + test_rel_3 + '"';
// setup backend
cheBackend.setup();
// gets page
let pageObject = factory._getPageFromResponse(users, headersLink);
// flush command
httpBackend.flush();
// check page users and links
expect(pageObject.users).toEqual(users);
expect(pageObject.links.get(test_rel_1)).toEqual(test_link_1);
expect(pageObject.links.get(test_rel_2)).toEqual(test_link_2);
expect(pageObject.links.get(test_rel_3)).toEqual(test_link_3);
}
);
/**
* Gets maxItems and skipCount from link params
*/
it('Gets maxItems and skipCount from link params', () => {
let skipCount = 20;
let maxItems = 5;
let test_link = 'https://aio.codenvy-dev.com/api/admin/user?skipCount=' + skipCount + '&maxItems=' + maxItems;
// setup backend
cheBackend.setup();
// gets page
let pageParams = factory._getPageParamByLink(test_link);
// flush command
httpBackend.flush();
// check page users and links
expect(parseInt(pageParams.maxItems, 10)).toEqual(maxItems);
expect(parseInt(pageParams.skipCount, 10)).toEqual(skipCount);
}
);
});

View File

@ -0,0 +1,15 @@
/*
* Copyright (c) 2015-2017 Codenvy, S.A.
* 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:
* Codenvy, S.A. - initial API and implementation
*/
declare namespace che.namespace {
}

View File

@ -31,6 +31,14 @@ export class CheHttpBackend {
private defaultProfilePrefs: any;
private defaultBranding: any;
private defaultPreferences: any;
private defaultUser: che.IUser;
private userIdMap: Map<string, che.IUser>;
private userEmailMap: Map<string, che.IUser>;
private factoriesMap: Map<string, che.IFactory>;
private pageMaxItem: number;
private pageSkipCount: number;
private isAutoSnapshot: boolean = false;
private isAutoRestore: boolean = false;
@ -51,6 +59,13 @@ export class CheHttpBackend {
this.workspaceAgentMap = new Map();
this.stacks = [];
this.defaultUser = {};
this.userIdMap = new Map();
this.userEmailMap = new Map();
this.factoriesMap = new Map();
this.pageMaxItem = 5;
this.pageSkipCount = 0;
this.defaultProfile = cheAPIBuilder.getProfileBuilder().withId('idDefaultUser').withEmail('eclipseChe@eclipse.org').withFirstName('FirstName').withLastName('LastName').build();
this.defaultProfilePrefs = {};
this.defaultBranding = {};
@ -113,6 +128,27 @@ export class CheHttpBackend {
this.httpBackend.when('POST', '/api/analytics/log/session-usage').respond(200, {});
// change password
this.httpBackend.when('POST', '/api/user/password').respond(() => {
return [200, {success: true, errors: []}];
});
// create new user
this.httpBackend.when('POST', '/api/user').respond(() => {
return [200, {success: true, errors: []}];
});
this.httpBackend.when('GET', '/api/user').respond(this.defaultUser);
let userIdKeys = this.userIdMap.keys();
for (let key of userIdKeys) {
this.httpBackend.when('GET', '/api/user/' + key).respond(this.userIdMap.get(key));
}
let userEmailKeys = this.userEmailMap.keys();
for (let key of userEmailKeys) {
this.httpBackend.when('GET', '/api/user/find?email=' + key).respond(this.userEmailMap.get(key));
}
}
/**
@ -387,4 +423,84 @@ export class CheHttpBackend {
this.httpBackend.when('POST', this.workspaceAgentMap.get(workspaceId) + '/svn/info?workspaceId=' + workspaceId).respond(svnInfo);
}
/**
* Setup Backend for factories
*/
factoriesBackendSetup() {
this.setup();
let allFactories = [];
let pageFactories = [];
let factoriesKeys = this.factoriesMap.keys();
for (let key of factoriesKeys) {
let factory = this.factoriesMap.get(key);
this.httpBackend.when('GET', '/api/factory/' + factory.id).respond(factory);
this.httpBackend.when('DELETE', '/api/factory/' + factory.id).respond(() => {
return [200, {success: true, errors: []}];
});
allFactories.push(factory);
}
if (this.defaultUser) {
this.httpBackend.when('GET', '/api/user').respond(this.defaultUser);
if (allFactories.length > this.pageSkipCount) {
if(allFactories.length > this.pageSkipCount + this.pageMaxItem) {
pageFactories = allFactories.slice(this.pageSkipCount, this.pageSkipCount + this.pageMaxItem);
} else {
pageFactories = allFactories.slice(this.pageSkipCount);
}
}
this.httpBackend.when('GET', '/api/factory/find?creator.userId=' + this.defaultUser.id + '&maxItems=' + this.pageMaxItem + '&skipCount=' + this.pageSkipCount).respond(pageFactories);
}
}
/**
* Add the given factory
* @param factory
*/
addUserFactory(factory) {
this.factoriesMap.set(factory.id, factory);
}
/**
* Sets max objects on response
* @param pageMaxItem
*/
setPageMaxItem(pageMaxItem) {
this.pageMaxItem = pageMaxItem;
}
/**
* Sets skip count of values
* @param pageSkipCount
*/
setPageSkipCount(pageSkipCount) {
this.pageSkipCount = pageSkipCount;
}
/**
* Add the given user
* @param user
*/
setDefaultUser(user) {
this.defaultUser = user;
}
/**
* Add the given user to userIdMap
* @param user
*/
addUserById(user) {
this.userIdMap.set(user.id, user);
}
/**
* Add the given user to userEmailMap
* @param user
*/
addUserEmail(user) {
this.userEmailMap.set(user.email, user);
}
}

View File

@ -227,4 +227,22 @@ declare namespace _che {
label: string;
location: string;
}
export interface IUser {
id: string;
name: string;
email: string;
aliases: Array<string>;
}
export interface IFactory {
id: string;
name?: string;
v: string;
workspace: IWorkspaceConfig;
creator: any;
ide?: any;
button?: any;
policies?: any;
}
}

View File

@ -69,6 +69,10 @@
<groupId>org.eclipse.che.core</groupId>
<artifactId>che-core-api-factory-shared</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.che.core</groupId>
<artifactId>che-core-api-git-shared</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.che.core</groupId>
<artifactId>che-core-api-machine-shared</artifactId>

View File

@ -943,4 +943,109 @@ public interface CoreLocalizationConstant extends Messages {
@Key("authentication.dialog.rejected.by.user")
String authenticationDialogRejectedByUser();
/* Factories */
@Key("projects.import.configuring.cloning")
String cloningSource();
@Key("create.factory.action.title")
String createFactoryActionTitle();
@Key("create.factory.already.exist")
String createFactoryAlreadyExist();
@Key("create.factory.unable.create.from.current.workspace")
String createFactoryFromCurrentWorkspaceFailed();
@Key("create.factory.form.title")
String createFactoryTitle();
@Key("create.factory.label.name")
String createFactoryName();
@Key("create.factory.label.link")
String createFactoryLink();
@Key("create.factory.button.create")
String createFactoryButton();
@Key("create.factory.button.close")
String createFactoryButtonClose();
@Key("create.factory.configure.button.tooltip")
String createFactoryConfigureTooltip();
@Key("create.factory.launch.button.tooltip")
String createFactoryLaunchTooltip();
@Key("import.config.view.name")
String importFromConfigurationName();
@Key("import.config.view.description")
String importFromConfigurationDescription();
@Key("project.import.configured.cloned")
String clonedSource(String projectName);
@Key("import.config.form.button.import")
String importButton();
@Key("import.config.view.title")
String importFromConfigurationTitle();
@Key("import.config.form.prompt")
String configFileTitle();
@Key("project.already.imported")
String projectAlreadyImported(String projectName);
@Key("project.import.cloned.with.checkout")
String clonedSourceWithCheckout(String projectName, String repoName, String ref, String branch);
@Key("project.import.cloned.with.checkout.start.point")
String clonedWithCheckoutOnStartPoint(String projectName, String repoName, String startPoint, String branch);
@Key("project.import.configuring.cloning")
String cloningSource(String projectName);
@Key("project.import.ssh.key.upload.failed.title")
String cloningSourceSshKeyUploadFailedTitle();
@Key("project.import.ssh.key.upload.failed.text")
String cloningSourcesSshKeyUploadFailedText();
@Key("message.ssh.key.not.found.text")
String acceptSshNotFoundText();
@Key("project.import.cloning.failed.without.start.point")
String cloningSourceWithCheckoutFailed(String branch, String repoName);
@Key("project.import.cloning.failed.with.start.point")
String cloningSourceCheckoutFailed(String project, String branch);
@Key("project.import.cloning.failed.title")
String cloningSourceFailedTitle(String projectName);
@Key("project.import.configuring.failed")
String configuringSourceFailed(String projectName);
@Key("welcome.preferences.title")
String welcomePreferencesTitle();
@Key("export.config.view.name")
String exportConfigName();
@Key("export.config.view.description")
String exportConfigDescription();
@Key("export.config.error.message")
String exportConfigErrorMessage();
@Key("export.config.dialog.not.under.vcs.title")
String exportConfigDialogNotUnderVcsTitle();
@Key("export.config.dialog.not.under.vcs.text")
String exportConfigDialogNotUnderVcsText();
}

View File

@ -48,7 +48,7 @@ import org.eclipse.che.ide.context.AppContextImpl;
import org.eclipse.che.ide.debug.DebugApiModule;
import org.eclipse.che.ide.editor.EditorApiModule;
import org.eclipse.che.ide.editor.preferences.EditorPreferencesModule;
import org.eclipse.che.ide.factory.FactoryApiModule;
import org.eclipse.che.ide.factory.inject.FactoryGinModule;
import org.eclipse.che.ide.filetypes.FileTypeApiModule;
import org.eclipse.che.ide.keybinding.KeyBindingManager;
import org.eclipse.che.ide.machine.MachineApiModule;
@ -110,7 +110,7 @@ public class CoreGinModule extends AbstractGinModule {
install(new ProjectApiModule());
install(new ProjectImportModule());
install(new OAuthApiModule());
install(new FactoryApiModule());
install(new FactoryGinModule());
// configure miscellaneous core components
bind(StandardComponentInitializer.class).in(Singleton.class);

View File

@ -0,0 +1,86 @@
/*******************************************************************************
* Copyright (c) 2012-2017 Codenvy, S.A.
* 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:
* Codenvy, S.A. - initial API and implementation
*******************************************************************************/
package org.eclipse.che.ide.factory;
import com.google.gwt.core.client.Callback;
import com.google.gwt.core.client.ScriptInjector;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import org.eclipse.che.ide.api.action.ActionManager;
import org.eclipse.che.ide.api.action.DefaultActionGroup;
import org.eclipse.che.ide.api.extension.Extension;
import org.eclipse.che.ide.factory.accept.AcceptFactoryHandler;
import org.eclipse.che.ide.factory.action.CreateFactoryAction;
import org.eclipse.che.ide.factory.json.ImportFromConfigAction;
import org.eclipse.che.ide.factory.welcome.OpenWelcomePageAction;
import static com.google.gwt.core.client.ScriptInjector.TOP_WINDOW;
import static org.eclipse.che.ide.api.action.IdeActions.GROUP_PROJECT;
import static org.eclipse.che.ide.api.action.IdeActions.GROUP_WORKSPACE;
/**
* @author Vladyslav Zhukovskii
*/
@Singleton
@Extension(title = "Factory", version = "3.0.0")
public class FactoryExtension {
@Inject
public FactoryExtension(AcceptFactoryHandler acceptFactoryHandler,
ActionManager actionManager,
FactoryResources resources,
CreateFactoryAction configureFactoryAction,
ImportFromConfigAction importFromConfigAction,
OpenWelcomePageAction openWelcomePageAction) {
acceptFactoryHandler.process();
/*
* Inject resources and js
*/
ScriptInjector.fromUrl("https://apis.google.com/js/client:plusone.js?parsetags=explicit")
.setWindow(TOP_WINDOW)
.inject();
ScriptInjector.fromUrl("https://connect.facebook.net/en_US/sdk.js")
.setWindow(TOP_WINDOW)
.setCallback(new Callback<Void, Exception>() {
@Override
public void onSuccess(Void result) {
init();
}
@Override
public void onFailure(Exception reason) {
}
private native void init() /*-{
$wnd.FB.init({
appId: "318167898391385",
xfbml: true,
version: "v2.1"
});
}-*/;
}).inject();
resources.factoryCSS().ensureInjected();
DefaultActionGroup projectGroup = (DefaultActionGroup)actionManager.getAction(GROUP_PROJECT);
DefaultActionGroup workspaceGroup = (DefaultActionGroup)actionManager.getAction(GROUP_WORKSPACE);
actionManager.registerAction("openWelcomePage", openWelcomePageAction);
actionManager.registerAction("importProjectFromCodenvyConfigAction", importFromConfigAction);
actionManager.registerAction("configureFactoryAction", configureFactoryAction);
projectGroup.add(importFromConfigAction);
workspaceGroup.add(configureFactoryAction);
}
}

View File

@ -0,0 +1,54 @@
/*******************************************************************************
* Copyright (c) 2012-2017 Codenvy, S.A.
* 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:
* Codenvy, S.A. - initial API and implementation
*******************************************************************************/
package org.eclipse.che.ide.factory;
import com.google.gwt.resources.client.ClientBundle;
import com.google.gwt.resources.client.CssResource;
import org.eclipse.che.ide.ui.Styles;
import org.vectomatic.dom.svg.ui.SVGResource;
/**
* Factory extension resources (css styles, images).
*
* @author Ann Shumilova
* @author Anton Korneta
*/
public interface FactoryResources extends ClientBundle {
interface FactoryCSS extends CssResource, Styles {
String label();
String createFactoryButton();
String labelErrorPosition();
}
interface Style extends CssResource {
String launchIcon();
String configureIcon();
}
@Source({"Factory.css", "org/eclipse/che/ide/api/ui/style.css", "org/eclipse/che/ide/ui/Styles.css"})
FactoryCSS factoryCSS();
@Source("export-config.svg")
SVGResource exportConfig();
@Source("import-config.svg")
SVGResource importConfig();
@Source("execute.svg")
SVGResource execute();
@Source("cog-icon.svg")
SVGResource configure();
}

View File

@ -0,0 +1,134 @@
/*******************************************************************************
* Copyright (c) 2012-2017 Codenvy, S.A.
* 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:
* Codenvy, S.A. - initial API and implementation
*******************************************************************************/
package org.eclipse.che.ide.factory.accept;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.inject.Singleton;
import com.google.web.bindery.event.shared.EventBus;
import org.eclipse.che.api.factory.shared.dto.FactoryDto;
import org.eclipse.che.api.factory.shared.dto.IdeActionDto;
import org.eclipse.che.api.factory.shared.dto.IdeDto;
import org.eclipse.che.ide.CoreLocalizationConstant;
import org.eclipse.che.ide.api.action.ActionManager;
import org.eclipse.che.ide.api.app.AppContext;
import org.eclipse.che.ide.api.factory.FactoryAcceptedEvent;
import org.eclipse.che.ide.api.machine.events.WsAgentStateEvent;
import org.eclipse.che.ide.api.machine.events.WsAgentStateHandler;
import org.eclipse.che.ide.api.notification.NotificationManager;
import org.eclipse.che.ide.api.notification.StatusNotification;
import org.eclipse.che.ide.factory.utils.FactoryProjectImporter;
import javax.inject.Inject;
import static org.eclipse.che.ide.api.notification.StatusNotification.DisplayMode.NOT_EMERGE_MODE;
/**
* @author Sergii Leschenko
* @author Anton Korneta
*/
@Singleton
public class AcceptFactoryHandler {
private final CoreLocalizationConstant localizationConstant;
private final FactoryProjectImporter factoryProjectImporter;
private final EventBus eventBus;
private final AppContext appContext;
private final ActionManager actionManager;
private final NotificationManager notificationManager;
private StatusNotification notification;
private boolean isImportingStarted;
@Inject
public AcceptFactoryHandler(CoreLocalizationConstant localizationConstant,
FactoryProjectImporter factoryProjectImporter,
EventBus eventBus,
AppContext appContext,
ActionManager actionManager,
NotificationManager notificationManager) {
this.factoryProjectImporter = factoryProjectImporter;
this.localizationConstant = localizationConstant;
this.eventBus = eventBus;
this.appContext = appContext;
this.actionManager = actionManager;
this.notificationManager = notificationManager;
}
/**
* Accepts factory if it is present in context of application
*/
public void process() {
final FactoryDto factory;
if ((factory = appContext.getFactory()) == null) {
return;
}
eventBus.addHandler(WsAgentStateEvent.TYPE, new WsAgentStateHandler() {
@Override
public void onWsAgentStarted(final WsAgentStateEvent event) {
if (isImportingStarted) {
return;
}
isImportingStarted = true;
notification = notificationManager
.notify(localizationConstant.cloningSource(), StatusNotification.Status.PROGRESS, NOT_EMERGE_MODE);
performOnAppLoadedActions(factory);
startImporting(factory);
}
@Override
public void onWsAgentStopped(WsAgentStateEvent event) {
}
});
}
private void startImporting(final FactoryDto factory) {
factoryProjectImporter.startImporting(factory,
new AsyncCallback<Void>() {
@Override
public void onSuccess(Void result) {
notification.setStatus(StatusNotification.Status.SUCCESS);
notification.setContent(localizationConstant.cloningSource());
performOnProjectsLoadedActions(factory);
}
@Override
public void onFailure(Throwable throwable) {
notification.setStatus(StatusNotification.Status.FAIL);
notification.setContent(throwable.getMessage());
}
});
}
private void performOnAppLoadedActions(final FactoryDto factory) {
final IdeDto ide = factory.getIde();
if (ide == null || ide.getOnAppLoaded() == null) {
return;
}
for (IdeActionDto action : ide.getOnAppLoaded().getActions()) {
actionManager.performAction(action.getId(), action.getProperties());
}
}
private void performOnProjectsLoadedActions(final FactoryDto factory) {
final IdeDto ide = factory.getIde();
if (ide == null || ide.getOnProjectsLoaded() == null) {
eventBus.fireEvent(new FactoryAcceptedEvent(factory));
return;
}
for (IdeActionDto action : ide.getOnProjectsLoaded().getActions()) {
actionManager.performAction(action.getId(), action.getProperties());
}
eventBus.fireEvent(new FactoryAcceptedEvent(factory));
}
}

View File

@ -0,0 +1,47 @@
/*******************************************************************************
* Copyright (c) 2012-2017 Codenvy, S.A.
* 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:
* Codenvy, S.A. - initial API and implementation
*******************************************************************************/
package org.eclipse.che.ide.factory.action;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import org.eclipse.che.ide.CoreLocalizationConstant;
import org.eclipse.che.ide.api.action.AbstractPerspectiveAction;
import org.eclipse.che.ide.api.action.ActionEvent;
import org.eclipse.che.ide.factory.configure.CreateFactoryPresenter;
import javax.validation.constraints.NotNull;
import java.util.Collections;
/**
* @author Anton Korneta
*/
@Singleton
public class CreateFactoryAction extends AbstractPerspectiveAction {
private final CreateFactoryPresenter presenter;
@Inject
public CreateFactoryAction(CreateFactoryPresenter presenter,
CoreLocalizationConstant localizationConstant) {
super(Collections.singletonList("Project Perspective"), localizationConstant.createFactoryActionTitle(), null, null, null);
this.presenter = presenter;
}
@Override
public void actionPerformed(ActionEvent e) {
presenter.showDialog();
}
@Override
public void updateInPerspective(@NotNull ActionEvent event) {
}
}

View File

@ -0,0 +1,117 @@
/*******************************************************************************
* Copyright (c) 2012-2017 Codenvy, S.A.
* 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:
* Codenvy, S.A. - initial API and implementation
*******************************************************************************/
package org.eclipse.che.ide.factory.configure;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import org.eclipse.che.api.core.rest.shared.dto.Link;
import org.eclipse.che.api.factory.shared.dto.FactoryDto;
import org.eclipse.che.api.promises.client.Operation;
import org.eclipse.che.api.promises.client.OperationException;
import org.eclipse.che.api.promises.client.PromiseError;
import org.eclipse.che.ide.CoreLocalizationConstant;
import org.eclipse.che.ide.api.app.AppContext;
import org.eclipse.che.ide.api.factory.FactoryServiceClient;
import org.eclipse.che.ide.util.Pair;
import java.util.Collections;
import java.util.List;
/**
* @author Anton Korneta
*/
@Singleton
public class CreateFactoryPresenter implements CreateFactoryView.ActionDelegate {
public static final String CONFIGURE_LINK = "/dashboard/#/factory/";
private final CreateFactoryView view;
private final AppContext appContext;
private final FactoryServiceClient factoryService;
private final CoreLocalizationConstant localizationConstant;
@Inject
public CreateFactoryPresenter(CreateFactoryView view,
AppContext appContext,
FactoryServiceClient factoryService,
CoreLocalizationConstant localizationConstant) {
this.view = view;
this.appContext = appContext;
this.factoryService = factoryService;
this.localizationConstant = localizationConstant;
view.setDelegate(this);
}
public void showDialog() {
view.showDialog();
}
@Override
public void onCreateClicked() {
final String factoryName = view.getFactoryName();
factoryService.getFactoryJson(appContext.getWorkspace().getId(), null)
.then(new Operation<FactoryDto>() {
@Override
public void apply(final FactoryDto factory) throws OperationException {
factoryService.findFactory(null, null, Collections.singletonList(Pair.of("name", factoryName)))
.then(saveFactory(factory, factoryName))
.catchError(logError());
}
})
.catchError(logError());
}
@Override
public void onFactoryNameChanged(String factoryName) {
view.enableCreateFactoryButton(isValidFactoryName(factoryName));
}
@Override
public void onCancelClicked() {
view.close();
}
private Operation<List<FactoryDto>> saveFactory(final FactoryDto factory, final String factoryName) {
return new Operation<List<FactoryDto>>() {
@Override
public void apply(List<FactoryDto> factories) throws OperationException {
if (!factories.isEmpty()) {
view.showFactoryNameError(localizationConstant.createFactoryAlreadyExist(), null);
} else {
factoryService.saveFactory(factory.withName(factoryName))
.then(new Operation<FactoryDto>() {
@Override
public void apply(FactoryDto factory) throws OperationException {
final Link link = factory.getLink("accept-named");
if (link != null) {
view.setAcceptFactoryLink(link.getHref());
}
view.setConfigureFactoryLink(CONFIGURE_LINK + factory.getId() + "/configure");
}
})
.catchError(logError());
}
}
};
}
private Operation<PromiseError> logError() {
return err -> view.showFactoryNameError(localizationConstant.createFactoryFromCurrentWorkspaceFailed(), err.getMessage());
}
private boolean isValidFactoryName(String name) {
if (name.length() == 0 || name.length() >= 125) {
return false;
}
view.hideFactoryNameError();
return true;
}
}

View File

@ -0,0 +1,63 @@
/*******************************************************************************
* Copyright (c) 2012-2017 Codenvy, S.A.
* 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:
* Codenvy, S.A. - initial API and implementation
*******************************************************************************/
package org.eclipse.che.ide.factory.configure;
import com.google.inject.ImplementedBy;
import org.eclipse.che.commons.annotation.Nullable;
import org.eclipse.che.ide.api.mvp.View;
import javax.validation.constraints.NotNull;
/**
* Representation of create factory popup.
*
* @author Anton Korneta
*/
@ImplementedBy(CreateFactoryViewImpl.class)
public interface CreateFactoryView extends View<CreateFactoryView.ActionDelegate> {
interface ActionDelegate {
/** Performs any actions appropriate in response to the user having pressed the Create button */
void onCreateClicked();
/** Performs any actions appropriate in response to the user having type into Factory name input */
void onFactoryNameChanged(String factoryName);
/** Performs any actions appropriate in response to the user having pressed the Cancel button. */
void onCancelClicked();
}
/** Preforms closing create factory popup */
void close();
/** Preforms showing create factory popup */
void showDialog();
/** Gets factory name from input */
String getFactoryName();
/** Set accept factory link */
void setAcceptFactoryLink(@NotNull String acceptLink);
/** Set accept factory link */
void setConfigureFactoryLink(@NotNull String configureLink);
/** Set enable create factory button */
void enableCreateFactoryButton(boolean enabled);
/** Shows error if factory name invalid */
void showFactoryNameError(@NotNull String labelMessage, @Nullable String tooltipMessage);
/** Hide error of factory name is valid */
void hideFactoryNameError();
}

View File

@ -0,0 +1,206 @@
/*******************************************************************************
* Copyright (c) 2012-2017 Codenvy, S.A.
* 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:
* Codenvy, S.A. - initial API and implementation
*******************************************************************************/
package org.eclipse.che.ide.factory.configure;
import com.google.common.base.Strings;
import com.google.gwt.event.dom.client.KeyCodes;
import com.google.gwt.event.dom.client.KeyUpEvent;
import com.google.gwt.regexp.shared.RegExp;
import com.google.gwt.uibinder.client.UiBinder;
import com.google.gwt.uibinder.client.UiField;
import com.google.gwt.uibinder.client.UiHandler;
import com.google.gwt.user.client.ui.Anchor;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.TextBox;
import com.google.gwt.user.client.ui.Widget;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import org.eclipse.che.commons.annotation.Nullable;
import org.eclipse.che.ide.CoreLocalizationConstant;
import org.eclipse.che.ide.factory.FactoryResources;
import org.eclipse.che.ide.ui.Tooltip;
import org.eclipse.che.ide.ui.menu.PositionController;
import org.eclipse.che.ide.ui.window.Window;
import org.eclipse.che.ide.ui.zeroclipboard.ClipboardButtonBuilder;
import javax.validation.constraints.NotNull;
import static com.google.gwt.dom.client.Style.Unit;
/**
* @author Anton Korneta
*/
@Singleton
public class CreateFactoryViewImpl extends Window implements CreateFactoryView {
private static final RegExp FACTORY_NAME_PATTERN = RegExp.compile("[^A-Za-z0-9_-]");
interface FactoryViewImplUiBinder extends UiBinder<Widget, CreateFactoryViewImpl> {
}
private final FactoryResources factoryResources;
private ActionDelegate delegate;
@UiField
FactoryResources.Style style;
@UiField
TextBox factoryName;
@UiField
TextBox factoryLink;
@UiField
Label factoryNameLabel;
@UiField
Label factoryLinkLabel;
@UiField
Label factoryNameErrorLabel;
@UiField
Button createFactoryButton;
@UiField
FlowPanel upperPanel;
@UiField
FlowPanel lowerPanel;
@UiField
FlowPanel createFactoryPanel;
@UiField
Anchor launch;
@UiField
Anchor configure;
private Tooltip labelsErrorTooltip;
@Inject
protected CreateFactoryViewImpl(FactoryViewImplUiBinder uiBinder,
CoreLocalizationConstant locale,
FactoryResources factoryResources,
ClipboardButtonBuilder buttonBuilder) {
this.factoryResources = factoryResources;
setTitle(locale.createFactoryTitle());
setWidget(uiBinder.createAndBindUi(this));
factoryNameLabel.setText(locale.createFactoryName());
factoryLinkLabel.setText(locale.createFactoryLink());
configure.getElement().insertFirst(factoryResources.configure().getSvg().getElement());
launch.getElement().insertFirst(factoryResources.execute().getSvg().getElement());
launch.addStyleName(style.launchIcon());
configure.addStyleName(style.configureIcon());
createFactoryButton.setEnabled(false);
Button cancelButton =
createButton(locale.createFactoryButtonClose(), "git-remotes-pull-cancel", event -> delegate.onCancelClicked());
createFactoryButton.addClickHandler(clickEvent -> delegate.onCreateClicked());
cancelButton.ensureDebugId("projectReadOnlyGitUrl-btnClose");
addButtonToFooter(cancelButton);
getWidget().getElement().getStyle().setPadding(0, Unit.PX);
buttonBuilder.withResourceWidget(factoryLink).build();
factoryLink.setReadOnly(true);
final Tooltip launchFactoryTooltip = Tooltip.create((elemental.dom.Element)launch.getElement(),
PositionController.VerticalAlign.TOP,
PositionController.HorizontalAlign.MIDDLE,
locale.createFactoryLaunchTooltip());
launchFactoryTooltip.setShowDelayDisabled(false);
final Tooltip configureFactoryTooltip = Tooltip.create((elemental.dom.Element)configure.getElement(),
PositionController.VerticalAlign.TOP,
PositionController.HorizontalAlign.MIDDLE,
locale.createFactoryConfigureTooltip());
configureFactoryTooltip.setShowDelayDisabled(false);
factoryName.getElement().setAttribute("placeholder", "new-factory-name");
}
@Override
public void setDelegate(ActionDelegate delegate) {
this.delegate = delegate;
}
@Override
public void showDialog() {
clear();
this.show();
}
@Override
public String getFactoryName() {
return factoryName.getText();
}
@Override
public void setAcceptFactoryLink(String acceptLink) {
factoryLink.setText(acceptLink);
launch.getElement().setAttribute("target", "_blank");
launch.setHref(acceptLink);
}
@Override
public void setConfigureFactoryLink(String configureLink) {
configure.getElement().setAttribute("target", "_blank");
configure.setHref(configureLink);
}
@Override
public void enableCreateFactoryButton(boolean enabled) {
createFactoryButton.setEnabled(enabled);
}
@Override
public void showFactoryNameError(@NotNull String labelMessage, @Nullable String tooltipMessage) {
factoryName.addStyleName(factoryResources.factoryCSS().inputError());
factoryNameErrorLabel.setText(labelMessage);
if (labelsErrorTooltip != null) {
labelsErrorTooltip.destroy();
}
if (!Strings.isNullOrEmpty(tooltipMessage)) {
labelsErrorTooltip = Tooltip.create((elemental.dom.Element)factoryNameErrorLabel.getElement(),
PositionController.VerticalAlign.TOP,
PositionController.HorizontalAlign.MIDDLE,
tooltipMessage);
labelsErrorTooltip.setShowDelayDisabled(false);
}
}
@Override
public void hideFactoryNameError() {
factoryName.removeStyleName(factoryResources.factoryCSS().inputError());
factoryNameErrorLabel.setText("");
}
@Override
public void close() {
this.hide();
}
@UiHandler({"factoryName"})
public void onProjectNameChanged(KeyUpEvent event) {
if (event.getNativeKeyCode() == KeyCodes.KEY_ENTER
&& createFactoryButton.isEnabled()) {
delegate.onCreateClicked();
} else {
String name = factoryName.getValue();
if (!Strings.isNullOrEmpty(name) && FACTORY_NAME_PATTERN.test(name)) {
name = name.replaceAll("[^A-Za-z0-9_]", "-");
factoryName.setValue(name);
}
delegate.onFactoryNameChanged(name);
}
}
private void clear() {
launch.getElement().removeAttribute("href");
configure.getElement().removeAttribute("href");
createFactoryButton.setEnabled(false);
factoryName.removeStyleName(factoryResources.factoryCSS().inputError());
factoryNameErrorLabel.setText("");
factoryName.setText("");
factoryLink.setText("");
}
}

View File

@ -0,0 +1,105 @@
<!--
Copyright (c) 2012-2017 Codenvy, S.A.
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:
Codenvy, S.A. - initial API and implementation
-->
<!DOCTYPE ui:UiBinder SYSTEM "http://dl.google.com/gwt/DTD/xhtml.ent">
<ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder'
xmlns:g='urn:import:com.google.gwt.user.client.ui'>
<ui:with field='res' type='org.eclipse.che.ide.factory.FactoryResources'/>
<ui:with field='locale' type='org.eclipse.che.ide.CoreLocalizationConstant'/>
<ui:style type='org.eclipse.che.ide.factory.FactoryResources.Style'>
@eval tabBorderColor org.eclipse.che.ide.api.theme.Style.theme.tabBorderColor();
.border {
border-bottom: 1px solid tabBorderColor;
}
.topPanel {
padding: 16px 16px 22px 16px;
}
.lowerPanel {
padding: 16px;
}
.centerAlign {
display: flex;
align-items: center;
}
.iconContainer {
padding-left: 15px;
display: flex;
justify-content: space-around;
width: 20%;
}
.launchIcon svg {
width: 18px;
height: 18px;
fill: #9B9B9B;
}
.launchIcon:hover svg {
fill: rgba(0, 182, 142, 0.91);
cursor: pointer;
}
.configureIcon svg {
margin-top: 1px;
width: 17px;
height: 17px;
fill: #9B9B9B;
}
.configureIcon:hover svg {
fill: #E0E0E0;
cursor: pointer;
}
.input {
width: literal("calc(100% - 95px)");
}
.inputContainer {
width: literal("calc(100% - 80px)");
}
.inputReadOnly {
float: left;
width: literal("calc(100% - 32px)");
}
.inputReadOnly + div {
margin: 0;
}
</ui:style>
<g:FlowPanel width="340px" ui:field="createFactoryPanel">
<g:FlowPanel ui:field="upperPanel" addStyleNames="{style.topPanel} {style.border}">
<g:Label ui:field="factoryNameLabel" addStyleNames="{res.factoryCSS.label}"/>
<g:TextBox ui:field="factoryName" addStyleNames="{style.input}"/>
<g:Button ui:field="createFactoryButton" text="{locale.createFactoryButton}"
addStyleNames="{style.border} {res.factoryCSS.createFactoryButton}"/>
<g:Label ui:field="factoryNameErrorLabel" wordWrap="true" addStyleNames="{res.factoryCSS.labelErrorPosition} {style.input}"/>
</g:FlowPanel>
<g:FlowPanel ui:field="lowerPanel" addStyleNames="{style.lowerPanel}">
<g:Label ui:field="factoryLinkLabel" addStyleNames="{res.factoryCSS.label}"/>
<g:FlowPanel addStyleNames="{style.centerAlign}">
<g:FlowPanel addStyleNames="{style.inputContainer}">
<g:TextBox ui:field="factoryLink" addStyleNames="{style.inputReadOnly}"/>
</g:FlowPanel>
<g:FlowPanel addStyleNames="{style.iconContainer}">
<g:Anchor ui:field="configure"/>
<g:Anchor ui:field="launch"/>
</g:FlowPanel>
</g:FlowPanel>
</g:FlowPanel>
</g:FlowPanel>
</ui:UiBinder>

View File

@ -0,0 +1,44 @@
/*******************************************************************************
* Copyright (c) 2012-2017 Codenvy, S.A.
* 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:
* Codenvy, S.A. - initial API and implementation
*******************************************************************************/
package org.eclipse.che.ide.factory.inject;
import com.google.gwt.inject.client.AbstractGinModule;
import com.google.gwt.inject.client.multibindings.GinMultibinder;
import org.eclipse.che.ide.api.factory.FactoryServiceClient;
import org.eclipse.che.ide.api.preferences.PreferencePagePresenter;
import org.eclipse.che.ide.factory.FactoryServiceClientImpl;
import org.eclipse.che.ide.factory.json.ImportFromConfigView;
import org.eclipse.che.ide.factory.json.ImportFromConfigViewImpl;
import org.eclipse.che.ide.factory.welcome.GreetingPartView;
import org.eclipse.che.ide.factory.welcome.GreetingPartViewImpl;
import org.eclipse.che.ide.factory.welcome.preferences.ShowWelcomePreferencePagePresenter;
import org.eclipse.che.ide.factory.welcome.preferences.ShowWelcomePreferencePageView;
import org.eclipse.che.ide.factory.welcome.preferences.ShowWelcomePreferencePageViewImpl;
import javax.inject.Singleton;
/**
* @author Vladyslav Zhukovskii
*/
public class FactoryGinModule extends AbstractGinModule {
@Override
protected void configure() {
bind(GreetingPartView.class).to(GreetingPartViewImpl.class).in(Singleton.class);
bind(ImportFromConfigView.class).to(ImportFromConfigViewImpl.class).in(Singleton.class);
bind(ShowWelcomePreferencePageView.class).to(ShowWelcomePreferencePageViewImpl.class).in(Singleton.class);
bind(FactoryServiceClient.class).to(FactoryServiceClientImpl.class).in(Singleton.class);
final GinMultibinder<PreferencePagePresenter> prefBinder = GinMultibinder.newSetBinder(binder(), PreferencePagePresenter.class);
prefBinder.addBinding().to(ShowWelcomePreferencePagePresenter.class);
}
}

View File

@ -0,0 +1,47 @@
/*******************************************************************************
* Copyright (c) 2012-2017 Codenvy, S.A.
* 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:
* Codenvy, S.A. - initial API and implementation
*******************************************************************************/
package org.eclipse.che.ide.factory.json;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import org.eclipse.che.ide.CoreLocalizationConstant;
import org.eclipse.che.ide.api.action.Action;
import org.eclipse.che.ide.api.action.ActionEvent;
import org.eclipse.che.ide.factory.FactoryResources;
/**
* @author Sergii Leschenko
*/
@Singleton
public class ImportFromConfigAction extends Action {
private final ImportFromConfigPresenter presenter;
@Inject
public ImportFromConfigAction(final ImportFromConfigPresenter presenter,
CoreLocalizationConstant locale,
FactoryResources resources) {
super(locale.importFromConfigurationName(), locale.importFromConfigurationDescription(), null, resources.importConfig());
this.presenter = presenter;
}
/** {@inheritDoc} */
@Override
public void actionPerformed(ActionEvent e) {
presenter.showDialog();
}
/** {@inheritDoc} */
@Override
public void update(ActionEvent event) {
}
}

View File

@ -0,0 +1,105 @@
/*******************************************************************************
* Copyright (c) 2012-2017 Codenvy, S.A.
* 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:
* Codenvy, S.A. - initial API and implementation
*******************************************************************************/
package org.eclipse.che.ide.factory.json;
import com.google.gwt.json.client.JSONException;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.inject.Inject;
import org.eclipse.che.api.factory.shared.dto.FactoryDto;
import org.eclipse.che.ide.CoreLocalizationConstant;
import org.eclipse.che.ide.api.notification.NotificationManager;
import org.eclipse.che.ide.api.notification.StatusNotification;
import org.eclipse.che.ide.dto.DtoFactory;
import org.eclipse.che.ide.factory.utils.FactoryProjectImporter;
import static org.eclipse.che.ide.api.notification.StatusNotification.DisplayMode.NOT_EMERGE_MODE;
/**
* Imports project from factory.json file
*
* @author Sergii Leschenko
*/
public class ImportFromConfigPresenter implements ImportFromConfigView.ActionDelegate {
private final CoreLocalizationConstant localizationConstant;
private final ImportFromConfigView view;
private final NotificationManager notificationManager;
private final DtoFactory dtoFactory;
private final FactoryProjectImporter projectImporter;
private final AsyncCallback<Void> importerCallback;
private StatusNotification notification;
@Inject
public ImportFromConfigPresenter(final CoreLocalizationConstant localizationConstant,
FactoryProjectImporter projectImporter,
ImportFromConfigView view,
NotificationManager notificationManager,
DtoFactory dtoFactory) {
this.localizationConstant = localizationConstant;
this.notificationManager = notificationManager;
this.view = view;
this.dtoFactory = dtoFactory;
this.view.setDelegate(this);
this.projectImporter = projectImporter;
importerCallback = new AsyncCallback<Void>() {
@Override
public void onSuccess(Void result) {
notification.setContent(localizationConstant.clonedSource(null));
notification.setStatus(StatusNotification.Status.SUCCESS);
}
@Override
public void onFailure(Throwable throwable) {
notification.setContent(throwable.getMessage());
notification.setStatus(StatusNotification.Status.FAIL);
}
};
}
/** Show dialog. */
public void showDialog() {
view.setEnabledImportButton(false);
view.showDialog();
}
/** {@inheritDoc} */
@Override
public void onCancelClicked() {
view.closeDialog();
}
/** {@inheritDoc} */
@Override
public void onImportClicked() {
view.closeDialog();
FactoryDto factoryJson;
try {
factoryJson = dtoFactory.createDtoFromJson(view.getFileContent(), FactoryDto.class);
} catch (JSONException jsonException) {
notification.setStatus(StatusNotification.Status.FAIL);
notification.setContent("Error parsing factory object.");
return;
}
notification = notificationManager.notify(localizationConstant.cloningSource(), null, StatusNotification.Status.PROGRESS, NOT_EMERGE_MODE);
projectImporter.startImporting(factoryJson, importerCallback);
}
@Override
public void onErrorReadingFile(String errorMessage) {
view.setEnabledImportButton(false);
notification.setStatus(StatusNotification.Status.FAIL);
notification.setContent(errorMessage);
}
}

View File

@ -0,0 +1,47 @@
/*******************************************************************************
* Copyright (c) 2012-2017 Codenvy, S.A.
* 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:
* Codenvy, S.A. - initial API and implementation
*******************************************************************************/
package org.eclipse.che.ide.factory.json;
import com.google.gwt.user.client.ui.IsWidget;
/**
* The view of {@link ImportFromConfigPresenter}.
*
* @author Sergii Leschenko
*/
public interface ImportFromConfigView extends IsWidget {
public interface ActionDelegate {
/** Performs any actions appropriate in response to the user having pressed the Cancel button. */
void onCancelClicked();
/** Performs any actions appropriate in response to the user having pressed the Import button. */
void onImportClicked();
/** Performs any actions appropriate in response to error reading file */
void onErrorReadingFile(String errorMessage);
}
/** Show dialog. */
void showDialog();
/** Close dialog */
void closeDialog();
/** Sets the delegate to receive events from this view. */
void setDelegate(ActionDelegate delegate);
/** Enables or disables import button */
void setEnabledImportButton(boolean enabled);
/** Get content of selected file */
String getFileContent();
}

View File

@ -0,0 +1,176 @@
/*******************************************************************************
* Copyright (c) 2012-2017 Codenvy, S.A.
* 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:
* Codenvy, S.A. - initial API and implementation
*******************************************************************************/
package org.eclipse.che.ide.factory.json;
import com.google.gwt.dom.client.Element;
import com.google.gwt.uibinder.client.UiBinder;
import com.google.gwt.uibinder.client.UiField;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.FileUpload;
import com.google.gwt.user.client.ui.FormPanel;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.Widget;
import com.google.inject.Inject;
import org.eclipse.che.ide.CoreLocalizationConstant;
import org.eclipse.che.ide.ui.window.Window;
/**
* The implementation of {@link ImportFromConfigView}.
*
* @author Sergii Leschenko
*/
public class ImportFromConfigViewImpl extends Window implements ImportFromConfigView {
@SuppressWarnings("unused") // used in native js
private static final int MAX_FILE_SIZE_MB = 3;
public interface ImportFromConfigViewBinder extends UiBinder<Widget, ImportFromConfigViewImpl> {
}
@UiField
FormPanel uploadForm;
@UiField
Label errorMessage;
FileUpload fileUpload;
private ActionDelegate delegate;
private String fileContent;
private final Button buttonImport;
@Inject
public ImportFromConfigViewImpl(ImportFromConfigViewBinder importFromConfigViewBinder,
CoreLocalizationConstant locale) {
this.setTitle(locale.importFromConfigurationTitle());
setWidget(importFromConfigViewBinder.createAndBindUi(this));
Button btnCancel = createButton(locale.cancelButton(), "import-from-config-btn-cancel", event -> delegate.onCancelClicked());
addButtonToFooter(btnCancel);
buttonImport = createButton(locale.importButton(), "import-from-config-btn-import", event -> delegate.onImportClicked());
addButtonToFooter(buttonImport);
}
/** {@inheritDoc} */
@Override
public void showDialog() {
errorMessage.setText("");
fileContent = null;
fileUpload = new FileUpload();
fileUpload.setHeight("22px");
fileUpload.setWidth("100%");
fileUpload.setName("file");
fileUpload.ensureDebugId("import-from-config-ChooseFile");
addHandler(fileUpload.getElement());
fileUpload.addChangeHandler(event -> buttonImport.setEnabled(fileUpload.getFilename() != null));
uploadForm.add(fileUpload);
this.show();
}
/** {@inheritDoc} */
@Override
public void closeDialog() {
hide();
onClose();
}
/** {@inheritDoc} */
@Override
public void setDelegate(ActionDelegate delegate) {
this.delegate = delegate;
}
@Override
public void setEnabledImportButton(boolean enabled) {
buttonImport.setEnabled(enabled);
}
@Override
public String getFileContent() {
return fileContent;
}
/** {@inheritDoc} */
@Override
protected void onClose() {
uploadForm.remove(fileUpload);
fileUpload = null;
}
private native void addHandler(Element element) /*-{
var instance = this;
function readFileContent(evt) {
// Check for the various File API support.
if (!window.File || !window.FileReader || !window.FileList || !window.Blob) {
instance.@org.eclipse.che.ide.factory.json.ImportFromConfigViewImpl::onError(Ljava/lang/String;)
('The File APIs are not fully supported in this browser.');
return;
}
var selectedFile = evt.target.files[0];
var max_size = @org.eclipse.che.ide.factory.json.ImportFromConfigViewImpl::MAX_FILE_SIZE_MB;
if (selectedFile.size > max_size * 100000) {
instance.@org.eclipse.che.ide.factory.json.ImportFromConfigViewImpl::resetUploadFileField()();
instance.@org.eclipse.che.ide.factory.json.ImportFromConfigViewImpl::setErrorMessageOnForm(Ljava/lang/String;)
('File size exceeds the limit ' + max_size + 'mb');
return;
}
var reader = new FileReader();
reader.onload = function () {
//reseting error message
instance.@org.eclipse.che.ide.factory.json.ImportFromConfigViewImpl::setErrorMessageOnForm(Ljava/lang/String;)('');
//getting file's content
instance.@org.eclipse.che.ide.factory.json.ImportFromConfigViewImpl::fileContent = reader.result;
};
reader.onerror = function (event) {
instance.@org.eclipse.che.ide.factory.json.ImportFromConfigViewImpl::onError(Ljava/lang/String;)
('Error reading config file ' + event.target.error.code);
};
reader.readAsText(selectedFile);
}
element.addEventListener('change', readFileContent, false);
}-*/;
private void resetUploadFileField() {
uploadForm.remove(fileUpload);
fileUpload = new FileUpload();
fileUpload.setHeight("22px");
fileUpload.setWidth("100%");
fileUpload.setName("file");
fileUpload.ensureDebugId("import-from-config-ChooseFile");
addHandler(fileUpload.getElement());
fileUpload.addChangeHandler(event -> buttonImport.setEnabled(fileUpload.getFilename() != null));
uploadForm.add(fileUpload);
}
private void setErrorMessageOnForm(String msg) {
errorMessage.setText(msg);
}
private void onError(String message) {
delegate.onErrorReadingFile(message);
}
}

View File

@ -0,0 +1,46 @@
<!--
Copyright (c) 2012-2017 Codenvy, S.A.
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:
Codenvy, S.A. - initial API and implementation
-->
<!DOCTYPE ui:UiBinder SYSTEM "http://dl.google.com/gwt/DTD/xhtml.ent">
<ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder'
xmlns:g='urn:import:com.google.gwt.user.client.ui'>
<ui:with field='locale' type='org.eclipse.che.ide.CoreLocalizationConstant'/>
<ui:style>
.emptyBorder {
margin: 6px;
}
.spacing {
margin-bottom: 10px;
}
.errorMsg {
color: red;
}
</ui:style>
<g:DockLayoutPanel unit="PX" width="350px" height="75px" addStyleNames="{style.emptyBorder}">
<g:north size="65.0">
<g:DockLayoutPanel unit="PX" width="100%" height="100%" styleName="{style.spacing}">
<g:north size="20.0">
<g:Label text="{locale.configFileTitle}"/>
</g:north>
<g:center size="25.0">
<g:FormPanel ui:field="uploadForm" debugId="import-from-config-uploadForm"/>
</g:center>
<g:south size="20.0">
<g:Label ui:field="errorMessage" text="" styleName="{style.errorMsg}"/>
</g:south>
</g:DockLayoutPanel>
</g:north>
</g:DockLayoutPanel>
</ui:UiBinder>

Some files were not shown because too many files have changed in this diff Show More