Skip to content

Commit

Permalink
CHE-3869: fix stack import and authoring
Browse files Browse the repository at this point in the history
Signed-off-by: Oleksii Kurinnyi <[email protected]>
  • Loading branch information
Oleksii Kurinnyi committed Jan 31, 2017
1 parent e102dd0 commit 669ce7e
Show file tree
Hide file tree
Showing 19 changed files with 258 additions and 322 deletions.
38 changes: 28 additions & 10 deletions dashboard/src/app/stacks/stack-details/stack-validation.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -232,27 +232,45 @@ export class StackValidationService {
errors.push('The key \'' + key + '\' is redundant in recipe.');
}
});

if (angular.isUndefined(recipe.location) && angular.isUndefined(recipe.content)) {
isValid = false;
errors.push('The recipe should have one of \'location\' or \'content\'.');
}

if (DOCKERFILE === recipe.type) {
if (!recipe.content || !recipe.contentType || !/^FROM\s+\w+/m.test(recipe.content)) {
if (angular.isDefined(recipe.location) && !recipe.location) {
isValid = false;
errors.push('The dockerfile is invalid.');
errors.push('Unknown recipe location.');
}
if (angular.isDefined(recipe.content)) {
if (!recipe.content) {
isValid = false;
errors.push('Unknown recipe content.');
} else if (!/^FROM\s+\w+/m.test(recipe.content)) {
isValid = false;
errors.push('The dockerfile is invalid.');
}
if (!recipe.contentType) {
errors.push('Unknown recipe contentType.');
}
}
if (!recipe.contentType) {
errors.push('Unknown recipe contentType.');
}
} else if (COMPOSE === recipe.type) {
if (!recipe.content || !recipe.contentType || !/^services:\n/m.test(recipe.content)) {
if (angular.isDefined(recipe.location) && !recipe.location) {
isValid = false;
errors.push('The composefile is invalid.');
errors.push('Unknown recipe location.');
}
if (angular.isDefined(recipe.content)) {
if (!recipe.content) {
isValid = false;
errors.push('Unknown recipe content.');
} else if (!/^services:\n/m.test(recipe.content)) {
isValid = false;
errors.push('The composefile is invalid.');
}
if (!recipe.contentType) {
errors.push('Unknown recipe contentType.');
}
}
if (!recipe.contentType) {
errors.push('Unknown recipe contentType.');
}
} else if (DOCKERIMAGE === recipe.type) {
if (!recipe.location) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,9 @@ export class WorkspaceConfigImportController {
angular.extend(editedWorkspaceConfig, this.workspaceConfig);

this.importWorkspaceJson = angular.toJson(editedWorkspaceConfig, true);
this.onChange();

let validateOnly = true;
this.onChange(validateOnly);
} catch (e) {
this.$log.error(e);
}
Expand All @@ -95,7 +97,7 @@ export class WorkspaceConfigImportController {
/**
* Callback when editor content is changed.
*/
onChange(): void {
onChange(validateOnly?: boolean): void {
if (!this.importWorkspaceJson) {
this.configValidationMessages = ['The config is required.'];
return;
Expand All @@ -105,15 +107,11 @@ export class WorkspaceConfigImportController {
let config = angular.fromJson(this.importWorkspaceJson);
let validationResult = this.validationService.getWorkspaceConfigValidation(config);

if (validationResult.errors.length) {
this.configValidationMessages = angular.copy(validationResult.errors);
} else {
// avoid flickering messages from other tabs
// while model is applying
this.$timeout(() => {
this.configValidationMessages.length = 0;
this.configErrorsNumber = this.configValidationMessages.length;
}, 500);
this.configValidationMessages = angular.copy(validationResult.errors);
this.configErrorsNumber = this.configValidationMessages.length;

if (validateOnly) {
return;
}

// immediately apply config on IU
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,10 @@ export class WorkspaceEnvironmentsController {
// for compose recipe
// check if there are machines without memory limit
let environment = this.workspaceConfig.environments[this.environmentName];
if (!environment) {
return;
}

if (environment.recipe && environment.recipe.type === 'compose') {
let recipeType = environment.recipe.type,
environmentManager = this.cheEnvironmentRegistry.getEnvironmentManager(recipeType);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,10 +46,10 @@
che-label-name="Select 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.">
<ng-form name="workspaceStackForm">
<workspace-stacks workspace-stack-on-change="workspaceEnvironmentsController.changeWorkspaceStack(config, stackId)"
workspace-name="workspaceEnvironmentsController.workspaceName"
environment-name="workspaceEnvironmentsController.environmentName"
workspace-imported-recipe="workspaceEnvironmentsController.workspaceImportedRecipe"></workspace-stacks>
<workspace-select-stack workspace-stack-on-change="workspaceEnvironmentsController.changeWorkspaceStack(config, stackId)"
workspace-name="workspaceEnvironmentsController.workspaceName"
environment-name="workspaceEnvironmentsController.environmentName"
workspace-imported-recipe="workspaceEnvironmentsController.workspaceImportedRecipe"></workspace-select-stack>
</ng-form>
</che-label-container>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ export class WorkspaceRecipeAuthoringController {

recipeFormat: string;
recipeScript: string;
recipeFormatCopy: string;
recipeScriptCopy: string;
recipeChange: Function;

editorOptions: {
lineWrapping: boolean,
Expand All @@ -37,7 +40,7 @@ export class WorkspaceRecipeAuthoringController {
* Default constructor that is using resource
* @ngInject for Dependency injection
*/
constructor($timeout: ng.ITimeoutService) {
constructor($scope: ng.IScope, $timeout: ng.ITimeoutService) {
this.$timeout = $timeout;

this.editorOptions = {
Expand All @@ -49,40 +52,59 @@ export class WorkspaceRecipeAuthoringController {
this.setEditor(editor);
}
};

$scope.$watch(() => { return this.recipeScript; }, () => {
this.recipeScriptCopy = this.recipeScript;
});
$scope.$watch(() => { return this.recipeFormat; }, () => {
this.recipeFormatCopy = this.recipeFormat || 'compose';
});

this.onRecipeChange();
}

setEditor(editor: any): void {
editor.on('paste', () => {
this.detectFormat(editor);
let content = editor.getValue();
this.detectFormat(content);
});
editor.on('change', () => {
this.trackChangesInProgress(editor);
let content = editor.getValue();
this.trackChangesInProgress(content);
});
}

trackChangesInProgress(editor: any): void {
trackChangesInProgress(content: string): void {
if (this.editingTimeoutPromise) {
this.$timeout.cancel(this.editingTimeoutPromise);
}

this.editingTimeoutPromise = this.$timeout(() => {
this.detectFormat(editor);
}, 1000);
this.detectFormat(content);
}, 100);
}

detectFormat(editor: any): void {
let content = editor.getValue();

detectFormat(content: string): void {
// compose format detection:
if (content.match(/^services:\n/m)) {
this.recipeFormat = 'compose';
this.recipeFormatCopy = 'compose';
this.editorOptions.mode = 'text/x-yaml';
}

// docker file format detection
if (content.match(/^FROM\s+\w+/m)) {
this.recipeFormat = 'dockerfile';
this.recipeFormatCopy = 'dockerfile';
this.editorOptions.mode = 'text/x-dockerfile';
}
}

onRecipeChange() {
this.$timeout(() => {
this.detectFormat(this.recipeScriptCopy);
this.recipeChange({
recipeFormat: this.recipeFormatCopy,
recipeScript: this.recipeScriptCopy
});
}, 10);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ export class WorkspaceRecipeAuthoring {
// scope values
this.scope = {
recipeScript: '=cheRecipeScript',
recipeFormat: '=cheRecipeFormat'
recipeFormat: '=cheRecipeFormat',
recipeChange: '&cheRecipeChange'
};

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,18 @@

<div layout="column" class="recipe-editor">
<ui-codemirror ui-codemirror="workspaceRecipeAuthoringController.editorOptions"
ng-model-options="{ updateOn: 'default blur', debounce: { 'default': 1000, 'blur': 0 } }"
ng-model="workspaceRecipeAuthoringController.recipeScript"></ui-codemirror>
ng-model-options="{ updateOn: 'default blur', debounce: { 'default': 1000, 'blur': 0 }, allowInvalid: true }"
ng-model="workspaceRecipeAuthoringController.recipeScriptCopy"
ng-change="workspaceRecipeAuthoringController.onRecipeChange()"></ui-codemirror>
<che-input che-form="workspaceRecipeAuthoringForm"
che-name="recipe"
type="hidden"
ng-model="workspaceRecipeAuthoringController.recipeScript"
ng-model="workspaceRecipeAuthoringController.recipeScriptCopy"
ng-required>
<div ng-message="required">The recipe is required.</div>
<che-error-messages che-message-scope="workspace-details-environment"
che-message-name="Recipe content">
<div ng-message="required">The recipe is required.</div>
</che-error-messages>
</che-input>
<div class="recipe-docs-link">
<a href="https://www.eclipse.org/che/docs/workspace/stacks/index.html#stack-administration" target="_blank">Custom stack documentation</a>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,38 @@
* @author Oleksii Kurinnyi
*/
export class WorkspaceRecipeImportController {
$timeout: ng.ITimeoutService;

recipeUrl: string;
recipeFormat: string;
recipeUrlCopy: string;
recipeFormatCopy: string;
recipeChange: Function;

/**
* Default constructor that is using resource
* @ngInject for Dependency injection
*/
constructor() {
constructor($scope: ng.IScope, $timeout: ng.ITimeoutService) {
this.recipeFormat = this.recipeFormat || 'compose';
this.$timeout = $timeout;

$scope.$watch(() => { return this.recipeFormat; }, () => {
this.recipeFormatCopy = this.recipeFormat || 'compose';
});
$scope.$watch(() => { return this.recipeUrl; }, () => {
this.recipeUrlCopy = this.recipeUrl;
});
}

onRecipeChange() {
this.$timeout(() => {
this.recipeChange({
recipeUrl: this.recipeUrlCopy,
recipeFormat: this.recipeFormatCopy
});
}, 10)

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ export class WorkspaceRecipeImport {
// scope values
this.scope = {
recipeUrl: '=cheRecipeUrl',
recipeFormat: '=cheRecipeFormat'
recipeFormat: '=cheRecipeFormat',
recipeChange: '&cheRecipeChange'
};

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,20 +10,27 @@
aria-label="URL of the Recipe"
che-pattern="^(ftp|https?)://[\w@.\-\_]+(:\d{1,5})?(/[\w#!:.?+=&%@!\_\-/]+)*$"
che-width="auto"
ng-model="workspaceRecipeImportCtrl.recipeUrl"
ng-model="workspaceRecipeImportCtrl.recipeUrlCopy"
ng-change="workspaceRecipeImportCtrl.onRecipeChange()"
ng-model-options="{ updateOn: 'default blur', debounce: { 'default': 200, 'blur': 0 }, allowInvalid: true }"
minlength="3"
ng-required
ng-maxlength="255">
<div ng-message="required">URL is required.</div>
<div ng-message="minlength">URL has to be more than 3 characters long.</div>
<div ng-message="maxlength">URL has to be less than 255 characters long.</div>
<div ng-message="pattern">URL is not valid.</div>
<che-error-messages che-message-scope="workspace-details-environment"
che-message-name="Recipe URL">
<div ng-message="required">URL is required.</div>
<div ng-message="minlength">URL has to be more than 3 characters long.</div>
<div ng-message="maxlength">URL has to be less than 255 characters long.</div>
<div ng-message="pattern">URL is not valid.</div>
</che-error-messages>
</che-input>
</div>
</che-label-container>
<che-label-container che-label-name="Recipe Type">
<div layout="column">
<che-toggle ng-model="workspaceRecipeImportCtrl.recipeFormat" class="recipe-format">
<che-toggle ng-model="workspaceRecipeImportCtrl.recipeFormatCopy"
ng-change="workspaceRecipeImportCtrl.onRecipeChange()"
class="recipe-format">
<che-toggle-button che-title="compose"></che-toggle-button>
<che-toggle-button che-title="dockerfile"></che-toggle-button>
</che-toggle>
Expand Down
Loading

0 comments on commit 669ce7e

Please sign in to comment.