From 54b99ea9c9c59576470bea534d11f13377b663d8 Mon Sep 17 00:00:00 2001 From: Richard Cox Date: Thu, 27 Jul 2017 16:33:30 +0100 Subject: [PATCH 1/7] Split deploy destination + source step into two steps --- .../frontend/i18n/en_US/app-push.json | 13 +- .../deploy-app-workflow/deploy-app-bits.html | 52 +++--- .../deploy-app-destination.html | 6 + .../view/deploy-app-workflow/deploy-app.scss | 8 +- .../deploy-app-workflow/deploy-app.service.js | 159 ++++++++++-------- .../frontend/i18n/en_US/deploy-location.json | 2 +- 6 files changed, 126 insertions(+), 114 deletions(-) create mode 100644 components/cf-app-push/frontend/src/view/deploy-app-workflow/deploy-app-destination.html diff --git a/components/cf-app-push/frontend/i18n/en_US/app-push.json b/components/cf-app-push/frontend/i18n/en_US/app-push.json index 27f8be644a..a06e365a5c 100644 --- a/components/cf-app-push/frontend/i18n/en_US/app-push.json +++ b/components/cf-app-push/frontend/i18n/en_US/app-push.json @@ -5,8 +5,13 @@ "deploy-app-dialog": { "title": "Deploy Application", "button-deploy": "Deploy", - "step-info": { - "title": "Destination and Source", + "step-destination": { + "title": "Destination", + "busy": "Updating Cloud Foundry Data", + "enter-failed": "Failed to refresh cloud foundry data. Please try again. If this error persists, please contact the Administrator." + }, + "step-source": { + "title": "Source", "github": { "label": "Public Github", "project-label": "Project", @@ -43,9 +48,7 @@ "description": "For existing apps, if the relative path to your manifest.yml is not in the root please supply it below.", "path-label": "Path to manifest", "path-placeholder": "Relative path of manifest to root of app directory" - }, - "busy": "Updating Cloud Foundry Data", - "enter-failed": "Failed to refresh cloud foundry data. Please try again. If this error persists, please contact the Administrator." + } }, "step-deploying": { "title": "Deploy Application", diff --git a/components/cf-app-push/frontend/src/view/deploy-app-workflow/deploy-app-bits.html b/components/cf-app-push/frontend/src/view/deploy-app-workflow/deploy-app-bits.html index eadf61bb6d..424d8586c6 100644 --- a/components/cf-app-push/frontend/src/view/deploy-app-workflow/deploy-app-bits.html +++ b/components/cf-app-push/frontend/src/view/deploy-app-workflow/deploy-app-bits.html @@ -1,9 +1,3 @@ - @@ -24,32 +18,32 @@ -

deploy-app-dialog.step-info.source.file

-

deploy-app-dialog.step-info.source.folder

+

deploy-app-dialog.step-source.source.file

+

deploy-app-dialog.step-source.source.folder

- +
- + - deploy-app-dialog.step-info.github.project-not-found + deploy-app-dialog.step-source.github.project-not-found + placeholder="{{'deploy-app-dialog.step-source.github.project-placeholder' | translate}}"/>
- +
@@ -76,17 +70,17 @@
- deploy-app-dialog.step-info.github.branch-last-commit + deploy-app-dialog.step-source.github.branch-last-commit {{ step.data.githubCommit.sha | limitTo:6}} {{ step.data.githubCommit.commit.message | limitTo:50}}...
- deploy-app-dialog.step-info.github.branch-last-commit-committer{{ step.data.githubCommit.author.login}} + deploy-app-dialog.step-source.github.branch-last-commit-committer{{ step.data.githubCommit.author.login || step.data.githubCommit.commit.author.name}}
- deploy-app-dialog.step-info.github.branch-last-commit-date {{ step.data.githubCommit.commit.author.date | momentDateFormat }} + deploy-app-dialog.step-source.github.branch-last-commit-date {{ step.data.githubCommit.commit.author.date | momentDateFormat }}
@@ -98,24 +92,24 @@
- - + +
- - + +
- - + + - + - + diff --git a/components/cf-app-push/frontend/src/view/deploy-app-workflow/deploy-app-destination.html b/components/cf-app-push/frontend/src/view/deploy-app-workflow/deploy-app-destination.html new file mode 100644 index 0000000000..6da0143fc0 --- /dev/null +++ b/components/cf-app-push/frontend/src/view/deploy-app-workflow/deploy-app-destination.html @@ -0,0 +1,6 @@ + diff --git a/components/cf-app-push/frontend/src/view/deploy-app-workflow/deploy-app.scss b/components/cf-app-push/frontend/src/view/deploy-app-workflow/deploy-app.scss index d2a474aa18..0080ef0269 100644 --- a/components/cf-app-push/frontend/src/view/deploy-app-workflow/deploy-app.scss +++ b/components/cf-app-push/frontend/src/view/deploy-app-workflow/deploy-app.scss @@ -8,8 +8,8 @@ $deploy-status-bounce-spacer: 6px; // Select option height is text size + margin $select-option-height: 39px; -.modal.detail-view.detail-view-dialog>.modal-dialog>.modal-content .detail-view-content.deploy-app wizard .wizard-step { - padding: 0 $console-unit-space $console-unit-space $console-unit-space; +.deploy-app-wizard.modal.detail-view.detail-view-dialog>.modal-dialog>.modal-content .detail-view-content.deploy-app wizard .wizard-step { + padding: 0 $console-unit-space 6px $console-unit-space; } .deploy-app { @@ -280,7 +280,3 @@ percent-gauge.deploy-app-file-progress { font-size: $font-size-large1; font-weight: $console-font-weight-semibold; } - -.deploy-app-wizard.modal.detail-view.detail-view-dialog>.modal-dialog>.modal-content .detail-view-content.deploy-app wizard .wizard-step { - padding-bottom: 6px; -} \ No newline at end of file diff --git a/components/cf-app-push/frontend/src/view/deploy-app-workflow/deploy-app.service.js b/components/cf-app-push/frontend/src/view/deploy-app-workflow/deploy-app.service.js index acea0e5f52..ff9b221a89 100644 --- a/components/cf-app-push/frontend/src/view/deploy-app-workflow/deploy-app.service.js +++ b/components/cf-app-push/frontend/src/view/deploy-app-workflow/deploy-app.service.js @@ -107,7 +107,7 @@ // How often to check for the app being created var DISCOVER_APP_TIMER_PERIOD = 2000; - var allowBack = false; + var allowBack = true; var templatePath = 'plugins/cf-app-push/view/deploy-app-workflow/'; @@ -165,35 +165,21 @@ fileScanData: null }; - /* - * Not all browsers allows a folder to be selected by an input field - */ - function isInputDirSupported() { - /* eslint-disable angular/document-service */ - var tmpInput = document.createElement('input'); - /* eslint-enable angular/document-service */ - return 'webkitdirectory' in tmpInput; - } - vm.folderSupport = isInputDirSupported(); - var stepInfo = { - title: 'deploy-app-dialog.step-info.title', - templateUrl: templatePath + 'deploy-app-bits.html', - formName: 'deploy-info-form', + // , + // allowNext: function () { + // return vm.userInput.sourceType === 'github' && vm.userInput.githubProjectValid || vm.userInput.sourceType === 'local' && angular.isDefined(vm.userInput.fileScanData); + // }, + + var stepLocation = { + title: 'deploy-app-dialog.step-destination.title', + templateUrl: templatePath + 'deploy-app-destination.html', + formName: 'deploy-destination-form', data: vm.data, userInput: vm.userInput, - folderSupport: vm.folderSupport, - showBusyOnEnter: 'deploy-app-dialog.step-info.busy', - nextBtnText: 'deploy-app-dialog.button-deploy', - stepCommit: true, - bytesToHumanSize: appUtilsService.bytesToHumanSize, - allowNext: function () { - return vm.userInput.sourceType === 'github' && vm.userInput.githubProjectValid || vm.userInput.sourceType === 'local' && angular.isDefined(vm.userInput.fileScanData); - }, - dropItemHandler: dropHandler, + showBusyOnEnter: 'deploy-app-dialog.step-destination.busy', onEnter: function () { - allowBack = false; if (vm.data.deployStatus) { // Previously been at this step, no need to fetch instances again return; @@ -211,56 +197,27 @@ .value(); [].push.apply(vm.data.serviceInstances, validServiceInstances); }).catch(function () { - return $q.reject('deploy-app-dialog.step-info.enter-failed'); + return $q.reject('deploy-app-dialog.step-destination.enter-failed'); }); } }; - /* - * Handle a drop event - */ - function dropHandler(items) { - vm.userInput.cfIgnoreFile = false; - // Find out what has been dropped and take appropriate action - itemDropHelper.identify(items).then(function (info) { - vm.userInput.localPath = info.value ? info.value.name : ''; - if (info.isFiles) { - vm.options.wizardCtrl.showBusy('deploy-app-dialog.step-info.scanning'); - itemDropHelper.traverseFiles(info.value, CF_IGNORE_FILE, CF_DEFAULT_IGNORES).then(function (results) { - vm.userInput.fileScanData = results; - vm.userInput.sourceType = 'local'; - vm.userInput.cfIgnoreFile = results.foundIgnoreFile; - }).finally(function () { - vm.options.wizardCtrl.showBusy(); - }); - } else if (info.isArchiveFile) { - vm.userInput.sourceType = 'local'; - var res = itemDropHelper.initScanner(); - vm.userInput.fileScanData = res.addFile(info.value); - vm.userInput.sourceType = 'local'; - } else if (info.isWebLink) { - // Check if this is a GitHub link - if (info.value.toLowerCase().indexOf(gitHubUrlBase) === 0) { - vm.userInput.sourceType = 'github'; - var urlParts = info.value.substring(gitHubUrlBase.length).split('/'); - if (urlParts.length > 1) { - var branch; - if (urlParts.length > 3 && urlParts[2] === 'tree') { - branch = urlParts[3]; - } - var project = urlParts[0] + '/' + urlParts[1]; - if (vm.userInput.githubProject === project) { - // Project is the same, so just change the branch - vm.selectBranch(branch ? branch : vm.data.githubProject.default_branch); - } else { - vm.userInput.autoSelectGithubBranch = branch; - vm.userInput.githubProject = project; - } - } - } - } - }); - } + var stepSource = { + title: 'deploy-app-dialog.step-source.title', + templateUrl: templatePath + 'deploy-app-bits.html', + formName: 'deploy-info-form', + data: vm.data, + userInput: vm.userInput, + folderSupport: vm.folderSupport, + showBusyOnEnter: 'deploy-app-dialog.step-source.busy', + nextBtnText: 'deploy-app-dialog.button-deploy', + stepCommit: true, + bytesToHumanSize: appUtilsService.bytesToHumanSize, + allowNext: function () { + return vm.userInput.sourceType === 'github' && vm.userInput.githubProjectValid || vm.userInput.sourceType === 'local' && vm.userInput.fileScanData; + }, + dropItemHandler: dropHandler + }; var stepDeploying = { title: 'deploy-app-dialog.step-deploying.title', @@ -276,7 +233,7 @@ onEnter: function () { allowBack = false; return startDeploy().catch(function (error) { - allowBack = false; + allowBack = true; return $q.reject($translate.instant('deploy-app-dialog.step-deploying.submit-failed', {reason: error})); }); }, @@ -298,7 +255,7 @@ cancel: 'buttons.cancel', back: 'buttons.previous' }, - steps: [stepInfo, stepDeploying] + steps: [stepLocation, stepSource, stepDeploying] } }; @@ -408,6 +365,62 @@ } }); + /* + * Handle a drop event + */ + function dropHandler(items) { + vm.userInput.cfIgnoreFile = false; + // Find out what has been dropped and take appropriate action + itemDropHelper.identify(items).then(function (info) { + vm.userInput.localPath = info.value ? info.value.name : ''; + if (info.isFiles) { + vm.options.wizardCtrl.showBusy('deploy-app-dialog.step-info.scanning'); + itemDropHelper.traverseFiles(info.value, CF_IGNORE_FILE, CF_DEFAULT_IGNORES).then(function (results) { + vm.userInput.fileScanData = results; + vm.userInput.sourceType = 'local'; + vm.userInput.cfIgnoreFile = results.foundIgnoreFile; + }).finally(function () { + vm.options.wizardCtrl.showBusy(); + }); + } else if (info.isArchiveFile) { + vm.userInput.sourceType = 'local'; + var res = itemDropHelper.initScanner(); + vm.userInput.fileScanData = res.addFile(info.value); + vm.userInput.sourceType = 'local'; + } else if (info.isWebLink) { + // Check if this is a GitHub link + if (info.value.toLowerCase().indexOf(gitHubUrlBase) === 0) { + vm.userInput.sourceType = 'github'; + var urlParts = info.value.substring(gitHubUrlBase.length).split('/'); + if (urlParts.length > 1) { + var branch; + if (urlParts.length > 3 && urlParts[2] === 'tree') { + branch = urlParts[3]; + } + var project = urlParts[0] + '/' + urlParts[1]; + if (vm.userInput.githubProject === project) { + // Project is the same, so just change the branch + vm.selectBranch(branch ? branch : vm.data.githubProject.default_branch); + } else { + vm.userInput.autoSelectGithubBranch = branch; + vm.userInput.githubProject = project; + } + } + } + } + }); + } + + /* + * Not all browsers allows a folder to be selected by an input field + */ + function isInputDirSupported() { + /* eslint-disable angular/document-service */ + var tmpInput = document.createElement('input'); + /* eslint-enable angular/document-service */ + return 'webkitdirectory' in tmpInput; + } + // Handle result of a file input form field selection function handleFileInputSelect(items) { // File list from a file input form field diff --git a/components/cloud-foundry/frontend/i18n/en_US/deploy-location.json b/components/cloud-foundry/frontend/i18n/en_US/deploy-location.json index c9f4d42fb0..df26b520f1 100644 --- a/components/cloud-foundry/frontend/i18n/en_US/deploy-location.json +++ b/components/cloud-foundry/frontend/i18n/en_US/deploy-location.json @@ -1,6 +1,6 @@ { "app.deploy-location": { - "title": "Select deploy location", + "title": "Select the [[@:cloud-foundry]] instance, organization and space where the app will be deployed to.", "cf-label": "[[@:cloud-foundry]]", "cf-placeholder": "Select [[@:cloud-foundry]]", "org-label": "[[@:organization]]", From cdd134522f0e42c2ab5162af9268dd284e1f3faf Mon Sep 17 00:00:00 2001 From: Richard Cox Date: Thu, 27 Jul 2017 19:04:42 +0100 Subject: [PATCH 2/7] Refactor/reduce deploy app service WIP - Move steps out into their own service - Move input sources out into seperate directives --- .../deploy-app-data.service.js | 17 + .../view/deploy-app-workflow/deploy-app.scss | 277 +------- .../deploy-app-workflow/deploy-app.service.js | 634 +----------------- .../deploy-step-deploying.html} | 8 +- .../deploy-step-deploying.scss | 23 + .../deploy-step-deploying.service.js | 352 ++++++++++ .../deploy-step-destination.html} | 0 .../deploy-step-destination.scss | 0 .../deploy-step-destination.service.js | 61 ++ .../deploy-source-git.directive.js | 115 ++++ .../deploy-source-git/deploy-source-git.html | 65 ++ .../deploy-source-local.directive.js | 0 .../deploy-source-local.html | 0 .../deploy-step-source.html} | 71 +- .../deploy-step-source.scss | 253 +++++++ .../deploy-step-source.service.js | 172 +++++ 16 files changed, 1102 insertions(+), 946 deletions(-) create mode 100644 components/cf-app-push/frontend/src/view/deploy-app-workflow/deploy-app-data.service.js rename components/cf-app-push/frontend/src/view/deploy-app-workflow/{deploy-app-deploying.html => deploy-step-deploying/deploy-step-deploying.html} (56%) create mode 100644 components/cf-app-push/frontend/src/view/deploy-app-workflow/deploy-step-deploying/deploy-step-deploying.scss create mode 100644 components/cf-app-push/frontend/src/view/deploy-app-workflow/deploy-step-deploying/deploy-step-deploying.service.js rename components/cf-app-push/frontend/src/view/deploy-app-workflow/{deploy-app-destination.html => deploy-step-destination/deploy-step-destination.html} (100%) create mode 100644 components/cf-app-push/frontend/src/view/deploy-app-workflow/deploy-step-destination/deploy-step-destination.scss create mode 100644 components/cf-app-push/frontend/src/view/deploy-app-workflow/deploy-step-destination/deploy-step-destination.service.js create mode 100644 components/cf-app-push/frontend/src/view/deploy-app-workflow/deploy-step-source/deploy-source-git/deploy-source-git.directive.js create mode 100644 components/cf-app-push/frontend/src/view/deploy-app-workflow/deploy-step-source/deploy-source-git/deploy-source-git.html create mode 100644 components/cf-app-push/frontend/src/view/deploy-app-workflow/deploy-step-source/deploy-source-local/deploy-source-local.directive.js create mode 100644 components/cf-app-push/frontend/src/view/deploy-app-workflow/deploy-step-source/deploy-source-local/deploy-source-local.html rename components/cf-app-push/frontend/src/view/deploy-app-workflow/{deploy-app-bits.html => deploy-step-source/deploy-step-source.html} (56%) create mode 100644 components/cf-app-push/frontend/src/view/deploy-app-workflow/deploy-step-source/deploy-step-source.scss create mode 100644 components/cf-app-push/frontend/src/view/deploy-app-workflow/deploy-step-source/deploy-step-source.service.js diff --git a/components/cf-app-push/frontend/src/view/deploy-app-workflow/deploy-app-data.service.js b/components/cf-app-push/frontend/src/view/deploy-app-workflow/deploy-app-data.service.js new file mode 100644 index 0000000000..1b26e154f3 --- /dev/null +++ b/components/cf-app-push/frontend/src/view/deploy-app-workflow/deploy-app-data.service.js @@ -0,0 +1,17 @@ +// (function () { +// 'use strict'; +// +// angular +// .module('cf-app-push') +// .factory('appDeployAppDataService', DeployAppDataService); +// +// function DeployAppDataService() { +// return { +// data: {}, +// userInput: {}, +// newAppGuid: undefined, +// allowBack: true +// }; +// } +// +// })(); diff --git a/components/cf-app-push/frontend/src/view/deploy-app-workflow/deploy-app.scss b/components/cf-app-push/frontend/src/view/deploy-app-workflow/deploy-app.scss index 0080ef0269..ec68ca9082 100644 --- a/components/cf-app-push/frontend/src/view/deploy-app-workflow/deploy-app.scss +++ b/components/cf-app-push/frontend/src/view/deploy-app-workflow/deploy-app.scss @@ -1,282 +1,11 @@ - -$github-avatar-height: 51px; -$github-avatar-width: 51px; -$github-info-commit-spacer: 3px; - -$deploy-status-bounce-spacer: 6px; - -// Select option height is text size + margin -$select-option-height: 39px; +@import "deploy-step-deploying/deploy-step-deploying"; +@import "deploy-step-destination/deploy-step-destination"; +@import "deploy-step-source/deploy-step-source"; .deploy-app-wizard.modal.detail-view.detail-view-dialog>.modal-dialog>.modal-content .detail-view-content.deploy-app wizard .wizard-step { padding: 0 $console-unit-space 6px $console-unit-space; } .deploy-app { - deploy-location { - .control-title { - margin-top: 0; - } - } - - // Applies to buttons to choose a file or folder - .form-group.deploy-app-file-controls { - padding-right: 0; - - .form-control.file-chooser { - width: 0.1px; - height: 0.1px; - opacity: 0; - overflow: hidden; - position: absolute; - z-index: -1; - - + label { - border: none; - background-color: transparent; - padding: 0; - height: 19px; - line-height: 19px; - font-size: $font-size-medium; - margin: 0; - display: flex; - - .deploy-app-form-btn { - height: $console-input-height; - width: $console-unit-space * 2; - display: flex; - justify-content: center; - align-items: center; - margin-top: -25px; - border-right: 1px solid $rule; - - &:hover { - background-color: $component-active-bg; - color: $component-active-color; - } - - &:first-of-type { - border-left: 1px solid $rule; - } - - &:last-child { - border-right: 0; - } - } - - &.clickable { - cursor: pointer; - } - - > i { - font-size: $icon-font-size-base; - margin-right: 6px; - } - - > span { - margin: 0; - padding: 0; - flex: 1; - } - - > label { - margin: 0; - cursor: pointer; - } - } - } - } - - p.deploy-app-choose-source { - margin: $console-unit-space * 0.5 0; - } - - #deploy-app-choose-bits { - display: none; - } - - .deploy-app-source-area { - display: flex; - - > .deploy-app-source { - display: flex; - flex: 1; - - > div { - flex: 1; - } - } - - p.control-title { - margin-top: 0; - } - - radio-input { - margin-bottom: $console-unit-space / 4; - } - - &.file-drop-active { - outline: 3px dashed $brand-primary; - outline-offset: 4px; - - > * { - opacity: 0.5; - } - } - - .deploy-app-source-details { - - &.deploy-app-invisible { - visibility: hidden; - } - - &.deploy-app-github-details { - margin-top: $console-unit-space / 2; - - .deploy-app-github-info { - border: 1px solid $rule; - padding: $console-unit-space / 4 $console-unit-space / 2; - max-width: $console-input-width * 1.25; - - &.deploy-app-file-details { - height: $console-input-height; - display: flex; - align-items: center; - padding-left: 18px; - - > p { - margin: 0; - } - - > i { - margin-right: 6px; - } - } - - img { - height: $github-avatar-height; - width: $github-avatar-width; - } - - .project { - display: flex; - flex-direction: row; - align-items: center; - - .project-info { - padding-left: $console-unit-space / 2; - } - } - - .lastCommit { - display: flex; - flex-direction: row; - align-items: center; - overflow-x: hidden; - - padding-top: $github-info-commit-spacer; - - .lastCommit-info { - padding-left: $console-unit-space / 2; - flex: 1; - display: flex; - flex-direction: column; - overflow-x: hidden; - - > .lastCommit-info-commit-message { - display: flex; - - > a { - text-overflow: ellipsis; - white-space: nowrap; - overflow-x: hidden; - } - } - - .lastCommit-date-committer { - display: flex; - - > .lastCommitter { - flex: 1; - } - } - } - } - } - - } - - .deploy-app-found-cfignore { - padding: 6px 20px; - border: 1px solid $rule; - margin-top: -1px; - > p { - margin :0; - } - } - } - } - - .deploy-app-github { - display: flex; - - .deploy-app-github-form { - .deploy-app-github-branch { - //margin-bottom: $select-option-height * 3 - $console-unit-space; - .dropdown-menu { - max-height: $select-option-height * 3; - } - } - } - } - - .rm-background{ - background-color: transparent; - } - - .deploy-app-log-status { - display: flex; - align-items: center; - padding-bottom: $console-unit-space; - & > span { - font-size: $font-size-large; - padding-right: $console-unit-space; - } - bounce-spinner { - margin-top: $deploy-status-bounce-spacer; - } - } - - cf-log-viewer { - .panel-heading.action-header { - background-color: $console-primary-bg; - } - } -} - -.folder-drop-progress { - display: flex; - align-items: center; - justify-content: center; -} - -percent-gauge.deploy-app-file-progress { - - .percent-gauge .percent-gauge-bar:after { - background-image: none; - } - - .percent-gauge .percent-gauge-bar .percent-gauge-utilized-bar { - background-color: $brand-primary; - - &.percent-gauge-at-max { - background-color: $brand-primary; - } - } -} -.deploy-app-file-upload-title { - margin-bottom: $console-unit-space; - font-size: $font-size-large1; - font-weight: $console-font-weight-semibold; } diff --git a/components/cf-app-push/frontend/src/view/deploy-app-workflow/deploy-app.service.js b/components/cf-app-push/frontend/src/view/deploy-app-workflow/deploy-app.service.js index ff9b221a89..16d7d8a901 100644 --- a/components/cf-app-push/frontend/src/view/deploy-app-workflow/deploy-app.service.js +++ b/components/cf-app-push/frontend/src/view/deploy-app-workflow/deploy-app.service.js @@ -89,156 +89,38 @@ * @param {object} itemDropHelper - the item drop helper service * @param {object} appUtilsService - the App Utils service */ - function DeployAppController($scope, $q, $uibModalInstance, $state, $location, $websocket, $translate, $log, $http, - $timeout, $filter, modelManager, itemDropHelper, appUtilsService) { + function DeployAppController($scope, $uibModalInstance, $state, appDeployStepDeployingService, + appDeployStepDestinationService, appDeployStepSourceService) { var vm = this; - var CF_IGNORE_FILE = '.cfignore'; - var CF_DEFAULT_IGNORES = '.cfignore\n_darcs\n.DS_Store\n.git\n.gitignore\n.hg\n.svn\n'; - - var gitHubUrlBase = 'https://github.com/'; - - var serviceInstanceModel = modelManager.retrieve('app.model.serviceInstance.user'); - var authModel = modelManager.retrieve('cloud-foundry.model.auth'); - - var hasPushStarted, newAppGuid, discoverAppTimer; - - // How often to check for the app being created - var DISCOVER_APP_TIMER_PERIOD = 2000; - - var allowBack = true; - - var templatePath = 'plugins/cf-app-push/view/deploy-app-workflow/'; - - var socketEventTypes = { - DATA: 20000, - MANIFEST: 20001, - CLOSE_SUCCESS: 20002, - CLOSE_PUSH_ERROR: 40000, - CLOSE_NO_MANIFEST: 40001, - CLOSE_INVALID_MANIFEST: 40002, - CLOSE_FAILED_CLONE: 40003, - CLOSE_FAILED_NO_BRANCH: 40004, - CLOSE_FAILURE: 40005, - CLOSE_NO_SESSION: 40006, - CLOSE_NO_CNSI: 40007, - CLOSE_NO_CNSI_USERTOKEN: 40008, - EVENT_CLONED: 10000, - EVENT_FETCHED_MANIFEST: 10001, - EVENT_PUSH_STARTED: 10002, - EVENT_PUSH_COMPLETED: 10003, - SOURCE_REQUIRED: 30000, - SOURCE_GITHUB: 30001, - SOURCE_FOLDER: 30002, - SOURCE_FILE: 30003, - SOURCE_FILE_DATA: 30004, - SOURCE_FILE_ACK: 30005 - }; - - vm.data = { - serviceInstances: [], - logFilter: logFilter, - deployState: { - UNKNOWN: 1, - CLONED: 2, - FETCHED_MANIFEST: 3, - PUSHING: 4, - DEPLOYED: 5, - FAILED: 6, - SOCKET_OPEN: 7 + var session = { + data: { + deploying: {}, + destination: {}, + source: {} }, - githubBranches: [] - }; - - vm.userInput = { - serviceInstance: null, - organization: null, - space: null, - githubProject: '', - manifest: { - location: '/manifest.yml' + userInput: { + sourceType: 'github', + deploying: {}, + destination: {}, + source: {} }, - sourceType: 'github', - localPath: '', - localPathFile: null, - fileScanData: null - }; - - vm.folderSupport = isInputDirSupported(); - - // , - // allowNext: function () { - // return vm.userInput.sourceType === 'github' && vm.userInput.githubProjectValid || vm.userInput.sourceType === 'local' && angular.isDefined(vm.userInput.fileScanData); - // }, - - var stepLocation = { - title: 'deploy-app-dialog.step-destination.title', - templateUrl: templatePath + 'deploy-app-destination.html', - formName: 'deploy-destination-form', - data: vm.data, - userInput: vm.userInput, - showBusyOnEnter: 'deploy-app-dialog.step-destination.busy', - onEnter: function () { - if (vm.data.deployStatus) { - // Previously been at this step, no need to fetch instances again - return; - } - return serviceInstanceModel.list() - .then(function (serviceInstances) { - var validServiceInstances = _.chain(_.values(serviceInstances)) - .filter({cnsi_type: 'cf', valid: true}) - .filter(function (cnsi) { - return authModel.doesUserHaveRole(cnsi.guid, authModel.roles.space_developer); - }) - .map(function (o) { - return {label: o.name, value: o}; - }) - .value(); - [].push.apply(vm.data.serviceInstances, validServiceInstances); - }).catch(function () { - return $q.reject('deploy-app-dialog.step-destination.enter-failed'); - }); + wizard: { + allowBack: true } }; - var stepSource = { - title: 'deploy-app-dialog.step-source.title', - templateUrl: templatePath + 'deploy-app-bits.html', - formName: 'deploy-info-form', - data: vm.data, - userInput: vm.userInput, - folderSupport: vm.folderSupport, - showBusyOnEnter: 'deploy-app-dialog.step-source.busy', - nextBtnText: 'deploy-app-dialog.button-deploy', - stepCommit: true, - bytesToHumanSize: appUtilsService.bytesToHumanSize, - allowNext: function () { - return vm.userInput.sourceType === 'github' && vm.userInput.githubProjectValid || vm.userInput.sourceType === 'local' && vm.userInput.fileScanData; - }, - dropItemHandler: dropHandler - }; + var stepLocation = appDeployStepDestinationService.getStep(session); + var stepSource = appDeployStepSourceService.getStep(session, $scope); + var stepDeploy = appDeployStepDeployingService.getStep(session); - var stepDeploying = { - title: 'deploy-app-dialog.step-deploying.title', - templateUrl: templatePath + 'deploy-app-deploying.html', - data: vm.data, - userInput: vm.userInput, - cancelBtnText: 'buttons.close', - nextBtnText: 'deploy-app-dialog.step-deploying.next-button', - showBusyOnEnter: 'deploy-app-dialog.step-deploying.busy', - allowNext: function () { - return !!newAppGuid; - }, - onEnter: function () { - allowBack = false; - return startDeploy().catch(function (error) { - allowBack = true; - return $q.reject($translate.instant('deploy-app-dialog.step-deploying.submit-failed', {reason: error})); - }); - }, - isLastStep: true - }; + function destroy() { + stepLocation.destroy(); + stepSource.destroy(); + stepDeploy.destroy(); + session = null; + } vm.options = { workflow: { @@ -248,491 +130,37 @@ disableJump: true, allowCancelAtLastStep: true, allowBack: function () { - return allowBack; + return session.wizard.allowBack; }, title: 'deploy-app-dialog.title', btnText: { cancel: 'buttons.cancel', back: 'buttons.previous' }, - steps: [stepLocation, stepSource, stepDeploying] + steps: [stepLocation.step, stepSource.step, stepDeploy.step] } }; // Actions for the wizard controller vm.actions = { stop: function () { - $uibModalInstance.dismiss({reload: !!hasPushStarted}); - resetSocket(); + $uibModalInstance.dismiss({reload: !!session.wizard.hasPushStarted}); + destroy(); }, finish: function () { $uibModalInstance.close(); $state.go('cf.applications.application.summary', { - cnsiGuid: vm.userInput.serviceInstance.guid, - guid: newAppGuid, + cnsiGuid: session.userInput.destination.serviceInstance.guid, + guid: session.wizard.newAppGuid, newlyCreated: false }); - resetSocket(); + destroy(); } }; - vm.selectBranch = function (branch) { - var foundBranch = _.find(vm.data.githubBranches, function (o) { - return o.value && o.value.name === branch; - }); - vm.userInput.githubBranch = foundBranch ? foundBranch.value : undefined; - }; - - $scope.$on('$destroy', resetSocket); - $scope.$on('$destroy', function () { - $timeout.cancel(discoverAppTimer); - }); - - // Watch for the file or folder being selected by the input field and process - $scope.$watch(function () { - return vm.userInput.localPathFile; - }, function (newVal, oldVal) { - if (newVal && oldVal !== newVal) { - handleFileInputSelect(newVal); - } - }); - - var debounceGithubProjectFetch = _.debounce(function () { - var project = vm.userInput.githubProject; - if (!project || project.length === 0) { - vm.userInput.githubProjectValid = false; - return; - } - - $http.get('https://api.github.com/repos/' + project) - .then(function (response) { - vm.userInput.githubProjectValid = true; - vm.data.githubProject = response.data; - vm.userInput.githubProjectCached = project; - - $http.get('https://api.github.com/repos/' + project + '/branches') - .then(function (response) { - vm.data.githubBranches.length = 0; - [].push.apply(vm.data.githubBranches, _.map(response.data, function selectOptionMapping(o) { - return { - label: o.name, - value: o - }; - })); + $scope.$on('$destroy', destroy); - var branch = vm.userInput.autoSelectGithubBranch ? vm.userInput.autoSelectGithubBranch : vm.data.githubProject.default_branch; - vm.userInput.autoSelectGithubBranch = undefined; - var foundBranch = _.find(vm.data.githubBranches, function (o) { - return o.value && o.value.name === branch; - }); - vm.userInput.githubBranch = foundBranch ? foundBranch.value : undefined; - }) - .catch(function () { - vm.data.githubBranches.length = 0; - }); - - }) - .catch(function (response) { - if (response.status === 404) { - vm.userInput.githubProjectValid = false; - vm.data.githubBranches.length = 0; - delete vm.userInput.githubBranch; - delete vm.data.githubCommit; - } - }); - }, 1000); - - $scope.$watch(function () { - return vm.userInput.githubProject; - }, function (oldVal, newVal) { - if (oldVal !== newVal) { - debounceGithubProjectFetch(); - } - }); - - $scope.$watch(function () { - return vm.userInput.githubBranch; - }, function (newVal, oldVal) { - if (newVal && oldVal !== newVal) { - $http.get('https://api.github.com/repos/' + vm.userInput.githubProject + '/commits/' + newVal.commit.sha) - .then(function (response) { - vm.data.githubCommit = response.data; - }) - .catch(function () { - delete vm.data.githubCommit; - }); - } - }); - - /* - * Handle a drop event - */ - function dropHandler(items) { - vm.userInput.cfIgnoreFile = false; - // Find out what has been dropped and take appropriate action - itemDropHelper.identify(items).then(function (info) { - vm.userInput.localPath = info.value ? info.value.name : ''; - if (info.isFiles) { - vm.options.wizardCtrl.showBusy('deploy-app-dialog.step-info.scanning'); - itemDropHelper.traverseFiles(info.value, CF_IGNORE_FILE, CF_DEFAULT_IGNORES).then(function (results) { - vm.userInput.fileScanData = results; - vm.userInput.sourceType = 'local'; - vm.userInput.cfIgnoreFile = results.foundIgnoreFile; - }).finally(function () { - vm.options.wizardCtrl.showBusy(); - }); - } else if (info.isArchiveFile) { - vm.userInput.sourceType = 'local'; - var res = itemDropHelper.initScanner(); - vm.userInput.fileScanData = res.addFile(info.value); - vm.userInput.sourceType = 'local'; - } else if (info.isWebLink) { - // Check if this is a GitHub link - if (info.value.toLowerCase().indexOf(gitHubUrlBase) === 0) { - vm.userInput.sourceType = 'github'; - var urlParts = info.value.substring(gitHubUrlBase.length).split('/'); - if (urlParts.length > 1) { - var branch; - if (urlParts.length > 3 && urlParts[2] === 'tree') { - branch = urlParts[3]; - } - var project = urlParts[0] + '/' + urlParts[1]; - if (vm.userInput.githubProject === project) { - // Project is the same, so just change the branch - vm.selectBranch(branch ? branch : vm.data.githubProject.default_branch); - } else { - vm.userInput.autoSelectGithubBranch = branch; - vm.userInput.githubProject = project; - } - } - } - } - }); - } - - /* - * Not all browsers allows a folder to be selected by an input field - */ - function isInputDirSupported() { - /* eslint-disable angular/document-service */ - var tmpInput = document.createElement('input'); - /* eslint-enable angular/document-service */ - return 'webkitdirectory' in tmpInput; - } - - // Handle result of a file input form field selection - function handleFileInputSelect(items) { - // File list from a file input form field - var res, rootFolderName, cfIgnoreFile; - res = itemDropHelper.initScanner(CF_DEFAULT_IGNORES); - vm.userInput.cfIgnoreFile = false; - if (items.length === 1) { - if (itemDropHelper.isArchiveFile(items[0].name)) { - vm.userInput.fileScanData = res.addFile(items[0]); - vm.userInput.sourceType = 'local'; - vm.userInput.localPath = items[0].name; - } - } else { - // See if we can find the .cfignore file - for (var j = 0; j < items.length; j++) { - var filePath = items[j].webkitRelativePath.split('/'); - // First part is the root folder name - if (filePath.length === 2 && !rootFolderName) { - rootFolderName = filePath[0]; - } - if (filePath.length > 2) { - break; - } else if (filePath.length === 2 && filePath[1] === CF_IGNORE_FILE) { - cfIgnoreFile = items[j]; - break; - } - } - - var promise = $q.resolve(''); - // Did we find an ignore file? - if (cfIgnoreFile) { - promise = itemDropHelper.readFileContents(cfIgnoreFile); - } - - promise.then(function (ignores) { - res = itemDropHelper.initScanner(CF_DEFAULT_IGNORES + ignores); - vm.userInput.cfIgnoreFile = !!ignores; - res.rootFolderName = rootFolderName; - _.each(items, function (file) { - res.addFile(file); - }); - vm.userInput.fileScanData = res; - vm.userInput.sourceType = 'local'; - vm.userInput.localPath = rootFolderName || $translate.instant('deploy-app-dialog.step-info.local.folder'); - }); - } - } - - function createSocketUrl(serviceInstance, org, space) { - var protocol = $location.protocol() === 'https' ? 'wss' : 'ws'; - var url = protocol + '://' + $location.host() + ':' + $location.port(); - url += '/pp/v1/' + serviceInstance.guid + '/' + org.metadata.guid + '/' + space.metadata.guid + '/deploy'; - url += '?org=' + org.entity.name + '&space=' + space.entity.name; - return url; - } - - function resetSocket() { - if (vm.data.webSocket) { - vm.data.webSocket.onMessage = _.noop; - vm.data.webSocket.onClose = _.noop; - vm.data.webSocket.close(true); - } - } - - function discoverAppGuid(appName) { - if (discoverAppTimer) { - return; - } - - // Poll every 2 seconds to try and locate the app once it has been created - discoverAppTimer = $timeout(function () { - var spaceModel = modelManager.retrieve('cloud-foundry.model.space'); - var params = { - q: 'name:' + appName - }; - spaceModel.listAllAppsForSpace(vm.userInput.serviceInstance.guid, vm.userInput.space.metadata.guid, params) - .then(function (apps) { - if (apps.length === 1) { - newAppGuid = apps[0].metadata.guid; - } - }) - .finally(function () { - discoverAppTimer = undefined; - // Did not find the app - try again - if (!newAppGuid) { - discoverAppGuid(appName); - } - }); - }, DISCOVER_APP_TIMER_PERIOD); - } - - function logFilter(messageObj) { - if (messageObj.type !== socketEventTypes.DATA) { - return ''; - } - - return $filter('momentDateFormat')(messageObj.timestamp * 1000) + ': ' + messageObj.message.trim() + '\n'; - } - - function startDeploy() { - vm.data.deployStatus = vm.data.deployState.UNKNOWN; - hasPushStarted = false; - newAppGuid = null; - - var deployingPromise = $q.defer(); - - function pushStarted() { - hasPushStarted = true; - vm.data.deployStatus = vm.data.deployState.PUSHING; - deployingPromise.resolve(); - vm.data.uploadingFiles = undefined; - $log.debug('Deploy Application: Push Started'); - } - - function deploySuccessful() { - vm.data.deployStatus = vm.data.deployState.DEPLOYED; - $log.debug('Deploy Application: Deploy Successful'); - // Mark the wizard step as complete (so it gets a tick icon) - vm.options.workflow.steps[1].complete = true; - } - - function deployFailed(errorString) { - allowBack = true; - vm.data.deployStatus = vm.data.deployState.FAILED; - var failureDescription = $translate.instant(errorString); - vm.data.deployFailure = $translate.instant('deploy-app-dialog.step-deploying.title-deploy-failed', {reason: failureDescription}); - deployingPromise.reject(failureDescription); - $log.warn('Deploy Application: Failed: ' + failureDescription); - } - - function sendSourceMetadata() { - if (vm.userInput.sourceType === 'github') { - sendGitHubSourceMetadata(); - } else if (vm.userInput.sourceType === 'local') { - sendLocalSourceMetadata(); - } - } - - function sendGitHubSourceMetadata() { - var github = { - project:vm.userInput.githubProject, - branch: vm.userInput.githubBranch.name - }; - - var msg = { - message: angular.toJson(github), - timestamp: Math.round((new Date()).getTime() / 1000), - type: socketEventTypes.SOURCE_GITHUB - }; - - // Send the source metadata - vm.data.webSocket.send(angular.toJson(msg)); - } - - function sendLocalSourceMetadata() { - var metadata = { - files: [], - folders: [] - }; - - collectFoldersAndFiles(metadata, null, vm.userInput.fileScanData.root); - - vm.userInput.fileTransfers = metadata.files; - metadata.files = metadata.files.length; - vm.data.uploadingFiles = { - remaining: metadata.files, - bytes: 0, - total: vm.userInput.fileScanData.total, - fileName: '' - }; - - deployingPromise.resolve(); - - var msg = { - message: angular.toJson(metadata), - timestamp: Math.round((new Date()).getTime() / 1000), - type: socketEventTypes.SOURCE_FOLDER - }; - - // Send the source metadata - vm.data.webSocket.send(angular.toJson(msg)); - } - - function collectFoldersAndFiles(metadata, base, folder) { - _.each(folder.files, function (file) { - var filePath = base ? base + '/' + file.name : file.name; - file.fullPath = filePath; - metadata.files.push(file); - }); - - _.each(folder.folders, function (sub, name) { - var fullPath = base ? base + '/' + name : name; - metadata.folders.push(fullPath); - collectFoldersAndFiles(metadata, fullPath, sub); - }); - } - - function sendNextFile() { - if (vm.userInput.fileTransfers.length > 0) { - var file = vm.userInput.fileTransfers.shift(); - - // Send file metadata - var msg = { - message: file.fullPath, - timestamp: Math.round((new Date()).getTime() / 1000), - type: socketEventTypes.SOURCE_FILE - }; - - vm.data.uploadingFiles.fileName = file.fullPath; - - // Send the file name metadata - vm.data.webSocket.send(angular.toJson(msg)); - - // Now send the file data as a binary message - var reader = new FileReader(); - reader.onload = function (e) { - var output = e.target.result; - vm.data.webSocket.send(output); - vm.data.uploadingFiles.bytes += file.size; - }; - reader.readAsArrayBuffer(file); - } - } - - resetSocket(); - - // Determine web socket url and open connection - var socketUrl = createSocketUrl(vm.userInput.serviceInstance, vm.userInput.organization, vm.userInput.space); - - vm.data.webSocket = $websocket(socketUrl, null, { - reconnectIfNotNormalClose: false, - binaryType: 'arraybuffer' - }); - - // Handle Connection responses - vm.data.webSocket.onOpen(function () { - vm.data.deployStatus = vm.data.deployState.SOCKET_OPEN; - }); - - /* eslint-disable complexity */ - vm.data.webSocket.onMessage(function (message) { - var logData = angular.fromJson(message.data); - - switch (logData.type) { - case socketEventTypes.DATA: - // Ignore, handled by custom log viewer filter - break; - case socketEventTypes.CLOSE_FAILED_CLONE: - case socketEventTypes.CLOSE_FAILED_NO_BRANCH: - case socketEventTypes.CLOSE_FAILURE: - case socketEventTypes.CLOSE_INVALID_MANIFEST: - case socketEventTypes.CLOSE_NO_MANIFEST: - case socketEventTypes.CLOSE_PUSH_ERROR: - case socketEventTypes.CLOSE_NO_SESSION: - case socketEventTypes.CLOSE_NO_CNSI: - case socketEventTypes.CLOSE_NO_CNSI_USERTOKEN: - var type = _.findKey(socketEventTypes, function (type) { - return type === logData.type; - }); - deployFailed('deploy-app-dialog.socket.event-type.' + type); - break; - case socketEventTypes.CLOSE_SUCCESS: - deploySuccessful(); - break; - case socketEventTypes.EVENT_CLONED: - vm.data.deployStatus = vm.data.deployState.CLONED; - $log.debug('Deploy Application: Cloned'); - break; - case socketEventTypes.EVENT_FETCHED_MANIFEST: - vm.data.deployStatus = vm.data.deployState.FETCHED_MANIFEST; - $log.debug('Deploy Application: Fetched manifest'); - break; - case socketEventTypes.EVENT_PUSH_STARTED: - pushStarted(); - $log.debug('Deploy Application: Push Started'); - break; - case socketEventTypes.EVENT_PUSH_COMPLETED: - $log.debug('Deploy Application: Push Completed'); - break; - case socketEventTypes.MANIFEST: - var manifest = angular.fromJson(logData.message); - var app = _.get(manifest, 'Applications[0]', {}); - if (app.Name) { - discoverAppGuid(app.Name); - } - break; - case socketEventTypes.SOURCE_REQUIRED: - sendSourceMetadata(); - break; - case socketEventTypes.SOURCE_FILE_ACK: - sendNextFile(); - break; - default: - $log.error('Unknown deploy application socket event type: ', logData.type); - break; - } - - }); - /* eslint-enable complexity */ - - vm.data.webSocket.onClose(function () { - if (vm.data.deployStatus === vm.data.deployState.UNKNOWN) { - // Closed before socket has successfully opened - deployFailed('deploy-app-dialog.socket.failed-connection'); - } else if (vm.data.deployStatus !== vm.data.deployState.DEPLOYED && vm.data.deployStatus !== vm.data.deployState.FAILED) { - // Have connected to socket but not received a close message containing deploy result - deployFailed('deploy-app-dialog.socket.failed-unknown'); - } - }); - - return deployingPromise.promise; - } } })(); diff --git a/components/cf-app-push/frontend/src/view/deploy-app-workflow/deploy-app-deploying.html b/components/cf-app-push/frontend/src/view/deploy-app-workflow/deploy-step-deploying/deploy-step-deploying.html similarity index 56% rename from components/cf-app-push/frontend/src/view/deploy-app-workflow/deploy-app-deploying.html rename to components/cf-app-push/frontend/src/view/deploy-app-workflow/deploy-step-deploying/deploy-step-deploying.html index b4f00fe06d..26eb4bc342 100644 --- a/components/cf-app-push/frontend/src/view/deploy-app-workflow/deploy-app-deploying.html +++ b/components/cf-app-push/frontend/src/view/deploy-app-workflow/deploy-step-deploying/deploy-step-deploying.html @@ -1,8 +1,8 @@
- deploy-app-dialog.step-deploying.title-deploying - deploy-app-dialog.step-deploying.title-deploy-success - {{ step.data.deployFailure }} - + deploy-app-dialog.step-deploying.title-deploying + deploy-app-dialog.step-deploying.title-deploy-success + {{ step.data.deployFailure }} +
diff --git a/components/cf-app-push/frontend/src/view/deploy-app-workflow/deploy-step-deploying/deploy-step-deploying.scss b/components/cf-app-push/frontend/src/view/deploy-app-workflow/deploy-step-deploying/deploy-step-deploying.scss new file mode 100644 index 0000000000..7a9c634f78 --- /dev/null +++ b/components/cf-app-push/frontend/src/view/deploy-app-workflow/deploy-step-deploying/deploy-step-deploying.scss @@ -0,0 +1,23 @@ +$deploy-status-bounce-spacer: 6px; + +.deploy-app { + + .deploy-app-log-status { + display: flex; + align-items: center; + padding-bottom: $console-unit-space; + & > span { + font-size: $font-size-large; + padding-right: $console-unit-space; + } + bounce-spinner { + margin-top: $deploy-status-bounce-spacer; + } + } + + cf-log-viewer { + .panel-heading.action-header { + background-color: $console-primary-bg; + } + } +} \ No newline at end of file diff --git a/components/cf-app-push/frontend/src/view/deploy-app-workflow/deploy-step-deploying/deploy-step-deploying.service.js b/components/cf-app-push/frontend/src/view/deploy-app-workflow/deploy-step-deploying/deploy-step-deploying.service.js new file mode 100644 index 0000000000..612d3fc05b --- /dev/null +++ b/components/cf-app-push/frontend/src/view/deploy-app-workflow/deploy-step-deploying/deploy-step-deploying.service.js @@ -0,0 +1,352 @@ +(function () { + 'use strict'; + + angular + .module('cf-app-push') + .factory('appDeployStepDeployingService', AppDeployStepDeployingService); + + function AppDeployStepDeployingService($q, $location, $translate) { + + var socketEventTypes = { + DATA: 20000, + MANIFEST: 20001, + CLOSE_SUCCESS: 20002, + CLOSE_PUSH_ERROR: 40000, + CLOSE_NO_MANIFEST: 40001, + CLOSE_INVALID_MANIFEST: 40002, + CLOSE_FAILED_CLONE: 40003, + CLOSE_FAILED_NO_BRANCH: 40004, + CLOSE_FAILURE: 40005, + CLOSE_NO_SESSION: 40006, + CLOSE_NO_CNSI: 40007, + CLOSE_NO_CNSI_USERTOKEN: 40008, + EVENT_CLONED: 10000, + EVENT_FETCHED_MANIFEST: 10001, + EVENT_PUSH_STARTED: 10002, + EVENT_PUSH_COMPLETED: 10003, + SOURCE_REQUIRED: 30000, + SOURCE_GITHUB: 30001, + SOURCE_FOLDER: 30002, + SOURCE_FILE: 30003, + SOURCE_FILE_DATA: 30004, + SOURCE_FILE_ACK: 30005 + }; + + // How often to check for the app being created + var DISCOVER_APP_TIMER_PERIOD = 2000; + + var deployState = { + UNKNOWN: 1, + CLONED: 2, + FETCHED_MANIFEST: 3, + PUSHING: 4, + DEPLOYED: 5, + FAILED: 6, + SOCKET_OPEN: 7 + }; + + return { + getStep: function (session) { + var data = session.data.deploying; + var userInput = session.userInput.deploying; + + return { + step: { + title: 'deploy-app-dialog.step-deploying.title', + templateUrl: 'plugins/cf-app-push/view/deploy-app-workflow/deploy-step-deploying/deploy-step-deploying.html', + data: data, + userInput: userInput, + cancelBtnText: 'buttons.close', + nextBtnText: 'deploy-app-dialog.step-deploying.next-button', + showBusyOnEnter: 'deploy-app-dialog.step-deploying.busy', + allowNext: function () { + return !!session.wizard.newAppGuid; + }, + onEnter: function () { + session.wizard.allowBack = false; + return startDeploy(data, userInput).catch(function (error) { + session.wizard.allowBack = true; + return $q.reject($translate.instant('deploy-app-dialog.step-deploying.submit-failed', {reason: error})); + }); + }, + isLastStep: true, + logFilter: logFilter, + deployState: deployState + }, + destroy: function () { + //TODO: RC + $timeout.cancel(session.data.discoverAppTimer); + resetSocket(session.data.webSocket); + } + }; + } + }; + + function discoverAppGuid(data, userInput, appName) { + if (data.discoverAppTimer) { + return; + } + + // Poll every 2 seconds to try and locate the app once it has been created + data.discoverAppTimer = $timeout(function () { + var spaceModel = modelManager.retrieve('cloud-foundry.model.space'); + var params = { + q: 'name:' + appName + }; + spaceModel.listAllAppsForSpace(userInput.serviceInstance.guid, userInput.space.metadata.guid, params) + .then(function (apps) { + if (apps.length === 1) { + session.wizard.newAppGuid = apps[0].metadata.guid; + } + }) + .finally(function () { + data.discoverAppTimer = undefined; + // Did not find the app - try again + if (!session.wizard.newAppGuid) { + discoverAppGuid(data, userInput, appName); + } + }); + }, DISCOVER_APP_TIMER_PERIOD); + } + + function logFilter(messageObj) { + if (messageObj.type !== socketEventTypes.DATA) { + return ''; + } + + return $filter('momentDateFormat')(messageObj.timestamp * 1000) + ': ' + messageObj.message.trim() + '\n'; + } + + function createSocketUrl(serviceInstance, org, space) { + var protocol = $location.protocol() === 'https' ? 'wss' : 'ws'; + var url = protocol + '://' + $location.host() + ':' + $location.port(); + url += '/pp/v1/' + serviceInstance.guid + '/' + org.metadata.guid + '/' + space.metadata.guid + '/deploy'; + url += '?org=' + org.entity.name + '&space=' + space.entity.name; + return url; + } + + function startDeploy(data, userInput) { + + data.deployStatus = data.deployState.UNKNOWN; + session.wizard.hasPushStarted = false; + session.wizard.newAppGuid = null; + + var deployingPromise = $q.defer(); + + function pushStarted() { + session.wizard.hasPushStarted = true; + data.deployStatus = data.deployState.PUSHING; + deployingPromise.resolve(); + data.uploadingFiles = undefined; + $log.debug('Deploy Application: Push Started'); + } + + function deploySuccessful() { + data.deployStatus = data.deployState.DEPLOYED; + $log.debug('Deploy Application: Deploy Successful'); + // Mark the wizard step as complete (so it gets a tick icon) + vm.options.workflow.steps[1].complete = true; + } + + function deployFailed(errorString) { + session.wizard.allowBack = true; + data.deployStatus = data.deployState.FAILED; + var failureDescription = $translate.instant(errorString); + data.deployFailure = $translate.instant('deploy-app-dialog.step-deploying.title-deploy-failed', {reason: failureDescription}); + deployingPromise.reject(failureDescription); + $log.warn('Deploy Application: Failed: ' + failureDescription); + } + + function sendSourceMetadata() { + if (userInput.sourceType === 'github') { + sendGitHubSourceMetadata(); + } else if (userInput.sourceType === 'local') { + sendLocalSourceMetadata(); + } + } + + function sendGitHubSourceMetadata() { + var github = { + project: userInput.githubProject, + branch: userInput.githubBranch.name + }; + + var msg = { + message: angular.toJson(github), + timestamp: Math.round((new Date()).getTime() / 1000), + type: socketEventTypes.SOURCE_GITHUB + }; + + // Send the source metadata + data.webSocket.send(angular.toJson(msg)); + } + + function sendLocalSourceMetadata() { + var metadata = { + files: [], + folders: [] + }; + + collectFoldersAndFiles(metadata, null, userInput.fileScanData.root); + + userInput.fileTransfers = metadata.files; + metadata.files = metadata.files.length; + data.uploadingFiles = { + remaining: metadata.files, + bytes: 0, + total: userInput.fileScanData.total, + fileName: '' + }; + + deployingPromise.resolve(); + + var msg = { + message: angular.toJson(metadata), + timestamp: Math.round((new Date()).getTime() / 1000), + type: socketEventTypes.SOURCE_FOLDER + }; + + // Send the source metadata + data.webSocket.send(angular.toJson(msg)); + } + + function collectFoldersAndFiles(metadata, base, folder) { + _.each(folder.files, function (file) { + file.fullPath = base ? base + '/' + file.name : file.name; + metadata.files.push(file); + }); + + _.each(folder.folders, function (sub, name) { + var fullPath = base ? base + '/' + name : name; + metadata.folders.push(fullPath); + collectFoldersAndFiles(metadata, fullPath, sub); + }); + } + + function sendNextFile() { + if (userInput.fileTransfers.length > 0) { + var file = userInput.fileTransfers.shift(); + + // Send file metadata + var msg = { + message: file.fullPath, + timestamp: Math.round((new Date()).getTime() / 1000), + type: socketEventTypes.SOURCE_FILE + }; + + data.uploadingFiles.fileName = file.fullPath; + + // Send the file name metadata + data.webSocket.send(angular.toJson(msg)); + + // Now send the file data as a binary message + var reader = new FileReader(); + reader.onload = function (e) { + var output = e.target.result; + data.webSocket.send(output); + data.uploadingFiles.bytes += file.size; + }; + reader.readAsArrayBuffer(file); + } + } + + resetSocket(data.webSocket); + + // Determine web socket url and open connection + var socketUrl = createSocketUrl(userInput.serviceInstance, userInput.organization, userInput.space); + + data.webSocket = $websocket(socketUrl, null, { + reconnectIfNotNormalClose: false, + binaryType: 'arraybuffer' + }); + + // Handle Connection responses + data.webSocket.onOpen(function () { + data.deployStatus = data.deployState.SOCKET_OPEN; + }); + + /* eslint-disable complexity */ + data.webSocket.onMessage(function (message) { + var logData = angular.fromJson(message.data); + + switch (logData.type) { + case socketEventTypes.DATA: + // Ignore, handled by custom log viewer filter + break; + case socketEventTypes.CLOSE_FAILED_CLONE: + case socketEventTypes.CLOSE_FAILED_NO_BRANCH: + case socketEventTypes.CLOSE_FAILURE: + case socketEventTypes.CLOSE_INVALID_MANIFEST: + case socketEventTypes.CLOSE_NO_MANIFEST: + case socketEventTypes.CLOSE_PUSH_ERROR: + case socketEventTypes.CLOSE_NO_SESSION: + case socketEventTypes.CLOSE_NO_CNSI: + case socketEventTypes.CLOSE_NO_CNSI_USERTOKEN: + var type = _.findKey(socketEventTypes, function (type) { + return type === logData.type; + }); + deployFailed('deploy-app-dialog.socket.event-type.' + type); + break; + case socketEventTypes.CLOSE_SUCCESS: + deploySuccessful(); + break; + case socketEventTypes.EVENT_CLONED: + data.deployStatus = data.deployState.CLONED; + $log.debug('Deploy Application: Cloned'); + break; + case socketEventTypes.EVENT_FETCHED_MANIFEST: + data.deployStatus = data.deployState.FETCHED_MANIFEST; + $log.debug('Deploy Application: Fetched manifest'); + break; + case socketEventTypes.EVENT_PUSH_STARTED: + pushStarted(); + $log.debug('Deploy Application: Push Started'); + break; + case socketEventTypes.EVENT_PUSH_COMPLETED: + $log.debug('Deploy Application: Push Completed'); + break; + case socketEventTypes.MANIFEST: + var manifest = angular.fromJson(logData.message); + var app = _.get(manifest, 'Applications[0]', {}); + if (app.Name) { + discoverAppGuid(data, userInput, app.Name); + } + break; + case socketEventTypes.SOURCE_REQUIRED: + sendSourceMetadata(); + break; + case socketEventTypes.SOURCE_FILE_ACK: + sendNextFile(); + break; + default: + $log.error('Unknown deploy application socket event type: ', logData.type); + break; + } + + }); + /* eslint-enable complexity */ + + data.webSocket.onClose(function () { + if (data.deployStatus === data.deployState.UNKNOWN) { + // Closed before socket has successfully opened + deployFailed('deploy-app-dialog.socket.failed-connection'); + } else if (data.deployStatus !== data.deployState.DEPLOYED && data.deployStatus !== data.deployState.FAILED) { + // Have connected to socket but not received a close message containing deploy result + deployFailed('deploy-app-dialog.socket.failed-unknown'); + } + }); + + return deployingPromise.promise; + } + + function resetSocket(webSocket) { + if (webSocket) { + webSocket.onMessage = _.noop; + webSocket.onClose = _.noop; + webSocket.close(true); + } + } + + } + +})(); diff --git a/components/cf-app-push/frontend/src/view/deploy-app-workflow/deploy-app-destination.html b/components/cf-app-push/frontend/src/view/deploy-app-workflow/deploy-step-destination/deploy-step-destination.html similarity index 100% rename from components/cf-app-push/frontend/src/view/deploy-app-workflow/deploy-app-destination.html rename to components/cf-app-push/frontend/src/view/deploy-app-workflow/deploy-step-destination/deploy-step-destination.html diff --git a/components/cf-app-push/frontend/src/view/deploy-app-workflow/deploy-step-destination/deploy-step-destination.scss b/components/cf-app-push/frontend/src/view/deploy-app-workflow/deploy-step-destination/deploy-step-destination.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/components/cf-app-push/frontend/src/view/deploy-app-workflow/deploy-step-destination/deploy-step-destination.service.js b/components/cf-app-push/frontend/src/view/deploy-app-workflow/deploy-step-destination/deploy-step-destination.service.js new file mode 100644 index 0000000000..04bce5093b --- /dev/null +++ b/components/cf-app-push/frontend/src/view/deploy-app-workflow/deploy-step-destination/deploy-step-destination.service.js @@ -0,0 +1,61 @@ +(function () { + 'use strict'; + + angular + .module('cf-app-push') + .factory('appDeployStepDestinationService', AppDeployStepDestinationService); + + function AppDeployStepDestinationService($q, modelManager) { + + return { + getStep: function (session) { + var serviceInstanceModel = modelManager.retrieve('app.model.serviceInstance.user'); + var authModel = modelManager.retrieve('cloud-foundry.model.auth'); + + var data = session.data.destination; + var userInput = session.userInput.destination; + + data.serviceInstances = []; + userInput.serviceInstance = null; + userInput.organization = null; + userInput.space = null; + + return { + step: { + title: 'deploy-app-dialog.step-destination.title', + templateUrl: 'plugins/cf-app-push/view/deploy-app-workflow/deploy-step-destination/deploy-step-destination.html', + formName: 'deploy-destination-form', + data: data, + userInput: userInput, + showBusyOnEnter: 'deploy-app-dialog.step-destination.busy', + onEnter: function () { + if (data.deployStatus) { + // Previously been at this step, no need to fetch instances again + return; + } + return serviceInstanceModel.list() + .then(function (serviceInstances) { + var validServiceInstances = _.chain(_.values(serviceInstances)) + .filter({cnsi_type: 'cf', valid: true}) + .filter(function (cnsi) { + return authModel.doesUserHaveRole(cnsi.guid, authModel.roles.space_developer); + }) + .map(function (o) { + return {label: o.name, value: o}; + }) + .value(); + [].push.apply(data.serviceInstances, validServiceInstances); + }).catch(function () { + return $q.reject('deploy-app-dialog.step-destination.enter-failed'); + }); + } + }, + destroy: function () { + //TODO: RC + } + }; + } + }; + } + +})(); diff --git a/components/cf-app-push/frontend/src/view/deploy-app-workflow/deploy-step-source/deploy-source-git/deploy-source-git.directive.js b/components/cf-app-push/frontend/src/view/deploy-app-workflow/deploy-step-source/deploy-source-git/deploy-source-git.directive.js new file mode 100644 index 0000000000..542d785d8e --- /dev/null +++ b/components/cf-app-push/frontend/src/view/deploy-app-workflow/deploy-step-source/deploy-source-git/deploy-source-git.directive.js @@ -0,0 +1,115 @@ +(function () { + 'use strict'; + + angular + .module('cf-app-push') + .directive('appDeploySourceGit', DeploySourceGit); + + /** + * @namespace cf-app-push.accountActions + * @memberof cf-app-push + * @name DeploySourceGit + * @description ???????? + * @returns {object} The ???????? directive definition object + */ + function DeploySourceGit() { + return { + scope: { + userInput: '=', + data: '=', + formName: '@', + valid: '=' + }, + bindToController: true, + controller: DeploySourceGitController, + controllerAs: 'dplyGitCtrl', + templateUrl: 'plugins/cf-app-push/view/deploy-app-workflow/deploy-step-source/deploy-source-git/deploy-source-git.html' + }; + } + + /** + * @namespace cf-app-push.DeploySourceGitController + * @memberof cf-app-push + * @name DeploySourceGitController + * @param {app.model.modelManager} modelManager - the application model manager + * @property {app.model.consoleInfo} consoleInfo - the consoleInfo model + * @constructor + */ + function DeploySourceGitController($http, $scope) { + var vm = this; + + vm.isValid = isValid; + + var debounceGithubProjectFetch = _.debounce(function () { + var project = vm.userInput.githubProject; + if (!project || project.length === 0) { + vm.userInput.githubProjectValid = false; + return; + } + + $http.get('https://api.github.com/repos/' + project) + .then(function (response) { + vm.userInput.githubProjectValid = true; + vm.data.githubProject = response.data; + vm.userInput.githubProjectCached = project; + + $http.get('https://api.github.com/repos/' + project + '/branches') + .then(function (response) { + vm.data.githubBranches.length = 0; + [].push.apply(vm.data.githubBranches, _.map(response.data, function selectOptionMapping(o) { + return { + label: o.name, + value: o + }; + })); + + var branch = vm.userInput.autoSelectGithubBranch ? vm.userInput.autoSelectGithubBranch : vm.githubProject.default_branch; + vm.userInput.autoSelectGithubBranch = undefined; + var foundBranch = _.find(vm.data.githubBranches, function (o) { + return o.value && o.value.name === branch; + }); + vm.userInput.githubBranch = foundBranch ? foundBranch.value : undefined; + }) + .catch(function () { + vm.data.githubBranches.length = 0; + }); + + }) + .catch(function (response) { + if (response.status === 404) { + vm.userInput.githubProjectValid = false; + vm.data.githubBranches.length = 0; + delete vm.userInput.githubBranch; + delete vm.data.githubCommit; + } + }); + }, 1000); + + $scope.$watch(function () { + return vm.userInput.githubProject; + }, function (oldVal, newVal) { + if (oldVal !== newVal) { + debounceGithubProjectFetch(); + } + }); + + $scope.$watch(function () { + return vm.userInput.githubBranch; + }, function (newVal, oldVal) { + if (newVal && oldVal !== newVal) { + $http.get('https://api.github.com/repos/' + vm.userInput.githubProject + '/commits/' + newVal.commit.sha) + .then(function (response) { + vm.data.githubCommit = response.data; + }) + .catch(function () { + delete vm.data.githubCommit; + }); + } + }); + + function isValid() { + return vm.userInput.githubProjectValid; + } + } + +})(); diff --git a/components/cf-app-push/frontend/src/view/deploy-app-workflow/deploy-step-source/deploy-source-git/deploy-source-git.html b/components/cf-app-push/frontend/src/view/deploy-app-workflow/deploy-step-source/deploy-source-git/deploy-source-git.html new file mode 100644 index 0000000000..234d7ac79d --- /dev/null +++ b/components/cf-app-push/frontend/src/view/deploy-app-workflow/deploy-step-source/deploy-source-git/deploy-source-git.html @@ -0,0 +1,65 @@ +
+
+
+ + + deploy-app-dialog.step-source.github.project-not-found + + +
+ +
+ + + +
+ + +
+
+
+ +
+ + {{ dplyGitCtrl.githubProject.description | limitTo:50}} ... +
+
+ +
+
+ +
+
+ +
+
+ deploy-app-dialog.step-source.github.branch-last-commit-committer{{ dplyGitCtrl.data.githubCommit.author.login || dplyGitCtrl.data.githubCommit.commit.author.name}} +
+
+ deploy-app-dialog.step-source.github.branch-last-commit-date {{ dplyGitCtrl.data.githubCommit.commit.author.date | momentDateFormat }} +
+
+
+
+
+
+
+
\ No newline at end of file diff --git a/components/cf-app-push/frontend/src/view/deploy-app-workflow/deploy-step-source/deploy-source-local/deploy-source-local.directive.js b/components/cf-app-push/frontend/src/view/deploy-app-workflow/deploy-step-source/deploy-source-local/deploy-source-local.directive.js new file mode 100644 index 0000000000..e69de29bb2 diff --git a/components/cf-app-push/frontend/src/view/deploy-app-workflow/deploy-step-source/deploy-source-local/deploy-source-local.html b/components/cf-app-push/frontend/src/view/deploy-app-workflow/deploy-step-source/deploy-source-local/deploy-source-local.html new file mode 100644 index 0000000000..e69de29bb2 diff --git a/components/cf-app-push/frontend/src/view/deploy-app-workflow/deploy-app-bits.html b/components/cf-app-push/frontend/src/view/deploy-app-workflow/deploy-step-source/deploy-step-source.html similarity index 56% rename from components/cf-app-push/frontend/src/view/deploy-app-workflow/deploy-app-bits.html rename to components/cf-app-push/frontend/src/view/deploy-app-workflow/deploy-step-source/deploy-step-source.html index 424d8586c6..0084d9b3d9 100644 --- a/components/cf-app-push/frontend/src/view/deploy-app-workflow/deploy-app-bits.html +++ b/components/cf-app-push/frontend/src/view/deploy-app-workflow/deploy-step-source/deploy-step-source.html @@ -24,71 +24,12 @@
-
-
-
- - - deploy-app-dialog.step-source.github.project-not-found - - -
- -
- - - -
- - -
-
-
- -
- - {{ step.data.githubProject.description | limitTo:50}} ... -
-
- -
-
- -
-
- -
-
- deploy-app-dialog.step-source.github.branch-last-commit-committer{{ step.data.githubCommit.author.login || step.data.githubCommit.commit.author.name}} -
-
- deploy-app-dialog.step-source.github.branch-last-commit-date {{ step.data.githubCommit.commit.author.date | momentDateFormat }} -
-
-
-
-
-
-
-
+ +
diff --git a/components/cf-app-push/frontend/src/view/deploy-app-workflow/deploy-step-source/deploy-step-source.scss b/components/cf-app-push/frontend/src/view/deploy-app-workflow/deploy-step-source/deploy-step-source.scss new file mode 100644 index 0000000000..214492c659 --- /dev/null +++ b/components/cf-app-push/frontend/src/view/deploy-app-workflow/deploy-step-source/deploy-step-source.scss @@ -0,0 +1,253 @@ +$github-avatar-height: 51px; +$github-avatar-width: 51px; +$github-info-commit-spacer: 3px; + +// Select option height is text size + margin +$select-option-height: 39px; + +.deploy-app { + deploy-location { + .control-title { + margin-top: 0; + } + } + + #deploy-app-choose-bits { + display: none; + } + + // Applies to buttons to choose a file or folder + .form-group.deploy-app-file-controls { + padding-right: 0; + + .form-control.file-chooser { + width: 0.1px; + height: 0.1px; + opacity: 0; + overflow: hidden; + position: absolute; + z-index: -1; + + + label { + border: none; + background-color: transparent; + padding: 0; + height: 19px; + line-height: 19px; + font-size: $font-size-medium; + margin: 0; + display: flex; + + .deploy-app-form-btn { + height: $console-input-height; + width: $console-unit-space * 2; + display: flex; + justify-content: center; + align-items: center; + margin-top: -25px; + border-right: 1px solid $rule; + + &:hover { + background-color: $component-active-bg; + color: $component-active-color; + } + + &:first-of-type { + border-left: 1px solid $rule; + } + + &:last-child { + border-right: 0; + } + } + + &.clickable { + cursor: pointer; + } + + > i { + font-size: $icon-font-size-base; + margin-right: 6px; + } + + > span { + margin: 0; + padding: 0; + flex: 1; + } + + > label { + margin: 0; + cursor: pointer; + } + } + } + } + + p.deploy-app-choose-source { + margin: $console-unit-space * 0.5 0; + } + + .deploy-app-source-area { + display: flex; + + > .deploy-app-source { + display: flex; + flex: 1; + + > div { + flex: 1; + } + } + + p.control-title { + margin-top: 0; + } + + radio-input { + margin-bottom: $console-unit-space / 4; + } + + &.file-drop-active { + outline: 3px dashed $brand-primary; + outline-offset: 4px; + + > * { + opacity: 0.5; + } + } + + .deploy-app-source-details { + + &.deploy-app-invisible { + visibility: hidden; + } + + &.deploy-app-github-details { + margin-top: $console-unit-space / 2; + + .deploy-app-github-info { + border: 1px solid $rule; + padding: $console-unit-space / 4 $console-unit-space / 2; + max-width: $console-input-width * 1.25; + + &.deploy-app-file-details { + height: $console-input-height; + display: flex; + align-items: center; + padding-left: 18px; + + > p { + margin: 0; + } + + > i { + margin-right: 6px; + } + } + + img { + height: $github-avatar-height; + width: $github-avatar-width; + } + + .project { + display: flex; + flex-direction: row; + align-items: center; + + .project-info { + padding-left: $console-unit-space / 2; + } + } + + .lastCommit { + display: flex; + flex-direction: row; + align-items: center; + overflow-x: hidden; + + padding-top: $github-info-commit-spacer; + + .lastCommit-info { + padding-left: $console-unit-space / 2; + flex: 1; + display: flex; + flex-direction: column; + overflow-x: hidden; + + > .lastCommit-info-commit-message { + display: flex; + + > a { + text-overflow: ellipsis; + white-space: nowrap; + overflow-x: hidden; + } + } + + .lastCommit-date-committer { + display: flex; + + > .lastCommitter { + flex: 1; + } + } + } + } + } + + } + + .deploy-app-found-cfignore { + padding: 6px 20px; + border: 1px solid $rule; + margin-top: -1px; + > p { + margin :0; + } + } + } + } + + .deploy-app-github { + display: flex; + + .deploy-app-github-form { + .deploy-app-github-branch { + //margin-bottom: $select-option-height * 3 - $console-unit-space; + .dropdown-menu { + max-height: $select-option-height * 3; + } + } + } + } + + .folder-drop-progress { + display: flex; + align-items: center; + justify-content: center; + } + + percent-gauge.deploy-app-file-progress { + + .percent-gauge .percent-gauge-bar:after { + background-image: none; + } + + .percent-gauge .percent-gauge-bar .percent-gauge-utilized-bar { + background-color: $brand-primary; + + &.percent-gauge-at-max { + background-color: $brand-primary; + } + } + } + + .deploy-app-file-upload-title { + margin-bottom: $console-unit-space; + font-size: $font-size-large1; + font-weight: $console-font-weight-semibold; + } +} + diff --git a/components/cf-app-push/frontend/src/view/deploy-app-workflow/deploy-step-source/deploy-step-source.service.js b/components/cf-app-push/frontend/src/view/deploy-app-workflow/deploy-step-source/deploy-step-source.service.js new file mode 100644 index 0000000000..7f95cf8a41 --- /dev/null +++ b/components/cf-app-push/frontend/src/view/deploy-app-workflow/deploy-step-source/deploy-step-source.service.js @@ -0,0 +1,172 @@ +(function () { + 'use strict'; + + angular + .module('cf-app-push') + .factory('appDeployStepSourceService', AppDeployStepSourceService); + + function AppDeployStepSourceService($q, $translate, appUtilsService) { + + var CF_IGNORE_FILE = '.cfignore'; + var CF_DEFAULT_IGNORES = '.cfignore\n_darcs\n.DS_Store\n.git\n.gitignore\n.hg\n.svn\n'; + + var gitHubUrlBase = 'https://github.com/'; + + return { + getStep: function (session, $scope) { + + // Watch for the file or folder being selected by the input field and process + $scope.$watch(function () { + return session.userInput.localPathFile; + }, function (newVal, oldVal) { + if (newVal && oldVal !== newVal) { + handleFileInputSelect(newVal); + } + }); + + var data = session.data.source; + var userInput = session.userInput.source; + + data.githubBranches = []; + userInput.githubProject = ''; + userInput.localPath = ''; + userInput.localPathFile = ''; + userInput.fileScanData = ''; + + return { + step: { + title: 'deploy-app-dialog.step-source.title', + templateUrl: 'plugins/cf-app-push/view/deploy-app-workflow/deploy-step-source/deploy-step-source.html', + formName: 'deploy-info-form', + data: data, + userInput: userInput, + folderSupport: isInputDirSupported(), + showBusyOnEnter: 'deploy-app-dialog.step-source.busy', + nextBtnText: 'deploy-app-dialog.button-deploy', + stepCommit: true, + bytesToHumanSize: appUtilsService.bytesToHumanSize, + allowNext: function () { + //TODO: RC wire in properly + return userInput.sourceType === 'github' && userInput.git.isValid() || userInput.sourceType === 'local' && userInput.fileScanData; + }, + dropItemHandler: dropHandler + }, + destroy: angular.noop + }; + } + }; + + function selectBranch(data, userInput, branch) { + var foundBranch = _.find(data.githubBranches, function (o) { + return o.value && o.value.name === branch; + }); + userInput.githubBranch = foundBranch ? foundBranch.value : undefined; + } + + /* + * Handle a drop event + */ + function dropHandler(data, userInput, items) { + userInput.cfIgnoreFile = false; + // Find out what has been dropped and take appropriate action + itemDropHelper.identify(items).then(function (info) { + userInput.localPath = info.value ? info.value.name : ''; + if (info.isFiles) { + vm.options.wizardCtrl.showBusy('deploy-app-dialog.step-info.scanning'); + itemDropHelper.traverseFiles(info.value, CF_IGNORE_FILE, CF_DEFAULT_IGNORES).then(function (results) { + userInput.fileScanData = results; + userInput.sourceType = 'local'; + userInput.cfIgnoreFile = results.foundIgnoreFile; + }).finally(function () { + vm.options.wizardCtrl.showBusy(); + }); + } else if (info.isArchiveFile) { + userInput.sourceType = 'local'; + var res = itemDropHelper.initScanner(); + userInput.fileScanData = res.addFile(info.value); + userInput.sourceType = 'local'; + } else if (info.isWebLink) { + // Check if this is a GitHub link + if (info.value.toLowerCase().indexOf(gitHubUrlBase) === 0) { + userInput.sourceType = 'github'; + var urlParts = info.value.substring(gitHubUrlBase.length).split('/'); + if (urlParts.length > 1) { + var branch; + if (urlParts.length > 3 && urlParts[2] === 'tree') { + branch = urlParts[3]; + } + var project = urlParts[0] + '/' + urlParts[1]; + if (userInput.githubProject === project) { + // Project is the same, so just change the branch + selectBranch(branch ? branch : data.githubProject.default_branch); + } else { + userInput.autoSelectGithubBranch = branch; + userInput.githubProject = project; + } + } + } + } + }); + } + + /* + * Not all browsers allows a folder to be selected by an input field + */ + function isInputDirSupported() { + /* eslint-disable angular/document-service */ + var tmpInput = document.createElement('input'); + /* eslint-enable angular/document-service */ + return 'webkitdirectory' in tmpInput; + } + + // Handle result of a file input form field selection + function handleFileInputSelect(data, userInput, items) { + // File list from a file input form field + var res, rootFolderName, cfIgnoreFile; + res = itemDropHelper.initScanner(CF_DEFAULT_IGNORES); + userInput.cfIgnoreFile = false; + if (items.length === 1) { + if (itemDropHelper.isArchiveFile(items[0].name)) { + userInput.fileScanData = res.addFile(items[0]); + userInput.sourceType = 'local'; + userInput.localPath = items[0].name; + } + } else { + // See if we can find the .cfignore file + for (var j = 0; j < items.length; j++) { + var filePath = items[j].webkitRelativePath.split('/'); + // First part is the root folder name + if (filePath.length === 2 && !rootFolderName) { + rootFolderName = filePath[0]; + } + if (filePath.length > 2) { + break; + } else if (filePath.length === 2 && filePath[1] === CF_IGNORE_FILE) { + cfIgnoreFile = items[j]; + break; + } + } + + var promise = $q.resolve(''); + // Did we find an ignore file? + if (cfIgnoreFile) { + promise = itemDropHelper.readFileContents(cfIgnoreFile); + } + + promise.then(function (ignores) { + res = itemDropHelper.initScanner(CF_DEFAULT_IGNORES + ignores); + userInput.cfIgnoreFile = !!ignores; + res.rootFolderName = rootFolderName; + _.each(items, function (file) { + res.addFile(file); + }); + userInput.fileScanData = res; + userInput.sourceType = 'local'; + userInput.localPath = rootFolderName || $translate.instant('deploy-app-dialog.step-info.local.folder'); + }); + } + } + + } + +})(); From bc00aaf0292e210b1061bf940e11b646920947d9 Mon Sep 17 00:00:00 2001 From: Richard Cox Date: Fri, 28 Jul 2017 12:04:07 +0100 Subject: [PATCH 3/7] UX for source mostly works now. Still plenty of tidy up to do WIP --- .../src/widgets/wizard/wizard.directive.js | 3 +- .../deploy-app-workflow/deploy-app.service.js | 8 +- .../deploy-step-deploying.service.js | 10 +- .../deploy-source-git.directive.js | 50 +++++- .../deploy-source-git/deploy-source-git.html | 13 +- .../deploy-source-local.directive.js | 142 ++++++++++++++++++ .../deploy-source-local.html | 38 +++++ .../deploy-step-source.html | 99 +++--------- .../deploy-step-source.service.js | 132 +++------------- 9 files changed, 284 insertions(+), 211 deletions(-) diff --git a/components/app-framework/src/widgets/wizard/wizard.directive.js b/components/app-framework/src/widgets/wizard/wizard.directive.js index d5d3e4c1f6..f22b980876 100644 --- a/components/app-framework/src/widgets/wizard/wizard.directive.js +++ b/components/app-framework/src/widgets/wizard/wizard.directive.js @@ -76,6 +76,8 @@ vm.hasMessage = false; vm.busyMessage = false; + vm.showBusy = showBusy; + if (vm.workflow.initControllers) { vm.workflow.initControllers(vm); } @@ -144,7 +146,6 @@ vm.switchToFirstReadyStep = switchToFirstReadyStep; vm.always = always; vm.allowCancel = allowCancel; - vm.showBusy = showBusy; vm.initPromise.then(function () { vm.onInitSuccess(); diff --git a/components/cf-app-push/frontend/src/view/deploy-app-workflow/deploy-app.service.js b/components/cf-app-push/frontend/src/view/deploy-app-workflow/deploy-app.service.js index 16d7d8a901..ee23dc89a6 100644 --- a/components/cf-app-push/frontend/src/view/deploy-app-workflow/deploy-app.service.js +++ b/components/cf-app-push/frontend/src/view/deploy-app-workflow/deploy-app.service.js @@ -101,18 +101,18 @@ source: {} }, userInput: { - sourceType: 'github', deploying: {}, destination: {}, source: {} }, wizard: { - allowBack: true + allowBack: true, + sourceType: 'github' } }; var stepLocation = appDeployStepDestinationService.getStep(session); - var stepSource = appDeployStepSourceService.getStep(session, $scope); + var stepSource = appDeployStepSourceService.getStep(session); var stepDeploy = appDeployStepDeployingService.getStep(session); function destroy() { @@ -125,7 +125,7 @@ vm.options = { workflow: { initControllers: function (wizardCtrl) { - vm.options.wizardCtrl = wizardCtrl; + session.showBusy = wizardCtrl.showBusy; }, disableJump: true, allowCancelAtLastStep: true, diff --git a/components/cf-app-push/frontend/src/view/deploy-app-workflow/deploy-step-deploying/deploy-step-deploying.service.js b/components/cf-app-push/frontend/src/view/deploy-app-workflow/deploy-step-deploying/deploy-step-deploying.service.js index 612d3fc05b..444f64cf3f 100644 --- a/components/cf-app-push/frontend/src/view/deploy-app-workflow/deploy-step-deploying/deploy-step-deploying.service.js +++ b/components/cf-app-push/frontend/src/view/deploy-app-workflow/deploy-step-deploying/deploy-step-deploying.service.js @@ -5,7 +5,7 @@ .module('cf-app-push') .factory('appDeployStepDeployingService', AppDeployStepDeployingService); - function AppDeployStepDeployingService($q, $location, $translate) { + function AppDeployStepDeployingService($q, $location, $translate, $timeout) { var socketEventTypes = { DATA: 20000, @@ -64,7 +64,7 @@ }, onEnter: function () { session.wizard.allowBack = false; - return startDeploy(data, userInput).catch(function (error) { + return startDeploy(session, data, userInput).catch(function (error) { session.wizard.allowBack = true; return $q.reject($translate.instant('deploy-app-dialog.step-deploying.submit-failed', {reason: error})); }); @@ -125,7 +125,7 @@ return url; } - function startDeploy(data, userInput) { + function startDeploy(session, data, userInput) { data.deployStatus = data.deployState.UNKNOWN; session.wizard.hasPushStarted = false; @@ -158,9 +158,9 @@ } function sendSourceMetadata() { - if (userInput.sourceType === 'github') { + if (session.wizard.sourceType === 'github') { sendGitHubSourceMetadata(); - } else if (userInput.sourceType === 'local') { + } else if (session.wizard.sourceType === 'local') { sendLocalSourceMetadata(); } } diff --git a/components/cf-app-push/frontend/src/view/deploy-app-workflow/deploy-step-source/deploy-source-git/deploy-source-git.directive.js b/components/cf-app-push/frontend/src/view/deploy-app-workflow/deploy-step-source/deploy-source-git/deploy-source-git.directive.js index 542d785d8e..42d02b0674 100644 --- a/components/cf-app-push/frontend/src/view/deploy-app-workflow/deploy-step-source/deploy-source-git/deploy-source-git.directive.js +++ b/components/cf-app-push/frontend/src/view/deploy-app-workflow/deploy-step-source/deploy-source-git/deploy-source-git.directive.js @@ -15,10 +15,12 @@ function DeploySourceGit() { return { scope: { + sourceType: '=', userInput: '=', data: '=', formName: '@', - valid: '=' + valid: '=', + dropInfo: '=' }, bindToController: true, controller: DeploySourceGitController, @@ -38,7 +40,14 @@ function DeploySourceGitController($http, $scope) { var vm = this; - vm.isValid = isValid; + $scope.$watch(function () { + //TODO: RC Improve - this should be valid if it has all the userInput fields required + return vm.userInput.githubProjectValid; + }, function () { + vm.valid = vm.userInput.githubProjectValid; + }); + + var gitHubUrlBase = 'https://github.com/'; var debounceGithubProjectFetch = _.debounce(function () { var project = vm.userInput.githubProject; @@ -63,7 +72,7 @@ }; })); - var branch = vm.userInput.autoSelectGithubBranch ? vm.userInput.autoSelectGithubBranch : vm.githubProject.default_branch; + var branch = vm.userInput.autoSelectGithubBranch ? vm.userInput.autoSelectGithubBranch : vm.data.githubProject.default_branch; vm.userInput.autoSelectGithubBranch = undefined; var foundBranch = _.find(vm.data.githubBranches, function (o) { return o.value && o.value.name === branch; @@ -107,9 +116,40 @@ } }); - function isValid() { - return vm.userInput.githubProjectValid; + $scope.$watch(function () { + return vm.dropInfo; + }, function (newVal, oldVal) { + if (oldVal !== newVal) { + var info = newVal; + // Check if this is a GitHub link + if (angular.isString(info.value) && info.value.toLowerCase().indexOf(gitHubUrlBase) === 0) { + vm.sourceType = 'github'; + var urlParts = info.value.substring(gitHubUrlBase.length).split('/'); + if (urlParts.length > 1) { + var branch; + if (urlParts.length > 3 && urlParts[2] === 'tree') { + branch = urlParts[3]; + } + var project = urlParts[0] + '/' + urlParts[1]; + if (vm.userInput.githubProject === project) { + // Project is the same, so just change the branch + selectBranch(branch ? branch : vm.data.githubProject.default_branch); + } else { + vm.userInput.autoSelectGithubBranch = branch; + vm.userInput.githubProject = project; + } + } + } + } + }); + + function selectBranch(branch) { + var foundBranch = _.find(vm.data.githubBranches, function (o) { + return o.value && o.value.name === branch; + }); + vm.userInput.githubBranch = foundBranch ? foundBranch.value : undefined; } + } })(); diff --git a/components/cf-app-push/frontend/src/view/deploy-app-workflow/deploy-step-source/deploy-source-git/deploy-source-git.html b/components/cf-app-push/frontend/src/view/deploy-app-workflow/deploy-step-source/deploy-source-git/deploy-source-git.html index 234d7ac79d..1a4f490b52 100644 --- a/components/cf-app-push/frontend/src/view/deploy-app-workflow/deploy-step-source/deploy-source-git/deploy-source-git.html +++ b/components/cf-app-push/frontend/src/view/deploy-app-workflow/deploy-step-source/deploy-source-git/deploy-source-git.html @@ -1,13 +1,12 @@
+ ng-class="{'has-error': dplyGitCtrl.userInput.githubProjectValid === false}"> - + deploy-app-dialog.step-source.github.project-not-found
@@ -18,23 +17,23 @@ ng-model="dplyGitCtrl.userInput.githubBranch" input-options="dplyGitCtrl.data.githubBranches" placeholder="{{ 'deploy-app-dialog.step-source.github.branch-placeholder' | translate}}" - disabled="!dplyGitCtrl.userInput.githubProjectValid || dplyGitCtrl.userInput.sourceType !== 'github'"> + disabled="!dplyGitCtrl.userInput.githubProjectValid">
+ ng-class="{'deploy-app-invisible': !(dplyGitCtrl.userInput.githubProjectValid && dplyGitCtrl.data.githubProject)}" >
- +
- {{ dplyGitCtrl.githubProject.description | limitTo:50}} ... + {{ dplyGitCtrl.data.githubProject.description | limitTo:50}} ...
diff --git a/components/cf-app-push/frontend/src/view/deploy-app-workflow/deploy-step-source/deploy-source-local/deploy-source-local.directive.js b/components/cf-app-push/frontend/src/view/deploy-app-workflow/deploy-step-source/deploy-source-local/deploy-source-local.directive.js index e69de29bb2..12ebf04d3e 100644 --- a/components/cf-app-push/frontend/src/view/deploy-app-workflow/deploy-step-source/deploy-source-local/deploy-source-local.directive.js +++ b/components/cf-app-push/frontend/src/view/deploy-app-workflow/deploy-step-source/deploy-source-local/deploy-source-local.directive.js @@ -0,0 +1,142 @@ +(function () { + 'use strict'; + + angular + .module('cf-app-push') + .directive('appDeploySourceLocal', DeploySourceLocal); + + /** + * @namespace cf-app-push.accountActions + * @memberof cf-app-push + * @name DeploySourceGit + * @description ???????? + * @returns {object} The ???????? directive definition object + */ + function DeploySourceLocal() { + return { + scope: { + sourceType: '=', + userInput: '=', + data: '=', + formName: '@', + valid: '=', + dropInfo: '=', + folderSupport: '=', + showBusy: '=' + }, + bindToController: true, + controller: DeploySourceLocalController, + controllerAs: 'dplyLocalCtrl', + templateUrl: 'plugins/cf-app-push/view/deploy-app-workflow/deploy-step-source/deploy-source-local/deploy-source-local.html' + }; + } + + /** + * @namespace cf-app-push.DeploySourceGitController + * @memberof cf-app-push + * @name DeploySourceGitController + * @param {app.model.modelManager} modelManager - the application model manager + * @property {app.model.consoleInfo} consoleInfo - the consoleInfo model + * @constructor + */ + function DeploySourceLocalController($timeout, $translate, $q, $scope, itemDropHelper) { + var vm = this; + + vm.userInput.localPathFile = ''; + + var CF_IGNORE_FILE = '.cfignore'; + var CF_DEFAULT_IGNORES = '.cfignore\n_darcs\n.DS_Store\n.git\n.gitignore\n.hg\n.svn\n'; + + $scope.$watch(function () { + //TODO: RC Improve - this should be valid if it has all the userInput fields required + return vm.userInput.fileScanData; + }, function () { + vm.valid = angular.isDefined(vm.userInput.fileScanData); + }); + + // Watch for the file or folder being selected by the input field and process + $scope.$watch(function () { + return vm.userInput.localPathFile; + }, function (newVal, oldVal) { + if (newVal && oldVal !== newVal) { + handleFileInputSelect(newVal); + } + }); + + $scope.$watch(function () { + return vm.dropInfo; + }, function (newVal, oldVal) { + if (oldVal !== newVal) { + vm.cfIgnoreFile = false; + var info = newVal; + vm.userInput.localPath = info.value ? info.value.name : ''; + + if (info.isFiles) { + vm.showBusy('deploy-app-dialog.step-source.scanning'); + itemDropHelper.traverseFiles(info.value, CF_IGNORE_FILE, CF_DEFAULT_IGNORES).then(function (results) { + vm.sourceType = 'local'; + vm.userInput.fileScanData = results; + vm.cfIgnoreFile = results.foundIgnoreFile; + }).finally(function () { + vm.showBusy(); + }); + } else if (info.isArchiveFile) { + // vm.sourceType = 'local'; + var res = itemDropHelper.initScanner(); + vm.userInput.fileScanData = res.addFile(info.value); + vm.sourceType = 'local'; + } + } + }); + + // Handle result of a file input form field selection + function handleFileInputSelect(items) { + // File list from a file input form field + var res, rootFolderName, cfIgnoreFile; + res = itemDropHelper.initScanner(CF_DEFAULT_IGNORES); + vm.userInput.cfIgnoreFile = false; + if (items.length === 1) { + if (itemDropHelper.isArchiveFile(items[0].name)) { + vm.userInput.fileScanData = res.addFile(items[0]); + vm.sourceType = 'local'; + vm.userInput.localPath = items[0].name; + } + } else { + // See if we can find the .cfignore file + for (var j = 0; j < items.length; j++) { + var filePath = items[j].webkitRelativePath.split('/'); + // First part is the root folder name + if (filePath.length === 2 && !rootFolderName) { + rootFolderName = filePath[0]; + } + if (filePath.length > 2) { + break; + } else if (filePath.length === 2 && filePath[1] === CF_IGNORE_FILE) { + cfIgnoreFile = items[j]; + break; + } + } + + var promise = $q.resolve(''); + // Did we find an ignore file? + if (cfIgnoreFile) { + promise = itemDropHelper.readFileContents(cfIgnoreFile); + } + + promise.then(function (ignores) { + res = itemDropHelper.initScanner(CF_DEFAULT_IGNORES + ignores); + vm.userInput.cfIgnoreFile = !!ignores; + res.rootFolderName = rootFolderName; + _.each(items, function (file) { + res.addFile(file); + }); + vm.userInput.fileScanData = res; + vm.sourceType = 'local'; + vm.userInput.localPath = rootFolderName || $translate.instant('deploy-app-dialog.step-info.local.folder'); + }); + } + } + + } + +})(); diff --git a/components/cf-app-push/frontend/src/view/deploy-app-workflow/deploy-step-source/deploy-source-local/deploy-source-local.html b/components/cf-app-push/frontend/src/view/deploy-app-workflow/deploy-step-source/deploy-source-local/deploy-source-local.html index e69de29bb2..962168425f 100644 --- a/components/cf-app-push/frontend/src/view/deploy-app-workflow/deploy-step-source/deploy-source-local/deploy-source-local.html +++ b/components/cf-app-push/frontend/src/view/deploy-app-workflow/deploy-step-source/deploy-source-local/deploy-source-local.html @@ -0,0 +1,38 @@ +
+
+
+ + + + + + + + +
+ + +
+
+ file_upload +

deploy-app-dialog.step-source.local.info

+
+
+

deploy-app-dialog.step-source.local.cfignore

+
+
+ +
+
\ No newline at end of file diff --git a/components/cf-app-push/frontend/src/view/deploy-app-workflow/deploy-step-source/deploy-step-source.html b/components/cf-app-push/frontend/src/view/deploy-app-workflow/deploy-step-source/deploy-step-source.html index 0084d9b3d9..a975bc0b0b 100644 --- a/components/cf-app-push/frontend/src/view/deploy-app-workflow/deploy-step-source/deploy-step-source.html +++ b/components/cf-app-push/frontend/src/view/deploy-app-workflow/deploy-step-source/deploy-step-source.html @@ -1,87 +1,36 @@ - - - - - - - - - - - - - - - - - - - - -

deploy-app-dialog.step-source.source.file

-

deploy-app-dialog.step-source.source.folder

-
+

deploy-app-dialog.step-source.source.file

+

deploy-app-dialog.step-source.source.folder

+
- + + + form-name="deploy-info-form" + valid="step.data.git.valid" + drop-info="step.data.dropInfo" + >
- - -
-
-
- - - - - - - - -
- - -
-
- file_upload -

deploy-app-dialog.step-source.local.info

-
-
-

deploy-app-dialog.step-source.local.cfignore

-
-
- -
-
+ + + + +
- - - - - - - - - diff --git a/components/cf-app-push/frontend/src/view/deploy-app-workflow/deploy-step-source/deploy-step-source.service.js b/components/cf-app-push/frontend/src/view/deploy-app-workflow/deploy-step-source/deploy-step-source.service.js index 7f95cf8a41..889aad7b68 100644 --- a/components/cf-app-push/frontend/src/view/deploy-app-workflow/deploy-step-source/deploy-step-source.service.js +++ b/components/cf-app-push/frontend/src/view/deploy-app-workflow/deploy-step-source/deploy-step-source.service.js @@ -5,32 +5,23 @@ .module('cf-app-push') .factory('appDeployStepSourceService', AppDeployStepSourceService); - function AppDeployStepSourceService($q, $translate, appUtilsService) { - - var CF_IGNORE_FILE = '.cfignore'; - var CF_DEFAULT_IGNORES = '.cfignore\n_darcs\n.DS_Store\n.git\n.gitignore\n.hg\n.svn\n'; - - var gitHubUrlBase = 'https://github.com/'; + function AppDeployStepSourceService(itemDropHelper, appUtilsService) { return { - getStep: function (session, $scope) { - - // Watch for the file or folder being selected by the input field and process - $scope.$watch(function () { - return session.userInput.localPathFile; - }, function (newVal, oldVal) { - if (newVal && oldVal !== newVal) { - handleFileInputSelect(newVal); - } - }); + getStep: function (session) { var data = session.data.source; var userInput = session.userInput.source; + var wizard = session.wizard; + // var showBusy = ; data.githubBranches = []; + data.folderSupport = isInputDirSupported(); + data.bytesToHumanSize = appUtilsService.bytesToHumanSize; + data.dropItemHandler = _.partial(dropHandler, _, data); + data.dropInfo = undefined; userInput.githubProject = ''; userInput.localPath = ''; - userInput.localPathFile = ''; userInput.fileScanData = ''; return { @@ -40,72 +31,33 @@ formName: 'deploy-info-form', data: data, userInput: userInput, - folderSupport: isInputDirSupported(), + wizard: wizard, + showBusy: function (msg) { + session.showBusy(msg); + }, showBusyOnEnter: 'deploy-app-dialog.step-source.busy', nextBtnText: 'deploy-app-dialog.button-deploy', stepCommit: true, - bytesToHumanSize: appUtilsService.bytesToHumanSize, allowNext: function () { //TODO: RC wire in properly - return userInput.sourceType === 'github' && userInput.git.isValid() || userInput.sourceType === 'local' && userInput.fileScanData; - }, - dropItemHandler: dropHandler + // return vm.userInput.sourceType === 'github' && vm.userInput.githubProjectValid || vm.userInput.sourceType === 'local' && angular.isDefined(vm.userInput.fileScanData); + return session.wizard.sourceType === 'github' && _.get(data, 'git.valid') || + session.wizard.sourceType === 'local' && _.get(data, 'source.valid'); + } }, destroy: angular.noop }; } }; - function selectBranch(data, userInput, branch) { - var foundBranch = _.find(data.githubBranches, function (o) { - return o.value && o.value.name === branch; - }); - userInput.githubBranch = foundBranch ? foundBranch.value : undefined; - } /* * Handle a drop event */ - function dropHandler(data, userInput, items) { - userInput.cfIgnoreFile = false; + function dropHandler(items, data) { // Find out what has been dropped and take appropriate action itemDropHelper.identify(items).then(function (info) { - userInput.localPath = info.value ? info.value.name : ''; - if (info.isFiles) { - vm.options.wizardCtrl.showBusy('deploy-app-dialog.step-info.scanning'); - itemDropHelper.traverseFiles(info.value, CF_IGNORE_FILE, CF_DEFAULT_IGNORES).then(function (results) { - userInput.fileScanData = results; - userInput.sourceType = 'local'; - userInput.cfIgnoreFile = results.foundIgnoreFile; - }).finally(function () { - vm.options.wizardCtrl.showBusy(); - }); - } else if (info.isArchiveFile) { - userInput.sourceType = 'local'; - var res = itemDropHelper.initScanner(); - userInput.fileScanData = res.addFile(info.value); - userInput.sourceType = 'local'; - } else if (info.isWebLink) { - // Check if this is a GitHub link - if (info.value.toLowerCase().indexOf(gitHubUrlBase) === 0) { - userInput.sourceType = 'github'; - var urlParts = info.value.substring(gitHubUrlBase.length).split('/'); - if (urlParts.length > 1) { - var branch; - if (urlParts.length > 3 && urlParts[2] === 'tree') { - branch = urlParts[3]; - } - var project = urlParts[0] + '/' + urlParts[1]; - if (userInput.githubProject === project) { - // Project is the same, so just change the branch - selectBranch(branch ? branch : data.githubProject.default_branch); - } else { - userInput.autoSelectGithubBranch = branch; - userInput.githubProject = project; - } - } - } - } + data.dropInfo = info; }); } @@ -119,54 +71,6 @@ return 'webkitdirectory' in tmpInput; } - // Handle result of a file input form field selection - function handleFileInputSelect(data, userInput, items) { - // File list from a file input form field - var res, rootFolderName, cfIgnoreFile; - res = itemDropHelper.initScanner(CF_DEFAULT_IGNORES); - userInput.cfIgnoreFile = false; - if (items.length === 1) { - if (itemDropHelper.isArchiveFile(items[0].name)) { - userInput.fileScanData = res.addFile(items[0]); - userInput.sourceType = 'local'; - userInput.localPath = items[0].name; - } - } else { - // See if we can find the .cfignore file - for (var j = 0; j < items.length; j++) { - var filePath = items[j].webkitRelativePath.split('/'); - // First part is the root folder name - if (filePath.length === 2 && !rootFolderName) { - rootFolderName = filePath[0]; - } - if (filePath.length > 2) { - break; - } else if (filePath.length === 2 && filePath[1] === CF_IGNORE_FILE) { - cfIgnoreFile = items[j]; - break; - } - } - - var promise = $q.resolve(''); - // Did we find an ignore file? - if (cfIgnoreFile) { - promise = itemDropHelper.readFileContents(cfIgnoreFile); - } - - promise.then(function (ignores) { - res = itemDropHelper.initScanner(CF_DEFAULT_IGNORES + ignores); - userInput.cfIgnoreFile = !!ignores; - res.rootFolderName = rootFolderName; - _.each(items, function (file) { - res.addFile(file); - }); - userInput.fileScanData = res; - userInput.sourceType = 'local'; - userInput.localPath = rootFolderName || $translate.instant('deploy-app-dialog.step-info.local.folder'); - }); - } - } - } })(); From d39e73c48833a0921ba75c4e2fa8141d1c6ae992 Mon Sep 17 00:00:00 2001 From: Richard Cox Date: Fri, 28 Jul 2017 13:07:45 +0100 Subject: [PATCH 4/7] Started deploy step, a few minor bugs to work through WIP --- .../view/deploy-app-workflow/deploy-app.scss | 1 + .../deploy-step-deploying.html | 8 +- .../deploy-step-deploying.service.js | 108 ++++++++++-------- .../deploy-step-source.scss | 6 +- .../frontend/i18n/en_US/deploy-location.json | 2 +- 5 files changed, 67 insertions(+), 58 deletions(-) diff --git a/components/cf-app-push/frontend/src/view/deploy-app-workflow/deploy-app.scss b/components/cf-app-push/frontend/src/view/deploy-app-workflow/deploy-app.scss index ec68ca9082..8b48b9d702 100644 --- a/components/cf-app-push/frontend/src/view/deploy-app-workflow/deploy-app.scss +++ b/components/cf-app-push/frontend/src/view/deploy-app-workflow/deploy-app.scss @@ -4,6 +4,7 @@ .deploy-app-wizard.modal.detail-view.detail-view-dialog>.modal-dialog>.modal-content .detail-view-content.deploy-app wizard .wizard-step { padding: 0 $console-unit-space 6px $console-unit-space; + min-height: 320px; } .deploy-app { diff --git a/components/cf-app-push/frontend/src/view/deploy-app-workflow/deploy-step-deploying/deploy-step-deploying.html b/components/cf-app-push/frontend/src/view/deploy-app-workflow/deploy-step-deploying/deploy-step-deploying.html index 26eb4bc342..b4f00fe06d 100644 --- a/components/cf-app-push/frontend/src/view/deploy-app-workflow/deploy-step-deploying/deploy-step-deploying.html +++ b/components/cf-app-push/frontend/src/view/deploy-app-workflow/deploy-step-deploying/deploy-step-deploying.html @@ -1,8 +1,8 @@
- deploy-app-dialog.step-deploying.title-deploying - deploy-app-dialog.step-deploying.title-deploy-success - {{ step.data.deployFailure }} - + deploy-app-dialog.step-deploying.title-deploying + deploy-app-dialog.step-deploying.title-deploy-success + {{ step.data.deployFailure }} +
diff --git a/components/cf-app-push/frontend/src/view/deploy-app-workflow/deploy-step-deploying/deploy-step-deploying.service.js b/components/cf-app-push/frontend/src/view/deploy-app-workflow/deploy-step-deploying/deploy-step-deploying.service.js index 444f64cf3f..0076e138ec 100644 --- a/components/cf-app-push/frontend/src/view/deploy-app-workflow/deploy-step-deploying/deploy-step-deploying.service.js +++ b/components/cf-app-push/frontend/src/view/deploy-app-workflow/deploy-step-deploying/deploy-step-deploying.service.js @@ -5,7 +5,7 @@ .module('cf-app-push') .factory('appDeployStepDeployingService', AppDeployStepDeployingService); - function AppDeployStepDeployingService($q, $location, $translate, $timeout) { + function AppDeployStepDeployingService($q, $location, $translate, $timeout, $websocket, $log, modelManager) { var socketEventTypes = { DATA: 20000, @@ -48,33 +48,34 @@ return { getStep: function (session) { var data = session.data.deploying; - var userInput = session.userInput.deploying; - return { - step: { - title: 'deploy-app-dialog.step-deploying.title', - templateUrl: 'plugins/cf-app-push/view/deploy-app-workflow/deploy-step-deploying/deploy-step-deploying.html', - data: data, - userInput: userInput, - cancelBtnText: 'buttons.close', - nextBtnText: 'deploy-app-dialog.step-deploying.next-button', - showBusyOnEnter: 'deploy-app-dialog.step-deploying.busy', - allowNext: function () { - return !!session.wizard.newAppGuid; - }, - onEnter: function () { - session.wizard.allowBack = false; - return startDeploy(session, data, userInput).catch(function (error) { - session.wizard.allowBack = true; - return $q.reject($translate.instant('deploy-app-dialog.step-deploying.submit-failed', {reason: error})); - }); - }, - isLastStep: true, - logFilter: logFilter, - deployState: deployState + data.deployState = deployState; + //TODO: RC not picked up? getting failure to filter + data.logFilter = logFilter; + + var step = { + title: 'deploy-app-dialog.step-deploying.title', + templateUrl: 'plugins/cf-app-push/view/deploy-app-workflow/deploy-step-deploying/deploy-step-deploying.html', + data: data, + cancelBtnText: 'buttons.close', + nextBtnText: 'deploy-app-dialog.step-deploying.next-button', + showBusyOnEnter: 'deploy-app-dialog.step-deploying.busy', + allowNext: function () { + return !!session.wizard.newAppGuid; + }, + onEnter: function () { + session.wizard.allowBack = false; + return startDeploy(session, step).catch(function (error) { + session.wizard.allowBack = true; + return $q.reject($translate.instant('deploy-app-dialog.step-deploying.submit-failed', {reason: error})); + }); }, + isLastStep: true + }; + + return { + step: step, destroy: function () { - //TODO: RC $timeout.cancel(session.data.discoverAppTimer); resetSocket(session.data.webSocket); } @@ -82,7 +83,7 @@ } }; - function discoverAppGuid(data, userInput, appName) { + function discoverAppGuid(data, destinationUserInput, appName) { if (data.discoverAppTimer) { return; } @@ -93,9 +94,10 @@ var params = { q: 'name:' + appName }; - spaceModel.listAllAppsForSpace(userInput.serviceInstance.guid, userInput.space.metadata.guid, params) + spaceModel.listAllAppsForSpace(destinationUserInput.serviceInstance.guid, destinationUserInput.space.metadata.guid, params) .then(function (apps) { if (apps.length === 1) { + //TODO: RC session undefined session.wizard.newAppGuid = apps[0].metadata.guid; } }) @@ -103,7 +105,7 @@ data.discoverAppTimer = undefined; // Did not find the app - try again if (!session.wizard.newAppGuid) { - discoverAppGuid(data, userInput, appName); + discoverAppGuid(data, destinationUserInput, appName); } }); }, DISCOVER_APP_TIMER_PERIOD); @@ -125,9 +127,14 @@ return url; } - function startDeploy(session, data, userInput) { + function startDeploy(session, step) { + + var data = session.data.deploying; + + var destinationUserInput = session.userInput.destination; + var sourceUserInput = session.userInput.source; - data.deployStatus = data.deployState.UNKNOWN; + data.deployStatus = deployState.UNKNOWN; session.wizard.hasPushStarted = false; session.wizard.newAppGuid = null; @@ -135,22 +142,22 @@ function pushStarted() { session.wizard.hasPushStarted = true; - data.deployStatus = data.deployState.PUSHING; + data.deployStatus = deployState.PUSHING; deployingPromise.resolve(); data.uploadingFiles = undefined; $log.debug('Deploy Application: Push Started'); } - function deploySuccessful() { - data.deployStatus = data.deployState.DEPLOYED; + function deploySuccessful(step) { + data.deployStatus = deployState.DEPLOYED; $log.debug('Deploy Application: Deploy Successful'); // Mark the wizard step as complete (so it gets a tick icon) - vm.options.workflow.steps[1].complete = true; + step.complete = true; } function deployFailed(errorString) { session.wizard.allowBack = true; - data.deployStatus = data.deployState.FAILED; + data.deployStatus = deployState.FAILED; var failureDescription = $translate.instant(errorString); data.deployFailure = $translate.instant('deploy-app-dialog.step-deploying.title-deploy-failed', {reason: failureDescription}); deployingPromise.reject(failureDescription); @@ -166,9 +173,10 @@ } function sendGitHubSourceMetadata() { + //TODO: RC Add to validation check var github = { - project: userInput.githubProject, - branch: userInput.githubBranch.name + project: sourceUserInput.githubProject, + branch: sourceUserInput.githubBranch.name }; var msg = { @@ -187,14 +195,14 @@ folders: [] }; - collectFoldersAndFiles(metadata, null, userInput.fileScanData.root); + collectFoldersAndFiles(metadata, null, sourceUserInput.fileScanData.root); - userInput.fileTransfers = metadata.files; + sourceUserInput.fileTransfers = metadata.files; metadata.files = metadata.files.length; data.uploadingFiles = { remaining: metadata.files, bytes: 0, - total: userInput.fileScanData.total, + total: sourceUserInput.fileScanData.total, fileName: '' }; @@ -224,8 +232,8 @@ } function sendNextFile() { - if (userInput.fileTransfers.length > 0) { - var file = userInput.fileTransfers.shift(); + if (sourceUserInput.fileTransfers.length > 0) { + var file = sourceUserInput.fileTransfers.shift(); // Send file metadata var msg = { @@ -253,7 +261,7 @@ resetSocket(data.webSocket); // Determine web socket url and open connection - var socketUrl = createSocketUrl(userInput.serviceInstance, userInput.organization, userInput.space); + var socketUrl = createSocketUrl(destinationUserInput.serviceInstance, destinationUserInput.organization, destinationUserInput.space); data.webSocket = $websocket(socketUrl, null, { reconnectIfNotNormalClose: false, @@ -262,7 +270,7 @@ // Handle Connection responses data.webSocket.onOpen(function () { - data.deployStatus = data.deployState.SOCKET_OPEN; + data.deployStatus = deployState.SOCKET_OPEN; }); /* eslint-disable complexity */ @@ -288,14 +296,14 @@ deployFailed('deploy-app-dialog.socket.event-type.' + type); break; case socketEventTypes.CLOSE_SUCCESS: - deploySuccessful(); + deploySuccessful(step); break; case socketEventTypes.EVENT_CLONED: - data.deployStatus = data.deployState.CLONED; + data.deployStatus = deployState.CLONED; $log.debug('Deploy Application: Cloned'); break; case socketEventTypes.EVENT_FETCHED_MANIFEST: - data.deployStatus = data.deployState.FETCHED_MANIFEST; + data.deployStatus = deployState.FETCHED_MANIFEST; $log.debug('Deploy Application: Fetched manifest'); break; case socketEventTypes.EVENT_PUSH_STARTED: @@ -309,7 +317,7 @@ var manifest = angular.fromJson(logData.message); var app = _.get(manifest, 'Applications[0]', {}); if (app.Name) { - discoverAppGuid(data, userInput, app.Name); + discoverAppGuid(data, destinationUserInput, app.Name); } break; case socketEventTypes.SOURCE_REQUIRED: @@ -327,10 +335,10 @@ /* eslint-enable complexity */ data.webSocket.onClose(function () { - if (data.deployStatus === data.deployState.UNKNOWN) { + if (data.deployStatus === deployState.UNKNOWN) { // Closed before socket has successfully opened deployFailed('deploy-app-dialog.socket.failed-connection'); - } else if (data.deployStatus !== data.deployState.DEPLOYED && data.deployStatus !== data.deployState.FAILED) { + } else if (data.deployStatus !== deployState.DEPLOYED && data.deployStatus !== deployState.FAILED) { // Have connected to socket but not received a close message containing deploy result deployFailed('deploy-app-dialog.socket.failed-unknown'); } diff --git a/components/cf-app-push/frontend/src/view/deploy-app-workflow/deploy-step-source/deploy-step-source.scss b/components/cf-app-push/frontend/src/view/deploy-app-workflow/deploy-step-source/deploy-step-source.scss index 214492c659..5882d8f546 100644 --- a/components/cf-app-push/frontend/src/view/deploy-app-workflow/deploy-step-source/deploy-step-source.scss +++ b/components/cf-app-push/frontend/src/view/deploy-app-workflow/deploy-step-source/deploy-step-source.scss @@ -84,9 +84,9 @@ $select-option-height: 39px; } } - p.deploy-app-choose-source { - margin: $console-unit-space * 0.5 0; - } + //p.deploy-app-choose-source { + // margin: $console-unit-space * 0.5 0; + //} .deploy-app-source-area { display: flex; diff --git a/components/cloud-foundry/frontend/i18n/en_US/deploy-location.json b/components/cloud-foundry/frontend/i18n/en_US/deploy-location.json index df26b520f1..ae7c96d582 100644 --- a/components/cloud-foundry/frontend/i18n/en_US/deploy-location.json +++ b/components/cloud-foundry/frontend/i18n/en_US/deploy-location.json @@ -1,6 +1,6 @@ { "app.deploy-location": { - "title": "Select the [[@:cloud-foundry]] instance, organization and space where the app will be deployed to.", + "title": "Select a [[@:cloud-foundry]] instance, organization and space for the app.", "cf-label": "[[@:cloud-foundry]]", "cf-placeholder": "Select [[@:cloud-foundry]]", "org-label": "[[@:organization]]", From 8bcf4510fd43712bd4b8f1b3b9038f0f797232b3 Mon Sep 17 00:00:00 2001 From: Richard Cox Date: Mon, 31 Jul 2017 14:30:17 +0100 Subject: [PATCH 5/7] Deploy fixes, removed common session data, added delay to 'scanning' busy screen --- .../frontend/i18n/en_US/app-push.json | 10 +-- .../deploy-app-data.service.js | 17 ----- .../deploy-app-workflow/deploy-app.service.js | 26 ++----- .../deploy-step-deploying.service.js | 66 ++++++++++------- .../deploy-step-destination.scss | 7 ++ .../deploy-step-destination.service.js | 22 ++++-- .../deploy-source-git.directive.js | 23 +++--- .../deploy-source-local.directive.js | 73 ++++++++++++------- .../deploy-source-local.html | 8 +- .../deploy-step-source.html | 6 -- .../deploy-step-source.scss | 5 -- .../deploy-step-source.service.js | 32 ++++---- 12 files changed, 153 insertions(+), 142 deletions(-) delete mode 100644 components/cf-app-push/frontend/src/view/deploy-app-workflow/deploy-app-data.service.js diff --git a/components/cf-app-push/frontend/i18n/en_US/app-push.json b/components/cf-app-push/frontend/i18n/en_US/app-push.json index a06e365a5c..ad4f8cfa02 100644 --- a/components/cf-app-push/frontend/i18n/en_US/app-push.json +++ b/components/cf-app-push/frontend/i18n/en_US/app-push.json @@ -64,15 +64,15 @@ "event-type": { "CLOSE_FAILED_NO_BRANCH": "Failed to clone repository (invalid branch), please ensure github details are correct", "CLOSE_FAILED_CLONE": "Failed to clone repository, please ensure github details are correct and try again", - "CLOSE_FAILURE": "Unknown failure", - "CLOSE_INVALID_MANIFEST": "Invalid manifest in repository", - "CLOSE_NO_MANIFEST": "Failed to find manifest in repository", - "CLOSE_PUSH_ERROR": "[[@:cloud-foundry]] push unsuccessful", + "CLOSE_FAILURE": "Unknown failure. See log for more information.", + "CLOSE_INVALID_MANIFEST": "Invalid manifest", + "CLOSE_NO_MANIFEST": "Failed to find manifest", + "CLOSE_PUSH_ERROR": "[[@:cloud-foundry]] push unsuccessful. See log for details.", "CLOSE_NO_SESSION": "Failed to create session", "CLOSE_NO_CNSI": "Unknown [[@:cloud-foundry]] instance", "CLOSE_NO_CNSI_USERTOKEN": "Invalid [[@:cloud-foundry]] token" }, - "failed-connection": "Failed to open connection", + "failed-connection": "Failed to open socket connection", "failed-unknown": "Unknown failure" } } diff --git a/components/cf-app-push/frontend/src/view/deploy-app-workflow/deploy-app-data.service.js b/components/cf-app-push/frontend/src/view/deploy-app-workflow/deploy-app-data.service.js deleted file mode 100644 index 1b26e154f3..0000000000 --- a/components/cf-app-push/frontend/src/view/deploy-app-workflow/deploy-app-data.service.js +++ /dev/null @@ -1,17 +0,0 @@ -// (function () { -// 'use strict'; -// -// angular -// .module('cf-app-push') -// .factory('appDeployAppDataService', DeployAppDataService); -// -// function DeployAppDataService() { -// return { -// data: {}, -// userInput: {}, -// newAppGuid: undefined, -// allowBack: true -// }; -// } -// -// })(); diff --git a/components/cf-app-push/frontend/src/view/deploy-app-workflow/deploy-app.service.js b/components/cf-app-push/frontend/src/view/deploy-app-workflow/deploy-app.service.js index ee23dc89a6..983da52609 100644 --- a/components/cf-app-push/frontend/src/view/deploy-app-workflow/deploy-app.service.js +++ b/components/cf-app-push/frontend/src/view/deploy-app-workflow/deploy-app.service.js @@ -75,31 +75,19 @@ * @name DeployAppController * @constructor * @param {object} $scope - the angular $scope service - * @param {object} $q - the angular $q service * @param {object} $uibModalInstance - the angular $uibModalInstance service used to close/dismiss a modal * @param {object} $state - the angular $state service - * @param {object} $location - the angular $location service - * @param {object} $websocket - the angular $websocket service - * @param {object} $translate - the angular $translate service - * @param {object} $log - the angular $log service - * @param {object} $http - the angular $http service - * @param {object} $timeout - the angular $timeout service - * @param {object} $filter - the angular $filter service - * @param {app.model.modelManager} modelManager - the Model management service - * @param {object} itemDropHelper - the item drop helper service - * @param {object} appUtilsService - the App Utils service + * @param {object} appDeployStepDestinationService - Service to provide the destination step (org/space of the app) + * @param {object} appDeployStepSourceService - Service to provide the source step (where the app source come from) + * @param {object} appDeployStepDeployingService - Service to provide the deploying step */ - function DeployAppController($scope, $uibModalInstance, $state, appDeployStepDeployingService, - appDeployStepDestinationService, appDeployStepSourceService) { + function DeployAppController($scope, $uibModalInstance, $state, appDeployStepDestinationService, + appDeployStepSourceService, appDeployStepDeployingService) { var vm = this; + // Contains the info the user has submitted in all steps and any vars that are used in multiple wizard steps var session = { - data: { - deploying: {}, - destination: {}, - source: {} - }, userInput: { deploying: {}, destination: {}, @@ -130,7 +118,7 @@ disableJump: true, allowCancelAtLastStep: true, allowBack: function () { - return session.wizard.allowBack; + return session ? session.wizard.allowBack : false; }, title: 'deploy-app-dialog.title', btnText: { diff --git a/components/cf-app-push/frontend/src/view/deploy-app-workflow/deploy-step-deploying/deploy-step-deploying.service.js b/components/cf-app-push/frontend/src/view/deploy-app-workflow/deploy-step-deploying/deploy-step-deploying.service.js index 0076e138ec..a6fd8a36ba 100644 --- a/components/cf-app-push/frontend/src/view/deploy-app-workflow/deploy-step-deploying/deploy-step-deploying.service.js +++ b/components/cf-app-push/frontend/src/view/deploy-app-workflow/deploy-step-deploying/deploy-step-deploying.service.js @@ -5,7 +5,20 @@ .module('cf-app-push') .factory('appDeployStepDeployingService', AppDeployStepDeployingService); - function AppDeployStepDeployingService($q, $location, $translate, $timeout, $websocket, $log, modelManager) { + /** + * @memberof appDeployStepDeployingService + * @name AppDeployStepDeployingService + * @constructor + * @param {object} $q - the angular $q service + * @param {object} $location - the angular $location service + * @param {object} $translate - the angular $translate service + * @param {object} $timeout - the angular $timeout service + * @param {object} $websocket - the angular $websocket service + * @param {object} $log - the angular $log service + * @param {object} $filter - the angular $filter service + * @param {app.model.modelManager} modelManager - the Model management service + */ + function AppDeployStepDeployingService($q, $location, $translate, $timeout, $websocket, $log, $filter, modelManager) { var socketEventTypes = { DATA: 20000, @@ -47,11 +60,11 @@ return { getStep: function (session) { - var data = session.data.deploying; - - data.deployState = deployState; - //TODO: RC not picked up? getting failure to filter - data.logFilter = logFilter; + var data = { + deployState: deployState, + logFilter: logFilter + }; + var wizardData = session.wizard; var step = { title: 'deploy-app-dialog.step-deploying.title', @@ -61,12 +74,12 @@ nextBtnText: 'deploy-app-dialog.step-deploying.next-button', showBusyOnEnter: 'deploy-app-dialog.step-deploying.busy', allowNext: function () { - return !!session.wizard.newAppGuid; + return !!wizardData.newAppGuid; }, onEnter: function () { - session.wizard.allowBack = false; - return startDeploy(session, step).catch(function (error) { - session.wizard.allowBack = true; + wizardData.allowBack = false; + return startDeploy(session, data, step).catch(function (error) { + wizardData.allowBack = true; return $q.reject($translate.instant('deploy-app-dialog.step-deploying.submit-failed', {reason: error})); }); }, @@ -76,14 +89,14 @@ return { step: step, destroy: function () { - $timeout.cancel(session.data.discoverAppTimer); - resetSocket(session.data.webSocket); + $timeout.cancel(data.discoverAppTimer); + resetSocket(data.webSocket); } }; } }; - function discoverAppGuid(data, destinationUserInput, appName) { + function discoverAppGuid(wizardData, data, destinationUserInput, appName) { if (data.discoverAppTimer) { return; } @@ -97,15 +110,14 @@ spaceModel.listAllAppsForSpace(destinationUserInput.serviceInstance.guid, destinationUserInput.space.metadata.guid, params) .then(function (apps) { if (apps.length === 1) { - //TODO: RC session undefined - session.wizard.newAppGuid = apps[0].metadata.guid; + wizardData.newAppGuid = apps[0].metadata.guid; } }) .finally(function () { data.discoverAppTimer = undefined; // Did not find the app - try again - if (!session.wizard.newAppGuid) { - discoverAppGuid(data, destinationUserInput, appName); + if (!wizardData.newAppGuid) { + discoverAppGuid(wizardData, data, destinationUserInput, appName); } }); }, DISCOVER_APP_TIMER_PERIOD); @@ -127,21 +139,20 @@ return url; } - function startDeploy(session, step) { - - var data = session.data.deploying; + function startDeploy(session, data, step) { + var wizardData = session.wizard; var destinationUserInput = session.userInput.destination; var sourceUserInput = session.userInput.source; data.deployStatus = deployState.UNKNOWN; - session.wizard.hasPushStarted = false; - session.wizard.newAppGuid = null; + wizardData.hasPushStarted = false; + wizardData.newAppGuid = null; var deployingPromise = $q.defer(); function pushStarted() { - session.wizard.hasPushStarted = true; + wizardData.hasPushStarted = true; data.deployStatus = deployState.PUSHING; deployingPromise.resolve(); data.uploadingFiles = undefined; @@ -156,7 +167,7 @@ } function deployFailed(errorString) { - session.wizard.allowBack = true; + wizardData.allowBack = true; data.deployStatus = deployState.FAILED; var failureDescription = $translate.instant(errorString); data.deployFailure = $translate.instant('deploy-app-dialog.step-deploying.title-deploy-failed', {reason: failureDescription}); @@ -165,15 +176,14 @@ } function sendSourceMetadata() { - if (session.wizard.sourceType === 'github') { + if (wizardData.sourceType === 'github') { sendGitHubSourceMetadata(); - } else if (session.wizard.sourceType === 'local') { + } else if (wizardData.sourceType === 'local') { sendLocalSourceMetadata(); } } function sendGitHubSourceMetadata() { - //TODO: RC Add to validation check var github = { project: sourceUserInput.githubProject, branch: sourceUserInput.githubBranch.name @@ -317,7 +327,7 @@ var manifest = angular.fromJson(logData.message); var app = _.get(manifest, 'Applications[0]', {}); if (app.Name) { - discoverAppGuid(data, destinationUserInput, app.Name); + discoverAppGuid(wizardData, data, destinationUserInput, app.Name); } break; case socketEventTypes.SOURCE_REQUIRED: diff --git a/components/cf-app-push/frontend/src/view/deploy-app-workflow/deploy-step-destination/deploy-step-destination.scss b/components/cf-app-push/frontend/src/view/deploy-app-workflow/deploy-step-destination/deploy-step-destination.scss index e69de29bb2..9e03aad011 100644 --- a/components/cf-app-push/frontend/src/view/deploy-app-workflow/deploy-step-destination/deploy-step-destination.scss +++ b/components/cf-app-push/frontend/src/view/deploy-app-workflow/deploy-step-destination/deploy-step-destination.scss @@ -0,0 +1,7 @@ +.deploy-app { + deploy-location { + .control-title { + margin-top: 0; + } + } +} \ No newline at end of file diff --git a/components/cf-app-push/frontend/src/view/deploy-app-workflow/deploy-step-destination/deploy-step-destination.service.js b/components/cf-app-push/frontend/src/view/deploy-app-workflow/deploy-step-destination/deploy-step-destination.service.js index 04bce5093b..ea18ec26d1 100644 --- a/components/cf-app-push/frontend/src/view/deploy-app-workflow/deploy-step-destination/deploy-step-destination.service.js +++ b/components/cf-app-push/frontend/src/view/deploy-app-workflow/deploy-step-destination/deploy-step-destination.service.js @@ -5,6 +5,13 @@ .module('cf-app-push') .factory('appDeployStepDestinationService', AppDeployStepDestinationService); + /** + * @memberof appDeployStepDestinationService + * @name AppDeployStepDestinationService + * @constructor + * @param {object} $q - the angular $q service + * @param {app.model.modelManager} modelManager - the Model management service + */ function AppDeployStepDestinationService($q, modelManager) { return { @@ -12,13 +19,15 @@ var serviceInstanceModel = modelManager.retrieve('app.model.serviceInstance.user'); var authModel = modelManager.retrieve('cloud-foundry.model.auth'); - var data = session.data.destination; - var userInput = session.userInput.destination; + var data = { + serviceInstances: [] + }; - data.serviceInstances = []; + var userInput = session.userInput.destination; userInput.serviceInstance = null; userInput.organization = null; userInput.space = null; + var wizardData = session.wizard; return { step: { @@ -29,7 +38,7 @@ userInput: userInput, showBusyOnEnter: 'deploy-app-dialog.step-destination.busy', onEnter: function () { - if (data.deployStatus) { + if (wizardData.fetchedServiceInstances) { // Previously been at this step, no need to fetch instances again return; } @@ -45,14 +54,13 @@ }) .value(); [].push.apply(data.serviceInstances, validServiceInstances); + wizardData.fetchedServiceInstances = true; }).catch(function () { return $q.reject('deploy-app-dialog.step-destination.enter-failed'); }); } }, - destroy: function () { - //TODO: RC - } + destroy: angular.noop }; } }; diff --git a/components/cf-app-push/frontend/src/view/deploy-app-workflow/deploy-step-source/deploy-source-git/deploy-source-git.directive.js b/components/cf-app-push/frontend/src/view/deploy-app-workflow/deploy-step-source/deploy-source-git/deploy-source-git.directive.js index 42d02b0674..fd32af82ec 100644 --- a/components/cf-app-push/frontend/src/view/deploy-app-workflow/deploy-step-source/deploy-source-git/deploy-source-git.directive.js +++ b/components/cf-app-push/frontend/src/view/deploy-app-workflow/deploy-step-source/deploy-source-git/deploy-source-git.directive.js @@ -17,8 +17,6 @@ scope: { sourceType: '=', userInput: '=', - data: '=', - formName: '@', valid: '=', dropInfo: '=' }, @@ -33,19 +31,16 @@ * @namespace cf-app-push.DeploySourceGitController * @memberof cf-app-push * @name DeploySourceGitController - * @param {app.model.modelManager} modelManager - the application model manager - * @property {app.model.consoleInfo} consoleInfo - the consoleInfo model + * @param {object} $http - the angular $http service + * @param {object} $scope - the angular $scope service * @constructor */ function DeploySourceGitController($http, $scope) { var vm = this; - $scope.$watch(function () { - //TODO: RC Improve - this should be valid if it has all the userInput fields required - return vm.userInput.githubProjectValid; - }, function () { - vm.valid = vm.userInput.githubProjectValid; - }); + vm.data = { + githubBranches: [] + }; var gitHubUrlBase = 'https://github.com/'; @@ -94,9 +89,15 @@ }); }, 1000); + $scope.$watch(function () { + return vm.userInput.githubProjectValid && vm.userInput.githubProject && vm.userInput.githubBranch.name; + }, function (newVal) { + vm.valid = newVal; + }); + $scope.$watch(function () { return vm.userInput.githubProject; - }, function (oldVal, newVal) { + }, function (newVal, oldVal) { if (oldVal !== newVal) { debounceGithubProjectFetch(); } diff --git a/components/cf-app-push/frontend/src/view/deploy-app-workflow/deploy-step-source/deploy-source-local/deploy-source-local.directive.js b/components/cf-app-push/frontend/src/view/deploy-app-workflow/deploy-step-source/deploy-source-local/deploy-source-local.directive.js index 12ebf04d3e..864127fb00 100644 --- a/components/cf-app-push/frontend/src/view/deploy-app-workflow/deploy-step-source/deploy-source-local/deploy-source-local.directive.js +++ b/components/cf-app-push/frontend/src/view/deploy-app-workflow/deploy-step-source/deploy-source-local/deploy-source-local.directive.js @@ -17,8 +17,6 @@ scope: { sourceType: '=', userInput: '=', - data: '=', - formName: '@', valid: '=', dropInfo: '=', folderSupport: '=', @@ -35,20 +33,25 @@ * @namespace cf-app-push.DeploySourceGitController * @memberof cf-app-push * @name DeploySourceGitController - * @param {app.model.modelManager} modelManager - the application model manager - * @property {app.model.consoleInfo} consoleInfo - the consoleInfo model + * @param {object} $translate - the angular $translate service + * @param {object} $q - the angular $q service + * @param {object} $scope - the angular $scope service + * @param {object} itemDropHelper - the item drop helper service + * @param {object} appUtilsService - the App Utils service * @constructor */ - function DeploySourceLocalController($timeout, $translate, $q, $scope, itemDropHelper) { + function DeploySourceLocalController($translate, $q, $scope, $timeout, itemDropHelper, appUtilsService) { var vm = this; + vm.data = { + bytesToHumanSize: appUtilsService.bytesToHumanSize + }; vm.userInput.localPathFile = ''; var CF_IGNORE_FILE = '.cfignore'; var CF_DEFAULT_IGNORES = '.cfignore\n_darcs\n.DS_Store\n.git\n.gitignore\n.hg\n.svn\n'; $scope.$watch(function () { - //TODO: RC Improve - this should be valid if it has all the userInput fields required return vm.userInput.fileScanData; }, function () { vm.valid = angular.isDefined(vm.userInput.fileScanData); @@ -66,25 +69,45 @@ $scope.$watch(function () { return vm.dropInfo; }, function (newVal, oldVal) { - if (oldVal !== newVal) { - vm.cfIgnoreFile = false; - var info = newVal; - vm.userInput.localPath = info.value ? info.value.name : ''; - - if (info.isFiles) { - vm.showBusy('deploy-app-dialog.step-source.scanning'); - itemDropHelper.traverseFiles(info.value, CF_IGNORE_FILE, CF_DEFAULT_IGNORES).then(function (results) { - vm.sourceType = 'local'; - vm.userInput.fileScanData = results; - vm.cfIgnoreFile = results.foundIgnoreFile; - }).finally(function () { - vm.showBusy(); - }); - } else if (info.isArchiveFile) { - // vm.sourceType = 'local'; - var res = itemDropHelper.initScanner(); - vm.userInput.fileScanData = res.addFile(info.value); - vm.sourceType = 'local'; + var info = newVal; + if (!info) { + return; + } + + if (info.isFiles || info.isArchiveFile) { + // Ensure we always update the sourceType even if the val has not changed + vm.sourceType = 'local'; + + if (oldVal !== newVal) { + vm.cfIgnoreFile = false; + vm.userInput.localPath = info.value ? info.value.name : ''; + + if (info.isFiles) { + var showBusy = true; + var hasShownBusy = false; + // For small files the busy screen might only show for a few short moments. To avoid a UI blip pause before + // showing the screen + $timeout(function () { + if (showBusy) { + vm.showBusy('deploy-app-dialog.step-source.scanning'); + hasShownBusy = true; + } + }, 100); + + itemDropHelper.traverseFiles(info.value, CF_IGNORE_FILE, CF_DEFAULT_IGNORES).then(function (results) { + vm.userInput.fileScanData = results; + vm.cfIgnoreFile = results.foundIgnoreFile; + }).finally(function () { + showBusy = false; + if (hasShownBusy) { + vm.showBusy(); + } + }); + } else if (info.isArchiveFile) { + // vm.sourceType = 'local'; + var res = itemDropHelper.initScanner(); + vm.userInput.fileScanData = res.addFile(info.value); + } } } }); diff --git a/components/cf-app-push/frontend/src/view/deploy-app-workflow/deploy-step-source/deploy-source-local/deploy-source-local.html b/components/cf-app-push/frontend/src/view/deploy-app-workflow/deploy-step-source/deploy-source-local/deploy-source-local.html index 962168425f..59327fd0a6 100644 --- a/components/cf-app-push/frontend/src/view/deploy-app-workflow/deploy-step-source/deploy-source-local/deploy-source-local.html +++ b/components/cf-app-push/frontend/src/view/deploy-app-workflow/deploy-step-source/deploy-source-local/deploy-source-local.html @@ -1,17 +1,17 @@
- - + + -