diff --git a/awx/ui/client/features/output/_index.less b/awx/ui/client/features/output/_index.less index 5fa8bec237..71228d20e7 100644 --- a/awx/ui/client/features/output/_index.less +++ b/awx/ui/client/features/output/_index.less @@ -281,3 +281,254 @@ background-color: @default-err; } + +// Job Details --------------------------------------------------------------------------------- + +@breakpoint-md: 1200px; + +.JobResults { + .OnePlusTwo-container(100%, @breakpoint-md); + + &.fullscreen { + .JobResults-rightSide { + max-width: 100%; + } + } +} + +.JobResults-leftSide { + .OnePlusTwo-left--panel(100%, @breakpoint-md); + max-width: 30%; + height: ~"calc(100vh - 177px)"; + + @media screen and (max-width: @breakpoint-md) { + max-width: 100%; + } +} + +.JobResults-rightSide { + .OnePlusTwo-right--panel(100%, @breakpoint-md); + height: ~"calc(100vh - 177px)"; + + @media (max-width: @breakpoint-md - 1px) { + padding-right: 15px; + } +} + +.JobResults-detailsPanel{ + overflow-y: scroll; +} + +.JobResults-stdoutActionButton--active { + display: none; + visibility: hidden; + flex:none; + width:0px; + padding-right: 0px; +} + +.JobResults-panelHeader { + display: flex; + height: 30px; +} + +.JobResults-panelHeaderText { + color: @default-interface-txt; + flex: 1 0 auto; + font-size: 14px; + font-weight: bold; + margin-right: 10px; + text-transform: uppercase; +} + +.JobResults-panelHeaderButtonActions { + display: flex; +} + +.JobResults-resultRow { + width: 100%; + display: flex; + padding-bottom: 10px; + flex-wrap: wrap; +} + +.JobResults-resultRow--variables { + flex-direction: column; + + #cm-variables-container { + width: 100%; + } +} + +.JobResults-resultRowLabel { + text-transform: uppercase; + color: @default-interface-txt; + font-size: 12px; + font-weight: normal!important; + width: 30%; + margin-right: 20px; + + @media screen and (max-width: @breakpoint-md) { + flex: 2.5 0 auto; + } +} + +.JobResults-resultRowLabel--fullWidth { + width: 100%; + margin-right: 0px; +} + +.JobResults-resultRowText { + width: ~"calc(70% - 20px)"; + flex: 1 0 auto; + text-transform: none; + word-wrap: break-word; +} + +.JobResults-resultRowText--fullWidth { + width: 100%; +} + +.JobResults-expandArrow { + color: #D7D7D7; + font-size: 14px; + font-weight: bold; + margin-right: 10px; + text-transform: uppercase; + margin-left: 10px; +} + +.JobResults-resultRowText--instanceGroup { + display: flex; +} + +.JobResults-isolatedBadge { + align-items: center; + background-color: @default-list-header-bg; + border-radius: 5px; + color: @default-stdout-txt; + display: flex; + font-size: 10px; + height: 16px; + margin: 3px 0 0 10px; + padding: 0 10px; + text-transform: uppercase; +} + +.JobResults-statusResultIcon { + padding-left: 0px; + padding-right: 10px; +} + +.JobResults-badgeRow { + display: flex; + align-items: center; + margin-right: 5px; +} + +.JobResults-badgeTitle{ + color: @default-interface-txt; + font-size: 14px; + margin-right: 10px; + font-weight: normal; + text-transform: uppercase; + margin-left: 20px; +} + +@media (max-width: @breakpoint-md) { + .JobResults-detailsPanel { + overflow-y: auto; + } + + .JobResults-rightSide { + height: inherit; + } +} + +.JobResults-timeBadge { + float:right; + font-size: 11px; + font-weight: normal; + padding: 1px 10px; + height: 14px; + margin: 3px 15px; + width: 80px; + background-color: @default-bg; + border-radius: 5px; + color: @default-interface-txt; + margin-right: -5px; +} + +.JobResults-panelRight { + display: flex; + flex-direction: column; +} + +.JobResults-panelRight .SmartSearch-bar { + width: 100%; +} + +.JobResults-panelRightTitle{ + flex-wrap: wrap; +} + +.JobResults-panelRightTitleText{ + word-wrap: break-word; + word-break: break-all; + max-width: 100%; +} + +.JobResults-badgeAndActionRow{ + display:flex; + flex: 1 0 auto; + justify-content: flex-end; + flex-wrap: wrap; + max-width: 100%; +} + +.StandardOut-panelHeader { + flex: initial; +} + +.StandardOut-panelHeader--jobIsRunning { + margin-bottom: 20px; +} + +host-status-bar { + flex: initial; + margin-bottom: 20px; +} + +smart-search { + flex: initial; +} + +job-results-standard-out { + flex: 1; + flex-basis: auto; + height: ~"calc(100% - 800px)"; + display: flex; + border: 1px solid @d7grey; + border-radius: 5px; + margin-top: 20px; +} +@media screen and (max-width: @breakpoint-md) { + job-results-standard-out { + height: auto; + } +} + +.JobResults-extraVarsHelp { + margin-left: 10px; + color: @default-icon; +} + +.JobResults-seeMoreLess { + color: #337AB7; + margin: 4px 0px; + text-transform: uppercase; + padding: 2px 0px; + cursor: pointer; + border-radius: 5px; + font-size: 11px; +} \ No newline at end of file diff --git a/awx/ui/client/lib/theme/index.less b/awx/ui/client/lib/theme/index.less index ec42e88a45..a0f7738272 100644 --- a/awx/ui/client/lib/theme/index.less +++ b/awx/ui/client/lib/theme/index.less @@ -81,9 +81,6 @@ @import '../../src/inventories-hosts/inventories/inventories.block.less'; @import '../../src/inventories-hosts/shared/associate-groups/associate-groups.block.less'; @import '../../src/inventories-hosts/shared/associate-hosts/associate-hosts.block.less'; -@import '../../src/job-results/host-status-bar/host-status-bar.block.less'; -@import '../../src/job-results/job-results-stdout/job-results-stdout.block.less'; -@import '../../src/job-results/job-results.block.less'; @import '../../src/job-submission/job-submission.block.less'; @import '../../src/license/license.block.less'; @import '../../src/login/loginModal/thirdPartySignOn/thirdPartySignOn.block.less'; @@ -116,7 +113,7 @@ @import '../../src/shared/text-label'; @import '../../src/shared/upgrade/upgrade.block.less'; @import '../../src/smart-status/smart-status.block.less'; -@import '../../src/standard-out/standard-out.block.less'; +@import '../../src/workflow-results/standard-out.block.less'; @import '../../src/system-tracking/date-picker/date-picker.block.less'; @import '../../src/system-tracking/fact-data-table/fact-data-table.block.less'; @import '../../src/system-tracking/fact-module-filter.block.less'; diff --git a/awx/ui/client/src/app.js b/awx/ui/client/src/app.js index 7399b6d130..0eec0fa356 100644 --- a/awx/ui/client/src/app.js +++ b/awx/ui/client/src/app.js @@ -19,7 +19,6 @@ import credentialTypes from './credential-types/main'; import organizations from './organizations/main'; import managementJobs from './management-jobs/main'; import workflowResults from './workflow-results/main'; -import jobResults from './job-results/main'; import jobSubmission from './job-submission/main'; import notifications from './notifications/main'; import about from './about/main'; @@ -30,7 +29,6 @@ import configuration from './configuration/main'; import home from './home/main'; import login from './login/main'; import activityStream from './activity-stream/main'; -import standardOut from './standard-out/main'; import Templates from './templates/main'; import teams from './teams/main'; import users from './users/main'; @@ -67,7 +65,6 @@ angular 'gettext', 'Timezones', 'lrInfiniteScroll', - about.name, access.name, license.name, @@ -86,10 +83,8 @@ angular login.name, activityStream.name, workflowResults.name, - jobResults.name, jobSubmission.name, notifications.name, - standardOut.name, Templates.name, portalMode.name, teams.name, @@ -242,23 +237,6 @@ angular $rootScope.crumbCache = []; $transitions.onStart({}, function(trans) { - // Remove any lingering intervals - // except on jobResults.* states - var jobResultStates = [ - 'jobResult', - 'jobResult.host-summary', - 'jobResult.host-event.details', - 'jobResult.host-event.json', - 'jobResult.host-events', - 'jobResult.host-event.stdout' - ]; - if ($rootScope.jobResultInterval && !_.includes(jobResultStates, trans.to().name) ) { - window.clearInterval($rootScope.jobResultInterval); - } - if ($rootScope.jobStdOutInterval && !_.includes(jobResultStates, trans.to().name) ) { - window.clearInterval($rootScope.jobStdOutInterval); - } - $rootScope.flashMessage = null; $('#form-modal2 .modal-body').empty(); diff --git a/awx/ui/client/src/job-results/event-queue.service.js b/awx/ui/client/src/job-results/event-queue.service.js deleted file mode 100644 index c97861ac6b..0000000000 --- a/awx/ui/client/src/job-results/event-queue.service.js +++ /dev/null @@ -1,77 +0,0 @@ -/************************************************* -* Copyright (c) 2016 Ansible, Inc. -* -* All Rights Reserved -*************************************************/ - -export default ['jobResultsService', 'parseStdoutService', function(jobResultsService, parseStdoutService){ - var val = {}; - - val = { - populateDefers: {}, - queue: {}, - // munge the raw event from the backend into the event_queue's format - munge: function(event) { - // basic data needed in the munged event - var mungedEvent = { - counter: event.counter, - id: event.id, - processed: false, - name: event.event_name, - changes: [] - }; - - // the interface for grabbing standard out is generalized and - // present across many types of events, so go ahead and check for - // updates to it - if (event.stdout) { - mungedEvent.stdout = parseStdoutService.parseStdout(event); - mungedEvent.start_line = event.start_line + 1; - mungedEvent.end_line = event.end_line + 1; - mungedEvent.actual_end_line = parseStdoutService.actualEndLine(event) + 1; - mungedEvent.changes.push('stdout'); - } - - // for different types of events, you need different types of data - if (event.event_name === 'playbook_on_start') { - mungedEvent.startTime = event.modified; - mungedEvent.changes.push('startTime'); - } if (event.event_name === 'playbook_on_stats') { - // get the data for populating the host status bar - mungedEvent.count = jobResultsService - .getCountsFromStatsEvent(event.event_data); - mungedEvent.finishedTime = event.modified; - mungedEvent.changes.push('count'); - mungedEvent.changes.push('countFinished'); - mungedEvent.changes.push('finishedTime'); - } - return mungedEvent; - }, - // reinitializes the event queue value for the job results page - initialize: function() { - val.queue = {}; - val.populateDefers = {}; - }, - // populates the event queue - populate: function(event) { - if (event) { - val.queue[event.counter] = val.munge(event); - - if (!val.queue[event.counter].processed) { - return val.munge(event); - } else { - return {}; - } - } else { - return {}; - } - }, - // the event has been processed in the view and should be marked as - // completed in the queue - markProcessed: function(event) { - val.queue[event.counter].processed = true; - } - }; - - return val; -}]; diff --git a/awx/ui/client/src/job-results/host-status-bar/host-status-bar.block.less b/awx/ui/client/src/job-results/host-status-bar/host-status-bar.block.less deleted file mode 100644 index ff27489751..0000000000 --- a/awx/ui/client/src/job-results/host-status-bar/host-status-bar.block.less +++ /dev/null @@ -1,87 +0,0 @@ -.HostStatusBar { - display: flex; - flex: 0 0 auto; - width: 100%; -} - -.HostStatusBar-ok, -.HostStatusBar-changed, -.HostStatusBar-unreachable, -.HostStatusBar-failures, -.HostStatusBar-skipped, -.HostStatusBar-noData { - height: 15px; - border-top: 5px solid @default-bg; - border-bottom: 5px solid @default-bg; -} - -.HostStatusBar-ok { - background-color: @default-succ; - display: flex; - flex: 0 0 auto; -} - -.HostStatusBar-changed { - background-color: @default-warning; - flex: 0 0 auto; -} - -.HostStatusBar-unreachable { - background-color: @default-unreachable; - flex: 0 0 auto; -} - -.HostStatusBar-dark { - background-color: @default-unreachable; - flex: 0 0 auto; -} - -.HostStatusBar-failures { - background-color: @default-err; - flex: 0 0 auto; -} - -.HostStatusBar-skipped { - background-color: @default-link; - flex: 0 0 auto; -} - -.HostStatusBar-noData { - background-color: @default-icon-hov; - flex: 1 0 auto; -} - -.HostStatusBar-tooltipLabel { - text-transform: uppercase; - margin-right: 15px; -} - -.HostStatusBar-tooltipBadge { - border-radius: 5px; - border: 1px solid @default-bg; -} - -.HostStatusBar-tooltipBadge--ok { - background-color: @default-succ; -} - -.HostStatusBar-tooltipBadge--unreachable { - background-color: @default-unreachable; -} - -.HostStatusBar-tooltipBadge--dark { - background-color: @default-unreachable; -} - -.HostStatusBar-tooltipBadge--skipped { - background-color: @default-link; -} - -.HostStatusBar-tooltipBadge--changed { - background-color: @default-warning; -} - -.HostStatusBar-tooltipBadge--failures { - background-color: @default-err; - -} diff --git a/awx/ui/client/src/job-results/host-status-bar/host-status-bar.directive.js b/awx/ui/client/src/job-results/host-status-bar/host-status-bar.directive.js deleted file mode 100644 index 5a8b5b3206..0000000000 --- a/awx/ui/client/src/job-results/host-status-bar/host-status-bar.directive.js +++ /dev/null @@ -1,47 +0,0 @@ -/************************************************* - * Copyright (c) 2016 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -// import hostStatusBarController from './host-status-bar.controller'; -export default [ 'templateUrl', - function(templateUrl) { - return { - scope: true, - templateUrl: templateUrl('job-results/host-status-bar/host-status-bar'), - restrict: 'E', - // controller: standardOutLogController, - link: function(scope) { - // as count is changed by event data coming in, - // update the host status bar - var toDestroy = scope.$watch('count', function(val) { - if (val) { - Object.keys(val).forEach(key => { - // reposition the hosts status bar by setting - // the various flex values to the count of - // those hosts - $(`.HostStatusBar-${key}`) - .css('flex', `${val[key]} 0 auto`); - - // set the tooltip to give how many hosts of - // each type - if (val[key] > 0) { - scope[`${key}CountTip`] = `${key}${val[key]}`; - } - }); - - // if there are any hosts that have finished, don't - // show default grey bar - scope.hasCount = (Object - .keys(val) - .filter(key => (val[key] > 0)).length > 0); - } - }); - - scope.$on('$destroy', function(){ - toDestroy(); - }); - } - }; -}]; diff --git a/awx/ui/client/src/job-results/host-status-bar/host-status-bar.partial.html b/awx/ui/client/src/job-results/host-status-bar/host-status-bar.partial.html deleted file mode 100644 index a8854b6d09..0000000000 --- a/awx/ui/client/src/job-results/host-status-bar/host-status-bar.partial.html +++ /dev/null @@ -1,30 +0,0 @@ -
-
-
-
-
-
-
-
-
diff --git a/awx/ui/client/src/job-results/host-status-bar/main.js b/awx/ui/client/src/job-results/host-status-bar/main.js deleted file mode 100644 index 2b17a2e414..0000000000 --- a/awx/ui/client/src/job-results/host-status-bar/main.js +++ /dev/null @@ -1,11 +0,0 @@ -/************************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -import hostStatusBar from './host-status-bar.directive'; - -export default - angular.module('hostStatusBarDirective', []) - .directive('hostStatusBar', hostStatusBar); diff --git a/awx/ui/client/src/job-results/job-results-stdout/job-results-stdout.block.less b/awx/ui/client/src/job-results/job-results-stdout/job-results-stdout.block.less deleted file mode 100644 index d186677cd1..0000000000 --- a/awx/ui/client/src/job-results/job-results-stdout/job-results-stdout.block.less +++ /dev/null @@ -1,271 +0,0 @@ -@breakpoint-md: 1200px; - -.JobResultsStdOut { - height: auto; - width: 100%; - display: flex; - flex-direction: column; - align-items: stretch; -} - -.JobResultsStdOut-toolbar { - flex: initial; - display: flex; - border-bottom: 0px; - border-radius: 5px; - border-bottom-left-radius: 0px; - border-bottom-right-radius: 0px; - user-select: none; - -moz-user-select: none; - -webkit-user-select: none; - -ms-user-select: none; -} - -.JobResultsStdOut-toolbarNumberColumn { - background-color: @default-list-header-bg; - color: @b7grey; - flex: initial; - display: flex; - justify-content: space-between; - width: 70px; - padding-bottom: 10px; - padding-left: 8px; - padding-right: 8px; - padding-top: 10px; - border-top-left-radius: 5px; - z-index: 1; - border-right: 1px solid @d7grey; -} - -.JobResultsStdOut-expandAllButton { - height: 18px; - width: 18px; - padding-left: 4px; - padding-top: 1px; - border-radius: 50%; - background-color: @default-bg; - font-size: 12px; - cursor: pointer; - color: #848992; -} - -.JobResultsStdOut-expandAllButton:hover .JobResultsStdOut-expandAllIcon, -.JobResultsStdOut-expandAllIcon:hover { - color: @default-data-txt; -} - -.JobResultsStdOut-toolbarStdoutColumn { - white-space: normal; - flex: 1; - display: flex; - justify-content: flex-end; - padding-right: 10px; - background-color: @default-secondary-bg; - border-top-right-radius: 5px; -} - -.JobResultsStdOut-followButton { - cursor: pointer; - width: 18px; - height: 18px; - width: 18px; - padding: 1px 0 0 4px; - border-radius: 50%; - margin-top: 10px; - font-size: 11px; - background-color: @default-icon; - color: @default-bg; -} - -.JobResultsStdOut-followIcon { - color: @default-bg; -} - -.JobResultsStdOut-followButton:hover { - background-color: @default-data-txt; -} - -.JobResultsStdOut-followButton.is-engaged { - background-color: @default-link; - color: @default-bg; -} - -.JobResultsStdOut-followButton.is-engaged .JobResultsStdOut-followIcon { - color: @default-bg; -} - -.JobResultsStdOut-followButton.is-engaged:hover { - background-color: @default-icon; -} - -.JobResultsStdOut-followButton.is-engaged:hover .JobResultsStdOut-followIcon, -.JobResultsStdOut-followButton.is-engaged .JobResultsStdOut-followIcon:hover { - color: @default-border; -} - -.JobResultsStdOut-stdoutContainer { - flex: 1; - position: relative; - background-color: @default-secondary-bg; - overflow-y: scroll; - overflow-x: hidden; -} - -.JobResultsStdOut-numberColumnPreload { - background-color: @default-list-header-bg; - border-right: 1px solid @d7grey; - position: absolute; - height: 100%; - width: 70px; -} - -.JobResultsStdOut-aLineOfStdOut { - display: flex; - font-family: Monaco, Menlo, Consolas, "Courier New", monospace; -} - -.JobResultsStdOut-lineExpander { - text-align: left; - padding-left: 11px; - margin-right: auto; -} - -.JobResultsStdOut-lineExpanderIcon { - font-size: 19px; - cursor: pointer; -} - -.JobResultsStdOut-lineExpanderIcon:hover { - color: @default-data-txt; -} - -.JobResultsStdOut-lineNumberColumn { - display: flex; - background-color: @default-list-header-bg; - text-align: right; - padding-right: 10px; - padding-top: 2px; - padding-bottom: 2px; - width: 75px; - flex: 1 0 70px; - user-select: none; - -moz-user-select: none; - -webkit-user-select: none; - -ms-user-select: none; - z-index: 1; - border-right: 1px solid @d7grey; - color: @default-icon; -} - -.JobResultsStdOut-stdoutColumn { - padding-left: 20px; - padding-right: 20px; - padding-top: 2px; - padding-bottom: 2px; - color: @default-interface-txt; - display: inline-block; - white-space: pre-wrap; - word-break: break-all; - width:100%; - background-color: @default-secondary-bg; -} - -.JobResultsStdOut-stdoutColumn--tooMany { - font-weight: bold; - text-transform: uppercase; - color: @default-err; -} - -.JobResultsStdOut-stdoutColumn--clickable { - cursor: pointer; -} - -.JobResultsStdOut-aLineOfStdOut:hover, -.JobResultsStdOut-aLineOfStdOut:hover .JobResultsStdOut-lineNumberColumn, -.JobResultsStdOut-aLineOfStdOut:hover .JobResultsStdOut-stdoutColumn { - background-color: @default-bg; -} - -.JobResultsStdOut-aLineOfStdOut:hover .JobResultsStdOut-lineNumberColumn { - border-right: 1px solid @default-bg; -} - -.JobResultsStdOut-footer { - height: 20px; - border-bottom-right-radius: 5px; - border-bottom-left-radius: 5px; - background-color: @default-secondary-bg; - border-top: 0px; - border-radius: 5px; - border-top-left-radius: 0px; - border-top-right-radius: 0px; - overflow: hidden; - margin-top: -1px; -} - -.JobResultsStdOut-footerNumberColumn { - background-color: @default-list-header-bg; - width: 70px; - height: 100%; - border-right: 1px solid @d7grey; -} - -.JobResultsStdOut-followAnchor { - height: 0px; -} - -.JobResultsStdOut-toTop { - color: @default-icon; - cursor: pointer; - font-family: monaco; - font-size: 10px; - margin-right: 20px; - text-align: right; - display: flex; - - span { - margin-left: auto; - } -} - -.JobResultsStdOut-toTop--numberColumn { - background: @default-list-header-bg; - height: 40px; - width: 70px; - border-right: 1px solid #D7D7D7; -} - -.JobResultsStdOut-toTop:hover { - color: @default-data-txt; -} - -.JobResultsStdOut-cappedLine { - color: @b7grey; - font-style: italic; -} - -@media (max-width: @breakpoint-md) { - .JobResultsStdOut-numberColumnPreload { - display: none; - } - - .JobResultsStdOut-topAnchor { - position: static; - width: 100%; - top: -20px; - margin-top: -250px; - margin-bottom: 250px; - } - - .JobResultsStdOut-followAnchor { - height: 0px; - } - - .JobResultsStdOut-stdoutContainer { - overflow-y: auto; - } - - .JobResultsStdOut-lineAnchor { - display: none !important; - } -} diff --git a/awx/ui/client/src/job-results/job-results-stdout/job-results-stdout.directive.js b/awx/ui/client/src/job-results/job-results-stdout/job-results-stdout.directive.js deleted file mode 100644 index 14a34a607a..0000000000 --- a/awx/ui/client/src/job-results/job-results-stdout/job-results-stdout.directive.js +++ /dev/null @@ -1,415 +0,0 @@ -/************************************************* - * Copyright (c) 2016 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -// import hostStatusBarController from './host-status-bar.controller'; -export default [ 'templateUrl', '$timeout', '$location', '$anchorScroll', - function(templateUrl, $timeout, $location, $anchorScroll) { - return { - scope: false, - templateUrl: templateUrl('job-results/job-results-stdout/job-results-stdout'), - restrict: 'E', - link: function(scope, element) { - var toDestroy = [], - resizer, - scrollWatcher; - - scope.$on('$destroy', function(){ - $(window).off("resize", resizer); - $(window).off("scroll", scrollWatcher); - $(".JobResultsStdOut-stdoutContainer").off('scroll', - scrollWatcher); - toDestroy.forEach(closureFunc => closureFunc()); - }); - - scope.stdoutContainerAvailable.resolve("container available"); - // utility function used to find the top visible line and - // parent header in the pane - // - // note that while this function is called when in mobile width - // the line anchor is not displayed in the css so calls - // to lineAnchor do nothing - var findTopLines = function() { - var $container = $('.JobResultsStdOut-stdoutContainer'); - - // get the first visible line's head element - var getHeadElement = function (line) { - var lineHasHeaderClass = !!(line - .hasClass("header_play") || - line.hasClass("header_task")); - var lineClassList; - var lineUUIDClass; - - if (lineHasHeaderClass) { - // find head element when the first visible - // line is a header - - lineClassList = line.attr("class") - .split(" "); - - // get the header class w task uuid... - lineUUIDClass = lineClassList - .filter(n => n - .indexOf("header_task_") > -1)[0]; - - // ...if that doesn't exist get the one - // w play uuid - if (!lineUUIDClass) { - lineUUIDClass = lineClassList - .filter(n => n. - indexOf("header_play_") > -1)[0]; - } - - // get the header line (not their might - // be more than one, so get the one with - // the actual header class) - // - // TODO it might be better in this case to just - // return `line` (less jumping with a cowsay - // case) - return $(".actual_header." + - lineUUIDClass); - } else { - // find head element when the first visible - // line is not a header - - lineClassList = line.attr("class") - .split(" "); - - // get the class w task uuid... - lineUUIDClass = lineClassList - .filter(n => n - .indexOf("task_") > -1)[0]; - - // ...if that doesn't exist get the one - // w play uuid - if (!lineUUIDClass) { - lineUUIDClass = lineClassList - .filter(n => n - .indexOf("play_") > -1)[0]; - } - - // get the header line (not their might - // be more than one, so get the one with - // the actual header class) - return $(".actual_header.header_" + - lineUUIDClass); - } - }; - - var visItem, - parentItem; - - // iterate through each line of standard out - $container.find('.JobResultsStdOut-aLineOfStdOut:visible') - .each( function () { - var $this = $(this); - - // check to see if the line is the first visible - // line in the viewport... - if ($this.position().top >= 0) { - - // ...if it is, return the line number - // for this line - visItem = parseInt($($this - .children()[0]) - .text()); - - // as well as the line number for it's - // closest parent header line - var $head = getHeadElement($this); - parentItem = parseInt($($head - .children()[0]) - .text()); - - // stop iterating over the standard out - // lines once the first one has been - // found - - $this = null; - return false; - } - - $this = null; - }); - - $container = null; - - return { - visLine: visItem, - parentVisLine: parentItem - }; - }; - - // find if window is initially mobile or desktop width - if (window.innerWidth <= 1200) { - scope.isMobile = true; - } else { - scope.isMobile = false; - } - - resizer = function() { - // and update the isMobile var accordingly - if (window.innerWidth <= 1200 && !scope.isMobile) { - scope.isMobile = true; - } else if (window.innerWidth > 1200 & scope.isMobile) { - scope.isMobile = false; - } - }; - // watch changes to the window size - $(window).resize(resizer); - - var lastScrollTop; - - var initScrollTop = function() { - lastScrollTop = 0; - }; - scrollWatcher = function() { - var st = $(this).scrollTop(); - var netScroll = st + $(this).innerHeight(); - var fullHeight; - - if (st < lastScrollTop){ - // user up scrolled, so disengage follow - scope.followEngaged = false; - } - - if (scope.isMobile) { - // for mobile the height is the body of the entire - // page - fullHeight = $("body").height(); - } else { - // for desktop the height is the body of the - // stdoutContainer, minus the "^ TOP" indicator - fullHeight = $(this)[0].scrollHeight - 25; - } - - if(netScroll >= fullHeight) { - // user scrolled all the way to bottom, so engage - // follow - scope.followEngaged = true; - } - - // pane is now overflowed, show top indicator. - if (st > 0) { - scope.stdoutOverflowed = true; - } - - lastScrollTop = st; - - st = null; - netScroll = null; - fullHeight = null; - }; - - // update scroll watchers when isMobile changes based on - // window resize - toDestroy.push(scope.$watch('isMobile', function(val) { - if (val === true) { - // make sure ^ TOP always shown for mobile - scope.stdoutOverflowed = true; - - // unbind scroll watcher on standard out container - $(".JobResultsStdOut-stdoutContainer") - .unbind('scroll'); - - // init scroll watcher on window - initScrollTop(); - $(window).on('scroll', scrollWatcher); - - } else if (val === false) { - // unbind scroll watcher on window - $(window).unbind('scroll'); - - // init scroll watcher on standard out container - initScrollTop(); - $(".JobResultsStdOut-stdoutContainer").on('scroll', - scrollWatcher); - } - })); - - // called to scroll to follow anchor - scope.followScroll = function() { - // a double-check to make sure the follow anchor is at - // the bottom of the standard out container - $(".JobResultsStdOut-followAnchor") - .appendTo(".JobResultsStdOut-stdoutContainer"); - - $location.hash('followAnchor'); - $anchorScroll(); - }; - - // called to scroll to top of standard out (when "^ TOP" is - // clicked) - scope.toTop = function() { - $location.hash('topAnchor'); - $anchorScroll(); - }; - - // called to scroll to the current line anchor - // when expand all/collapse all/filtering is engaged - // - // note that while this function can be called when in mobile - // width the line anchor is not displayed in the css so those - // calls do nothing - scope.lineAnchor = function() { - $location.hash('lineAnchor'); - $anchorScroll(); - }; - - // if following becomes active, go ahead and get to the bottom - // of the standard out pane - toDestroy.push(scope.$watch('followEngaged', function(val) { - // scroll to follow point if followEngaged is true - if (val) { - scope.followScroll(); - } - - // set up tooltip changes for not finsihed job - if (!scope.jobFinished) { - if (val) { - scope.followTooltip = "Currently following standard out as it comes in. Click to unfollow."; - } else { - scope.followTooltip = "Click to follow standard out as it comes in."; - } - } - })); - - // follow button ng-click function - scope.followToggleClicked = function() { - if (scope.jobFinished) { - // when the job is finished engage followScroll - scope.followScroll(); - } else { - // when the job is not finished toggle followEngaged - // which is watched above - scope.followEngaged = !scope.followEngaged; - } - }; - - // expand all/collapse all ng-click function - scope.toggleAllStdout = function(type) { - // find the top visible line in the container currently, - // as well as the header parent of that line - var topLines = findTopLines(); - - if (type === 'expand') { - // for expand prepend the lineAnchor to the visible - // line - $(".line_num_" + topLines.visLine) - .prepend($("#lineAnchor")); - } else { - // for collapse prepent the lineAnchor to the - // visible line's parent header - $(".line_num_" + topLines.parentVisLine) - .prepend($("#lineAnchor")); - } - - var expandClass; - if (type === 'expand') { - // for expand all, you'll need to find all the - // collapsed headers to expand them - expandClass = "fa-caret-right"; - } else { - // for collapse all, you'll need to find all the - // expanded headers to collapse them - expandClass = "fa-caret-down"; - } - - // find all applicable task headers that need to be - // toggled - element.find(".expanderizer--task."+expandClass) - .each((i, val) => { - // and trigger their expansion/collapsing - $timeout(function(){ - // TODO change to a direct call of the - // toggleLine function - angular.element(val).trigger('click'); - // TODO only call lineAnchor for those - // that are above the first visible line - scope.lineAnchor(); - }); - }); - - // find all applicable play headers that need to be - // toggled - element.find(".expanderizer--play."+expandClass) - .each((i, val) => { - // for collapse all, only collapse play - // headers that do not have children task - // headers - if(angular.element("." + - angular.element(val).attr("data-uuid")) - .find(".expanderizer--task") - .length === 0 || - type !== 'collapse') { - - // trigger their expansion/ - // collapsing - $timeout(function(){ - // TODO change to a direct - // call of the - // toggleLine function - angular.element(val) - .trigger('click'); - // TODO only call lineAnchor - // for those that are above - // the first visible line - scope.lineAnchor(); - }); - } - }); - }; - - // expand/collapse triangle ng-click function - scope.toggleLine = function($event, id) { - // if the section is currently expanded - if ($($event.currentTarget).hasClass("fa-caret-down")) { - // hide all the children lines - $(id).hide(); - - // and change the triangle for the header to collapse - $($event.currentTarget) - .removeClass("fa-caret-down"); - $($event.currentTarget) - .addClass("fa-caret-right"); - } else { - // if the section is currently collapsed - - // show all the children lines - $(id).show(); - - // and change the triangle for the header to expanded - $($event.currentTarget) - .removeClass("fa-caret-right"); - $($event.currentTarget) - .addClass("fa-caret-down"); - - // if the section you expanded is a play - if ($($event.currentTarget) - .hasClass("expanderizer--play")) { - // find all children task headers and - // expand them too - $("." + $($event.currentTarget) - .attr("data-uuid")) - .find(".expanderizer--task") - .each((i, val) => { - if ($(val) - .hasClass("fa-caret-right")) { - $timeout(function(){ - // TODO change to a - // direct call of the - // toggleLine function - angular.element(val) - .trigger('click'); - }); - } - }); - } - } - }; - } - }; -}]; diff --git a/awx/ui/client/src/job-results/job-results-stdout/job-results-stdout.partial.html b/awx/ui/client/src/job-results/job-results-stdout/job-results-stdout.partial.html deleted file mode 100644 index 63ae96d90c..0000000000 --- a/awx/ui/client/src/job-results/job-results-stdout/job-results-stdout.partial.html +++ /dev/null @@ -1,65 +0,0 @@ -
-
-
-
- - -
-
- - -
-
-
-
- - -
-
-
-
-
-
-
-
-
- -
-
The standard output is too large to display. Please specify additional filters to narrow the standard out.
-
Too much previous output to display. Showing running standard output.
-
Job details are not available for this job. Please download to view standard out.
-
- -
-
-
-
- -
diff --git a/awx/ui/client/src/job-results/job-results-stdout/main.js b/awx/ui/client/src/job-results/job-results-stdout/main.js deleted file mode 100644 index 5fc583b9b1..0000000000 --- a/awx/ui/client/src/job-results/job-results-stdout/main.js +++ /dev/null @@ -1,11 +0,0 @@ -/************************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -import jobResultsStdOut from './job-results-stdout.directive'; - -export default - angular.module('jobResultStdOutDirective', []) - .directive('jobResultsStandardOut', jobResultsStdOut); diff --git a/awx/ui/client/src/job-results/job-results.block.less b/awx/ui/client/src/job-results/job-results.block.less deleted file mode 100644 index 356aeb5a6a..0000000000 --- a/awx/ui/client/src/job-results/job-results.block.less +++ /dev/null @@ -1,248 +0,0 @@ -@breakpoint-md: 1200px; - -.JobResults { - .OnePlusTwo-container(100%, @breakpoint-md); - - &.fullscreen { - .JobResults-rightSide { - max-width: 100%; - } - } -} - -.JobResults-leftSide { - .OnePlusTwo-left--panel(100%, @breakpoint-md); - max-width: 30%; - height: ~"calc(100vh - 177px)"; - - @media screen and (max-width: @breakpoint-md) { - max-width: 100%; - } -} - -.JobResults-rightSide { - .OnePlusTwo-right--panel(100%, @breakpoint-md); - height: ~"calc(100vh - 177px)"; - - @media (max-width: @breakpoint-md - 1px) { - padding-right: 15px; - } -} - -.JobResults-detailsPanel{ - overflow-y: scroll; -} - -.JobResults-stdoutActionButton--active { - display: none; - visibility: hidden; - flex:none; - width:0px; - padding-right: 0px; -} - -.JobResults-panelHeader { - display: flex; - height: 30px; -} - -.JobResults-panelHeaderText { - color: @default-interface-txt; - flex: 1 0 auto; - font-size: 14px; - font-weight: bold; - margin-right: 10px; - text-transform: uppercase; -} - -.JobResults-panelHeaderButtonActions { - display: flex; -} - -.JobResults-resultRow { - width: 100%; - display: flex; - padding-bottom: 10px; - flex-wrap: wrap; -} - -.JobResults-resultRow--variables { - flex-direction: column; - - #cm-variables-container { - width: 100%; - } -} - -.JobResults-resultRowLabel { - text-transform: uppercase; - color: @default-interface-txt; - font-size: 12px; - font-weight: normal!important; - width: 30%; - margin-right: 20px; - - @media screen and (max-width: @breakpoint-md) { - flex: 2.5 0 auto; - } -} - -.JobResults-resultRowLabel--fullWidth { - width: 100%; - margin-right: 0px; -} - -.JobResults-resultRowText { - width: ~"calc(70% - 20px)"; - flex: 1 0 auto; - text-transform: none; - word-wrap: break-word; -} - -.JobResults-resultRowText--fullWidth { - width: 100%; -} - -.JobResults-expandArrow { - color: #D7D7D7; - font-size: 14px; - font-weight: bold; - margin-right: 10px; - text-transform: uppercase; - margin-left: 10px; -} - -.JobResults-resultRowText--instanceGroup { - display: flex; -} - -.JobResults-isolatedBadge { - align-items: center; - background-color: @default-list-header-bg; - border-radius: 5px; - color: @default-stdout-txt; - display: flex; - font-size: 10px; - height: 16px; - margin: 3px 0 0 10px; - padding: 0 10px; - text-transform: uppercase; -} - -.JobResults-statusResultIcon { - padding-left: 0px; - padding-right: 10px; -} - -.JobResults-badgeRow { - display: flex; - align-items: center; - margin-right: 5px; -} - -.JobResults-badgeTitle{ - color: @default-interface-txt; - font-size: 14px; - margin-right: 10px; - font-weight: normal; - text-transform: uppercase; - margin-left: 20px; -} - -@media (max-width: @breakpoint-md) { - .JobResults-detailsPanel { - overflow-y: auto; - } - - .JobResults-rightSide { - height: inherit; - } -} - -.JobResults-timeBadge { - float:right; - font-size: 11px; - font-weight: normal; - padding: 1px 10px; - height: 14px; - margin: 3px 15px; - width: 80px; - background-color: @default-bg; - border-radius: 5px; - color: @default-interface-txt; - margin-right: -5px; -} - -.JobResults-panelRight { - display: flex; - flex-direction: column; -} - -.JobResults-panelRight .SmartSearch-bar { - width: 100%; -} - -.JobResults-panelRightTitle{ - flex-wrap: wrap; -} - -.JobResults-panelRightTitleText{ - word-wrap: break-word; - word-break: break-all; - max-width: 100%; -} - -.JobResults-badgeAndActionRow{ - display:flex; - flex: 1 0 auto; - justify-content: flex-end; - flex-wrap: wrap; - max-width: 100%; -} - -.StandardOut-panelHeader { - flex: initial; -} - -.StandardOut-panelHeader--jobIsRunning { - margin-bottom: 20px; -} - -host-status-bar { - flex: initial; - margin-bottom: 20px; -} - -smart-search { - flex: initial; -} - -job-results-standard-out { - flex: 1; - flex-basis: auto; - height: ~"calc(100% - 800px)"; - display: flex; - border: 1px solid @d7grey; - border-radius: 5px; - margin-top: 20px; -} -@media screen and (max-width: @breakpoint-md) { - job-results-standard-out { - height: auto; - } -} - -.JobResults-extraVarsHelp { - margin-left: 10px; - color: @default-icon; -} - -.JobResults-seeMoreLess { - color: #337AB7; - margin: 4px 0px; - text-transform: uppercase; - padding: 2px 0px; - cursor: pointer; - border-radius: 5px; - font-size: 11px; -} diff --git a/awx/ui/client/src/job-results/job-results.controller.js b/awx/ui/client/src/job-results/job-results.controller.js deleted file mode 100644 index e7e8f2c716..0000000000 --- a/awx/ui/client/src/job-results/job-results.controller.js +++ /dev/null @@ -1,784 +0,0 @@ -export default ['jobData', 'jobDataOptions', 'jobLabels', 'jobFinished', 'count', '$scope', 'ParseTypeChange', - 'ParseVariableString', 'jobResultsService', 'eventQueue', '$compile', '$log', 'Dataset', '$q', - 'QuerySet', '$rootScope', 'moment', '$stateParams', 'i18n', 'fieldChoices', 'fieldLabels', - 'workflowResultsService', 'statusSocket', 'GetBasePath', '$state', 'jobExtraCredentials', -function(jobData, jobDataOptions, jobLabels, jobFinished, count, $scope, ParseTypeChange, - ParseVariableString, jobResultsService, eventQueue, $compile, $log, Dataset, $q, - QuerySet, $rootScope, moment, $stateParams, i18n, fieldChoices, fieldLabels, - workflowResultsService, statusSocket, GetBasePath, $state, jobExtraCredentials) { - - var toDestroy = []; - var cancelRequests = false; - var runTimeElapsedTimer = null; - - // download stdout tooltip text - $scope.standardOutTooltip = i18n._('Download Output'); - - // stdout full screen toggle tooltip text - $scope.toggleStdoutFullscreenTooltip = i18n._("Expand Output"); - - // this allows you to manage the timing of rest-call based events as - // filters are updated. see processPage for more info - var currentContext = 1; - $scope.firstCounterFromSocket = -1; - - $scope.explanationLimit = 150; - - // if the user enters the page mid-run, reset the search to include a param - // to only grab events less than the first counter from the websocket events - toDestroy.push($scope.$watch('firstCounterFromSocket', function(counter) { - if (counter > -1) { - // make it so that the search include a counter less than the - // first counter from the socket - let params = _.cloneDeep($stateParams.job_event_search); - params.counter__lte = "" + counter; - - Dataset = QuerySet.search(jobData.related.job_events, - params); - - Dataset.then(function(actualDataset) { - $scope.job_event_dataset = actualDataset.data; - }); - } - })); - - // used for tag search - $scope.job_event_dataset = Dataset.data; - - // used for tag search - $scope.list = { - basePath: jobData.related.job_events, - name: 'job_events' - }; - - // used for tag search - $scope.job_events = $scope.job_event_dataset.results; - - $scope.jobExtraCredentials = jobExtraCredentials; - - var getLinks = function() { - var getLink = function(key) { - if(key === 'schedule') { - if($scope.job.related.schedule) { - return '/#/templates/job_template/' + $scope.job.job_template + '/schedules' + $scope.job.related.schedule.split(/api\/v\d+\/schedules/)[1]; - } - else { - return null; - } - } - else if(key === 'inventory') { - if($scope.job.summary_fields.inventory && $scope.job.summary_fields.inventory.id) { - if($scope.job.summary_fields.inventory.kind && $scope.job.summary_fields.inventory.kind === 'smart') { - return '/#/inventories/smart/' + $scope.job.summary_fields.inventory.id; - } - else { - return '/#/inventories/inventory/' + $scope.job.summary_fields.inventory.id; - } - } - else { - return null; - } - } - else { - if ($scope.job.related[key]) { - return '/#/' + $scope.job.related[key] - .split(/api\/v\d+\//)[1]; - } else { - return null; - } - } - }; - - $scope.created_by_link = getLink('created_by'); - $scope.scheduled_by_link = getLink('schedule'); - $scope.inventory_link = getLink('inventory'); - $scope.project_link = getLink('project'); - $scope.machine_credential_link = getLink('credential'); - $scope.cloud_credential_link = getLink('cloud_credential'); - $scope.network_credential_link = getLink('network_credential'); - $scope.vault_credential_link = getLink('vault_credential'); - $scope.schedule_link = getLink('schedule'); - }; - - // uses options to set scope variables to their readable string - // value - var getLabels = function() { - var getLabel = function(key) { - if ($scope.jobOptions && $scope.jobOptions[key]) { - return $scope.jobOptions[key].choices - .filter(val => val[0] === $scope.job[key]) - .map(val => val[1])[0]; - } else { - return null; - } - }; - - $scope.type_label = getLabel('job_type'); - $scope.verbosity_label = getLabel('verbosity'); - }; - - var getTotalHostCount = function(count) { - return Object - .keys(count).reduce((acc, i) => acc += count[i], 0); - }; - - // put initially resolved request data on scope - $scope.job = jobData; - $scope.jobOptions = jobDataOptions.actions.GET; - $scope.labels = jobLabels; - $scope.jobFinished = jobFinished; - - // update label in left pane and tooltip in right pane when the job_status - // changes - toDestroy.push($scope.$watch('job_status', function(status) { - if (status) { - $scope.status_label = $scope.jobOptions.status.choices - .filter(val => val[0] === status) - .map(val => val[1])[0]; - $scope.status_tooltip = "Job " + $scope.status_label; - } - })); - - $scope.previousTaskFailed = false; - - toDestroy.push($scope.$watch('job.job_explanation', function(explanation) { - if (explanation && explanation.split(":")[0] === "Previous Task Failed") { - $scope.previousTaskFailed = true; - - var taskObj = JSON.parse(explanation.substring(explanation.split(":")[0].length + 1)); - // return a promise from the options request with the permission type choices (including adhoc) as a param - var fieldChoice = fieldChoices({ - $scope: $scope, - url: GetBasePath('unified_jobs'), - field: 'type' - }); - - // manipulate the choices from the options request to be set on - // scope and be usable by the list form - fieldChoice.then(function (choices) { - choices = - fieldLabels({ - choices: choices - }); - $scope.explanation_fail_type = choices[taskObj.job_type]; - $scope.explanation_fail_name = taskObj.job_name; - $scope.explanation_fail_id = taskObj.job_id; - $scope.task_detail = $scope.explanation_fail_type + " failed for " + $scope.explanation_fail_name + " with ID " + $scope.explanation_fail_id + "."; - }); - } else { - $scope.previousTaskFailed = false; - } - })); - - - // update the job_status value. Use the cached rootScope value if there - // is one. This is a workaround when the rest call for the jobData is - // made before some socket events come in for the job status - if ($rootScope['lastSocketStatus' + jobData.id]) { - $scope.job_status = $rootScope['lastSocketStatus' + jobData.id]; - delete $rootScope['lastSocketStatus' + jobData.id]; - } else { - $scope.job_status = jobData.status; - } - - // turn related api browser routes into front end routes - getLinks(); - - // the links below can't be set in getLinks because the - // links on the UI don't directly match the corresponding URL - // on the API browser - if(jobData.summary_fields && jobData.summary_fields.job_template && - jobData.summary_fields.job_template.id){ - $scope.job_template_link = `/#/templates/job_template/${$scope.job.summary_fields.job_template.id}`; - } - if(jobData.summary_fields && jobData.summary_fields.project_update && - jobData.summary_fields.project_update.status){ - $scope.project_status = jobData.summary_fields.project_update.status; - } - if(jobData.summary_fields && jobData.summary_fields.project_update && - jobData.summary_fields.project_update.id){ - $scope.project_update_link = `/#/scm_update/${jobData.summary_fields.project_update.id}`; - } - if(jobData.summary_fields && jobData.summary_fields.source_workflow_job && - jobData.summary_fields.source_workflow_job.id){ - $scope.workflow_result_link = `/#/workflows/${jobData.summary_fields.source_workflow_job.id}`; - } - if(jobData.result_traceback) { - $scope.job.result_traceback = jobData.result_traceback.trim().split('\n').join('
'); - } - - // use options labels to manipulate display of details - getLabels(); - - // set up a read only code mirror for extra vars - $scope.variables = ParseVariableString($scope.job.extra_vars); - $scope.parseType = 'yaml'; - ParseTypeChange({ scope: $scope, - field_id: 'pre-formatted-variables', - readOnly: true }); - - // Click binding for the expand/collapse button on the standard out log - $scope.stdoutFullScreen = false; - $scope.toggleStdoutFullscreen = function() { - $scope.stdoutFullScreen = !$scope.stdoutFullScreen; - - if ($scope.stdoutFullScreen === true) { - $scope.toggleStdoutFullscreenTooltip = i18n._("Collapse Output"); - } else if ($scope.stdoutFullScreen === false) { - $scope.toggleStdoutFullscreenTooltip = i18n._("Expand Output"); - } - }; - - $scope.deleteJob = function() { - jobResultsService.deleteJob($scope.job); - }; - - $scope.cancelJob = function() { - jobResultsService.cancelJob($scope.job); - }; - - $scope.lessLabels = false; - $scope.toggleLessLabels = function() { - if (!$scope.lessLabels) { - $('#job-results-labels').slideUp(200); - $scope.lessLabels = true; - } - else { - $('#job-results-labels').slideDown(200); - $scope.lessLabels = false; - } - }; - - // get initial count from resolve - $scope.count = count.val; - $scope.hostCount = getTotalHostCount(count.val); - $scope.countFinished = count.countFinished; - - // if the job is still running engage following of the last line in the - // standard out pane - $scope.followEngaged = !$scope.jobFinished; - - // follow button for completed job should specify that the - // button will jump to the bottom of the standard out pane, - // not follow lines as they come in - if ($scope.jobFinished) { - $scope.followTooltip = i18n._("Jump to last line of standard out."); - } else { - $scope.followTooltip = i18n._("Currently following standard out as it comes in. Click to unfollow."); - } - - $scope.events = {}; - - function updateJobElapsedTimer(time) { - $scope.job.elapsed = time; - } - - // For elapsed time while a job is running, compute the differnce in seconds, - // from the time the job started until now. Moment() returns the current - // time as a moment object. - if ($scope.job.started !== null && $scope.job.status === 'running') { - runTimeElapsedTimer = workflowResultsService.createOneSecondTimer($scope.job.started, updateJobElapsedTimer); - } - - // EVENT STUFF BELOW - var linesInPane = []; - - function addToLinesInPane(event) { - var arr = _.range(event.start_line, event.actual_end_line); - linesInPane = linesInPane.concat(arr); - linesInPane = linesInPane.sort(function(a, b) { - return a - b; - }); - } - - function appendToBottom (event){ - // if we get here then the event type was either a - // header line, recap line, or one of the additional - // event types, so we append it to the bottom. - // These are the event types for captured - // stdout not directly related to playbook or runner - // events: - // (0, 'debug', _('Debug'), False), - // (0, 'verbose', _('Verbose'), False), - // (0, 'deprecated', _('Deprecated'), False), - // (0, 'warning', _('Warning'), False), - // (0, 'system_warning', _('System Warning'), False), - // (0, 'error', _('Error'), True), - angular - .element(".JobResultsStdOut-stdoutContainer") - .append($compile(event - .stdout)($scope.events[event - .counter])); - } - - function putInCorrectPlace(event) { - if (linesInPane.length) { - for (var i = linesInPane.length - 1; i >= 0; i--) { - if (event.start_line > linesInPane[i]) { - $(`.line_num_${linesInPane[i]}`) - .after($compile(event - .stdout)($scope.events[event - .counter])); - i = -1; - } - } - } else { - appendToBottom(event); - } - } - - // This is where the async updates to the UI actually happen. - // Flow is event queue munging in the service -> $scope setting in here - var processEvent = function(event, context) { - // only care about filter context checking when the event comes - // as a rest call - if (context && context !== currentContext) { - return; - } - // put the event in the queue - var mungedEvent = eventQueue.populate(event); - - // make changes to ui based on the event returned from the queue - if (mungedEvent.changes) { - mungedEvent.changes.forEach(change => { - // we've got a change we need to make to the UI! - // update the necessary scope and make the change - if (change === 'startTime' && !$scope.job.start) { - $scope.job.start = mungedEvent.startTime; - } - - if (change === 'count' && !$scope.countFinished) { - // for all events that affect the host count, - // update the status bar as well as the host - // count badge - $scope.count = mungedEvent.count; - $scope.hostCount = getTotalHostCount(mungedEvent - .count); - } - - if (change === 'finishedTime' && !$scope.job.finished) { - $scope.job.finished = mungedEvent.finishedTime; - $scope.jobFinished = true; - $scope.followTooltip = i18n._("Jump to last line of standard out."); - if ($scope.followEngaged) { - if (!$scope.followScroll) { - $scope.followScroll = function() { - $log.error("follow scroll undefined, standard out directive not loaded yet?"); - }; - } - $scope.followScroll(); - } - } - - if (change === 'countFinished') { - // the playbook_on_stats event actually lets - // us know that we don't need to iteratively - // look at event to update the host counts - // any more. - $scope.countFinished = true; - } - - if(change === 'stdout'){ - if (!$scope.events[mungedEvent.counter]) { - // line hasn't been put in the pane yet - - // create new child scope - $scope.events[mungedEvent.counter] = $scope.$new(); - $scope.events[mungedEvent.counter] - .event = mungedEvent; - - // let's see if we have a specific place to put it in - // the pane - let $prevElem = $(`.next_is_${mungedEvent.start_line}`); - if ($prevElem && $prevElem.length) { - // if so, put it there - $(`.next_is_${mungedEvent.start_line}`) - .after($compile(mungedEvent - .stdout)($scope.events[mungedEvent - .counter])); - addToLinesInPane(mungedEvent); - } else { - var putIn; - var classList = $("div", - "
"+mungedEvent.stdout+"
") - .attr("class").split(" "); - if (classList - .filter(v => v.indexOf("task_") > -1) - .length) { - putIn = classList - .filter(v => v.indexOf("task_") > -1)[0]; - } else if(classList - .filter(v => v.indexOf("play_") > -1) - .length) { - putIn = classList - .filter(v => v.indexOf("play_") > -1)[0]; - } - - var putAfter; - var isDup = false; - - if ($(".header_" + putIn + ",." + putIn).length === 0) { - putInCorrectPlace(mungedEvent); - addToLinesInPane(mungedEvent); - } else { - $(".header_" + putIn + ",." + putIn) - .each((i, v) => { - if (angular.element(v).scope() - .event.start_line < mungedEvent - .start_line) { - putAfter = v; - } else if (angular.element(v).scope() - .event.start_line === mungedEvent - .start_line) { - isDup = true; - return false; - } else if (angular.element(v).scope() - .event.start_line > mungedEvent - .start_line) { - return false; - } else { - appendToBottom(mungedEvent); - addToLinesInPane(mungedEvent); - } - }); - } - - if (!isDup && putAfter) { - addToLinesInPane(mungedEvent); - $(putAfter).after($compile(mungedEvent - .stdout)($scope.events[mungedEvent - .counter])); - } - - - classList = null; - putIn = null; - } - - // delete ref to the elem because it might leak scope - // if you don't - $prevElem = null; - } - - // move the followAnchor to the bottom of the - // container - $(".JobResultsStdOut-followAnchor") - .appendTo(".JobResultsStdOut-stdoutContainer"); - } - }); - - // the changes have been processed in the ui, mark it in the - // queue - eventQueue.markProcessed(event); - } - }; - - $scope.stdoutContainerAvailable = $q.defer(); - $scope.hasSkeleton = $q.defer(); - - eventQueue.initialize(); - - $scope.playCount = 0; - $scope.taskCount = 0; - - - // used to show a message to just download for old jobs - // remove in 3.2.0 - $scope.isOld = 0; - $scope.showLegacyJobErrorMessage = false; - - toDestroy.push($scope.$watch('isOld', function (val) { - if (val >= 2) { - $scope.showLegacyJobErrorMessage = true; - } - })); - - // get header and recap lines - var skeletonPlayCount = 0; - var skeletonTaskCount = 0; - var getSkeleton = function(url) { - jobResultsService.getEvents(url) - .then(events => { - events.results.forEach(event => { - if (event.start_line === 0 && event.end_line === 0) { - $scope.isOld++; - } - // get the name in the same format as the data - // coming over the websocket - event.event_name = event.event; - delete event.event; - - // increment play and task count - if (event.event_name === "playbook_on_play_start") { - skeletonPlayCount++; - } else if (event.event_name === "playbook_on_task_start") { - skeletonTaskCount++; - } - - processEvent(event); - }); - if (events.next) { - getSkeleton(events.next); - } else { - // after the skeleton requests have completed, - // put the play and task count into the dom - $scope.playCount = skeletonPlayCount; - $scope.taskCount = skeletonTaskCount; - $scope.hasSkeleton.resolve("skeleton resolved"); - } - }); - }; - - $scope.stdoutContainerAvailable.promise.then(() => { - getSkeleton(jobData.related.job_events + "?order_by=start_line&or__event__in=playbook_on_start,playbook_on_play_start,playbook_on_task_start,playbook_on_stats"); - }); - - var getEvents; - - var processPage = function(events, context) { - // currentContext is the context of the filter when this request - // to processPage was made - // - // currentContext is the context of the filter currently - // - // if they are not the same, make sure to stop process events/ - // making rest calls for next pages/etc. (you can see context is - // also passed into getEvents and processEvent and similar checks - // exist in these functions) - // - // also, if the page doesn't contain results (i.e.: the response - // returns an error), don't process the page - if (context !== currentContext || events === undefined || - events.results === undefined) { - return; - } - - events.results.forEach(event => { - // get the name in the same format as the data - // coming over the websocket - event.event_name = event.event; - delete event.event; - - processEvent(event, context); - }); - if (events.next && !cancelRequests) { - getEvents(events.next, context); - } else { - // put those paused events into the pane - $scope.gotPreviouslyRanEvents.resolve(""); - } - }; - - // grab non-header recap lines - getEvents = function(url, context) { - if (context !== currentContext) { - return; - } - - jobResultsService.getEvents(url) - .then(events => { - processPage(events, context); - }); - }; - - // grab non-header recap lines - toDestroy.push($scope.$watch('job_event_dataset', function(val) { - if (val) { - eventQueue.initialize(); - - Object.keys($scope.events) - .forEach(v => { - // dont destroy scope events for skeleton lines - let name = $scope.events[v].event.name; - - if (!(name === "playbook_on_play_start" || - name === "playbook_on_task_start" || - name === "playbook_on_stats")) { - $scope.events[v].$destroy(); - $scope.events[v] = null; - delete $scope.events[v]; - } - }); - - // pause websocket events from coming in to the pane - $scope.gotPreviouslyRanEvents = $q.defer(); - currentContext += 1; - - let context = currentContext; - - $( ".JobResultsStdOut-aLineOfStdOut.not_skeleton" ).remove(); - $scope.hasSkeleton.promise.then(() => { - if (val.count > parseInt(val.maxEvents)) { - $(".header_task").hide(); - $(".header_play").hide(); - $scope.standardOutTooltip = '
' + - i18n._('The output is too large to display. Please download.') + - '
' + - '
' + - '' + - '' + - '' + - '' + - '
' + - '
'; - - if ($scope.job_status === "successful" || - $scope.job_status === "failed" || - $scope.job_status === "error" || - $scope.job_status === "canceled") { - $scope.tooManyEvents = true; - $scope.tooManyPastEvents = false; - } else { - $scope.tooManyPastEvents = true; - $scope.tooManyEvents = false; - $scope.gotPreviouslyRanEvents.resolve(""); - } - } else { - $(".header_task").show(); - $(".header_play").show(); - $scope.tooManyEvents = false; - $scope.tooManyPastEvents = false; - processPage(val, context); - } - }); - } - })); - - var buffer = []; - - var processBuffer = function() { - var follow = function() { - // if follow is engaged, - // scroll down to the followAnchor - if ($scope.followEngaged) { - if (!$scope.followScroll) { - $scope.followScroll = function() { - $log.error("follow scroll undefined, standard out directive not loaded yet?"); - }; - } - $scope.followScroll(); - } - }; - - for (let i = 0; i < 4; i++) { - processEvent(buffer[i]); - buffer.splice(i, 1); - } - - follow(); - }; - - var bufferInterval; - - // Processing of job_events messages from the websocket - toDestroy.push($scope.$on(`ws-job_events-${$scope.job.id}`, function(e, data) { - if (!bufferInterval) { - bufferInterval = setInterval(function(){ - processBuffer(); - }, 500); - } - - // use the lowest counter coming over the socket to retrigger pull data - // to only be for stuff lower than that id - // - // only do this for entering the jobs page mid-run (thus the - // data.counter is 1 conditional - if (data.counter === 1) { - $scope.firstCounterFromSocket = -2; - } - - if ($scope.firstCounterFromSocket !== -2 && - $scope.firstCounterFromSocket === -1 || - data.counter < $scope.firstCounterFromSocket) { - $scope.firstCounterFromSocket = data.counter; - } - - $q.all([$scope.gotPreviouslyRanEvents.promise, - $scope.hasSkeleton.promise]).then(() => { - // put the line in the - // standard out pane (and increment play and task - // count if applicable) - if (data.event_name === "playbook_on_play_start") { - $scope.playCount++; - } else if (data.event_name === "playbook_on_task_start") { - $scope.taskCount++; - } - buffer.push(data); - }); - })); - - // get previously set up socket messages from resolve - if (statusSocket && statusSocket[0] && statusSocket[0].job_status) { - $scope.job_status = statusSocket[0].job_status; - } - if ($scope.job_status === "running" && !$scope.job.elapsed) { - runTimeElapsedTimer = workflowResultsService.createOneSecondTimer(moment(), updateJobElapsedTimer); - } - - // Processing of job-status messages from the websocket - toDestroy.push($scope.$on(`ws-jobs`, function(e, data) { - if (parseInt(data.unified_job_id, 10) === - parseInt($scope.job.id,10)) { - // controller is defined, so set the job_status - $scope.job_status = data.status; - if(_.has(data, 'instance_group_name')){ - $scope.job.instance_group = true; - $scope.job.summary_fields.instance_group = { - "name": data.instance_group_name - }; - } - if (data.status === "running") { - if (!runTimeElapsedTimer) { - runTimeElapsedTimer = workflowResultsService.createOneSecondTimer(moment(), updateJobElapsedTimer); - } - } else if (data.status === "successful" || - data.status === "failed" || - data.status === "error" || - data.status === "canceled") { - workflowResultsService.destroyTimer(runTimeElapsedTimer); - - // When the fob is finished retrieve the job data to - // correct anything that was out of sync from the job run - jobResultsService.getJobData($scope.job.id).then(function(data){ - $scope.job = data; - $scope.jobFinished = true; - }); - } - } else if (parseInt(data.project_id, 10) === - parseInt($scope.job.project,10)) { - // this is a project status update message, so set the - // project status in the left pane - $scope.project_status = data.status; - $scope.project_update_link = `/#/scm_update/${data - .unified_job_id}`; - } else { - // controller was previously defined, but is not yet defined - // for this job. cache the socket status on root scope - $rootScope['lastSocketStatus' + data.unified_job_id] = data.status; - } - })); - - if (statusSocket && statusSocket[1]) { - statusSocket[1](); - } - - $scope.$on('$destroy', function(){ - if (statusSocket && statusSocket[1]) { - statusSocket[1](); - } - $( ".JobResultsStdOut-aLineOfStdOut" ).remove(); - cancelRequests = true; - eventQueue.initialize(); - Object.keys($scope.events) - .forEach(v => { - $scope.events[v].$destroy(); - $scope.events[v] = null; - }); - $scope.events = {}; - workflowResultsService.destroyTimer(runTimeElapsedTimer); - if (bufferInterval) { - clearInterval(bufferInterval); - } - toDestroy.forEach(closureFunc => closureFunc()); - }); -}]; diff --git a/awx/ui/client/src/job-results/job-results.partial.html b/awx/ui/client/src/job-results/job-results.partial.html deleted file mode 100644 index 9d8e1a119b..0000000000 --- a/awx/ui/client/src/job-results/job-results.partial.html +++ /dev/null @@ -1,566 +0,0 @@ -
-
-
- - -
-
- - -
-
- DETAILS -
- - -
- - - - - - - - - -
-
- - -
- - -
- -
- - {{ status_label | translate }} -
-
- - -
- -
- {{ job.job_explanation }} -
-
- {{task_detail | limitTo:explanationLimit}} - - ... - Show More - - Show Less -
-
- - -
- -
- {{ job.started | longDate }} -
-
- - -
- -
- {{ (job.finished | - longDate) || "Not Finished" }} -
-
- - -
- -
-
-
- - - - - - -
- -
- {{ type_label }} -
-
- - - - - - - - - - - - - - -
- - - -
- - -
- -
- {{ job.playbook }} -
-
- - -
- - -
- - -
- -
- - - {{ extraCredential.name }} - - {{$last ? '' : ', '}} - -
-
- - - - - - - - - - - -
- -
- {{ job.forks }} -
-
- - -
- -
- {{ job.limit }} -
-
- - -
- -
- {{ verbosity_label }} -
-
- - -
- -
- {{ job.summary_fields.instance_group.name }} - - Isolated - -
-
- - -
- -
- {{ job.job_tags }} -
-
- - -
- -
- {{ job.skip_tags }} -
-
- - -
- - -
- - -
- -
-
-
-
- {{ label }} -
-
-
-
-
- -
- -
-
- - -
-
- - -
-
- - - {{ job.name }} -
-
- -
- -
- Plays -
- - {{ playCount || 0}} - - - -
- Tasks -
- - {{ taskCount || 0}} - - - -
- Hosts -
- - {{ hostCount || 0}} - - - - - - - -
- Elapsed -
- - {{ job.elapsed * 1000 | duration: "hh:mm:ss" }} - -
- - -
- - - - - - - - - -
-
-
- - - - -
- -
-
-
diff --git a/awx/ui/client/src/job-results/job-results.route.js b/awx/ui/client/src/job-results/job-results.route.js deleted file mode 100644 index 60c06de7cd..0000000000 --- a/awx/ui/client/src/job-results/job-results.route.js +++ /dev/null @@ -1,187 +0,0 @@ -/************************************************* - * Copyright (c) 2016 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -import {templateUrl} from '../shared/template-url/template-url.factory'; - -const defaultParams = { - page_size: "200", - order_by: 'start_line', - not__event__in: 'playbook_on_start,playbook_on_play_start,playbook_on_task_start,playbook_on_stats' -}; - -export default { - name: 'jobResult', - url: '/jobs/{id: int}', - searchPrefix: 'job_event', - ncyBreadcrumb: { - parent: 'jobs', - label: '{{ job.id }} - {{ job.name }}' - }, - data: { - socket: { - "groups":{ - "jobs": ["status_changed", "summary"], - "job_events": [] - } - } - }, - params: { - job_event_search: { - value: defaultParams, - dynamic: true, - squash: '' - } - }, - resolve: { - statusSocket: ['$rootScope', '$stateParams', function($rootScope, $stateParams) { - var preScope = {}; - var eventOn = $rootScope.$on(`ws-jobs`, function(e, data) { - if (parseInt(data.unified_job_id, 10) === - parseInt($stateParams.id,10)) { - preScope.job_status = data.status; - } - }); - return [preScope, eventOn]; - }], - // the GET for the particular job - jobData: ['jobResultsService', '$stateParams', function(jobResultsService, $stateParams) { - return jobResultsService.getJobData($stateParams.id); - }], - Dataset: ['QuerySet', '$stateParams', 'jobData', - function(qs, $stateParams, jobData) { - let path = jobData.related.job_events; - return qs.search(path, $stateParams[`job_event_search`]); - } - ], - // used to signify if job is completed or still running - jobFinished: ['jobData', function(jobData) { - if (jobData.finished) { - return true; - } else { - return false; - } - }], - // after the GET for the job, this helps us keep the status bar from - // flashing as rest data comes in. If the job is finished and - // there's a playbook_on_stats event, go ahead and resolve the count - // so you don't get that flashing! - count: ['jobData', 'jobResultsService', 'Rest', '$q', '$stateParams', '$state', function(jobData, jobResultsService, Rest, $q, $stateParams, $state) { - var defer = $q.defer(); - if (jobData.finished) { - // if the job is finished, grab the playbook_on_stats - // role to get the final count - Rest.setUrl(jobData.related.job_events + - "?event=playbook_on_stats"); - Rest.get() - .then(({data}) => { - if(!data.results[0]){ - defer.resolve({val: { - ok: 0, - skipped: 0, - unreachable: 0, - failures: 0, - changed: 0 - }, countFinished: false}); - } - else { - defer.resolve({ - val: jobResultsService - .getCountsFromStatsEvent(data - .results[0].event_data), - countFinished: true}); - } - }) - .catch(() => { - defer.resolve({val: { - ok: 0, - skipped: 0, - unreachable: 0, - failures: 0, - changed: 0 - }, countFinished: false}); - }); - } else { - // make sure to not include any extra - // search params for a running job (because we can't filter - // incoming job events) - if (!_.isEqual($stateParams.job_event_search, defaultParams)) { - let params = _.cloneDeep($stateParams); - params.job_event_search = defaultParams; - $state.go('.', params, { reload: true }); - } - - // job isn't finished so just send an empty count and read - // from events - defer.resolve({val: { - ok: 0, - skipped: 0, - unreachable: 0, - failures: 0, - changed: 0 - }, countFinished: false}); - } - return defer.promise; - }], - // GET for the particular jobs labels to be displayed in the - // left-hand pane - jobLabels: ['Rest', 'GetBasePath', '$stateParams', '$q', function(Rest, GetBasePath, $stateParams, $q) { - var getNext = function(data, arr, resolve) { - Rest.setUrl(data.next); - Rest.get() - .then(({data}) => { - if (data.next) { - getNext(data, arr.concat(data.results), resolve); - } else { - resolve.resolve(arr.concat(data.results) - .map(val => val.name)); - } - }); - }; - - var seeMoreResolve = $q.defer(); - - Rest.setUrl(GetBasePath('jobs') + $stateParams.id + '/labels/'); - Rest.get() - .then(({data}) => { - if (data.next) { - getNext(data, data.results, seeMoreResolve); - } else { - seeMoreResolve.resolve(data.results - .map(val => val.name)); - } - }); - - return seeMoreResolve.promise; - }], - // OPTIONS request for the job. Used to make things like the - // verbosity data in the left-hand pane prettier than just an - // integer - jobDataOptions: ['Rest', 'GetBasePath', '$stateParams', '$q', function(Rest, GetBasePath, $stateParams, $q) { - Rest.setUrl(GetBasePath('jobs') + $stateParams.id); - var val = $q.defer(); - Rest.options() - .then(function(data) { - val.resolve(data.data); - }, function(data) { - val.reject(data); - }); - return val.promise; - }], - jobExtraCredentials: ['Rest', 'GetBasePath', '$stateParams', '$q', function(Rest, GetBasePath, $stateParams, $q) { - Rest.setUrl(GetBasePath('jobs') + $stateParams.id + '/extra_credentials'); - var val = $q.defer(); - Rest.get() - .then(function(res) { - val.resolve(res.data.results); - }, function(res) { - val.reject(res); - }); - return val.promise; - }] - }, - templateUrl: templateUrl('job-results/job-results'), - controller: 'jobResultsController' -}; diff --git a/awx/ui/client/src/job-results/job-results.service.js b/awx/ui/client/src/job-results/job-results.service.js deleted file mode 100644 index 6b77575fff..0000000000 --- a/awx/ui/client/src/job-results/job-results.service.js +++ /dev/null @@ -1,269 +0,0 @@ -/************************************************* -* Copyright (c) 2016 Ansible, Inc. -* -* All Rights Reserved -*************************************************/ - - -export default ['$q', 'Prompt', '$filter', 'Wait', 'Rest', '$state', 'ProcessErrors', 'GetBasePath', 'Alert', '$rootScope', 'i18n', -function ($q, Prompt, $filter, Wait, Rest, $state, ProcessErrors, GetBasePath, Alert, $rootScope, i18n) { - var val = { - // the playbook_on_stats event returns the count data in a weird format. - // format to what we need! - getCountsFromStatsEvent: function(event_data) { - var hosts = {}, - hostsArr; - - // iterate over the event_data and populate an object with hosts - // and their status data - Object.keys(event_data).forEach(key => { - // failed passes boolean not integer - if (key === "changed" || - key === "dark" || - key === "failures" || - key === "ok" || - key === "skipped") { - // array of hosts from each type ("changed", "dark", etc.) - hostsArr = Object.keys(event_data[key]); - hostsArr.forEach(host => { - if (!hosts[host]) { - // host has not been added to hosts object - // add now - hosts[host] = {}; - } - - if (!hosts[host][key]) { - // host doesn't have key - hosts[host][key] = 0; - } - hosts[host][key] += event_data[key][host]; - }); - } - }); - - var total_hosts_by_state = { - ok: 0, - skipped: 0, - unreachable: 0, - failures: 0, - changed: 0 - }; - - // each host belongs in at most *one* of these states depending on - // the state of its tasks - _.each(hosts, function(host) { - if (host.dark > 0){ - total_hosts_by_state.unreachable++; - } else if (host.failures > 0){ - total_hosts_by_state.failures++; - } else if (host.changed > 0){ - total_hosts_by_state.changed++; - } else if (host.ok > 0){ - total_hosts_by_state.ok++; - } else if (host.skipped > 0){ - total_hosts_by_state.skipped++; - } - }); - - return total_hosts_by_state; - }, - // rest call to grab previously complete job_events - getEvents: function(url) { - var val = $q.defer(); - - Rest.setUrl(url); - Rest.get() - .then(({data}) => { - val.resolve({results: data.results, - next: data.next}); - }) - .catch(({obj, status}) => { - ProcessErrors(null, obj, status, null, { - hdr: 'Error!', - msg: `Could not get job events. - Returned status: ${status}` - }); - val.reject(obj); - }); - - return val.promise; - }, - deleteJob: function(job) { - Prompt({ - hdr: i18n._("Delete Job"), - resourceName: `#${job.id} ` + $filter('sanitize')(job.name), - body: `
- ${i18n._("Are you sure you want to delete this job?")} -
`, - action: function() { - Wait('start'); - Rest.setUrl(job.url); - Rest.destroy() - .then(() => { - Wait('stop'); - $('#prompt-modal').modal('hide'); - $state.go('jobs'); - }) - .catch(({obj, status}) => { - Wait('stop'); - $('#prompt-modal').modal('hide'); - ProcessErrors(null, obj, status, null, { - hdr: 'Error!', - msg: `Could not delete job. - Returned status: ${status}` - }); - }); - }, - actionText: i18n._('DELETE') - }); - }, - cancelJob: function(job) { - var doCancel = function() { - Rest.setUrl(job.url + 'cancel'); - Rest.post({}) - .then(() => { - Wait('stop'); - $('#prompt-modal').modal('hide'); - }) - .catch(({obj, status}) => { - Wait('stop'); - $('#prompt-modal').modal('hide'); - ProcessErrors(null, obj, status, null, { - hdr: 'Error!', - msg: `Could not cancel job. - Returned status: ${status}` - }); - }); - }; - - Prompt({ - hdr: i18n._('Cancel Job'), - resourceName: `#${job.id} ` + $filter('sanitize')(job.name), - body: `
- ${i18n._("Are you sure you want to cancel this job?")} -
`, - action: function() { - Wait('start'); - Rest.setUrl(job.url + 'cancel'); - Rest.get() - .then(({data}) => { - if (data.can_cancel === true) { - doCancel(); - } else { - $('#prompt-modal').modal('hide'); - ProcessErrors(null, data, null, null, { - hdr: 'Error!', - msg: `Job has completed, - unabled to be canceled.` - }); - } - }); - }, - actionText: i18n._('PROCEED') - }); - }, - getJobData: function(id){ - var val = $q.defer(); - - Rest.setUrl(GetBasePath('jobs') + id ); - Rest.get() - .then(function(data) { - val.resolve(data.data); - }, function(data) { - val.reject(data); - - if (data.status === 404) { - Alert('Job Not Found', 'Cannot find job.', 'alert-info'); - } else if (data.status === 403) { - Alert('Insufficient Permissions', 'You do not have permission to view this job.', 'alert-info'); - } - - $state.go('jobs'); - }); - - return val.promise; - }, - // Generate a helper class for job_event statuses - // the stack for which status to display is - // unreachable > failed > changed > ok - // uses the API's runner events and convenience properties .failed .changed to determine status. - // see: job_event_callback.py for more filters to support - processEventStatus: function(event){ - if (event.event === 'runner_on_unreachable'){ - return { - class: 'HostEvent-status--unreachable', - status: 'unreachable' - }; - } - // equiv to 'runner_on_error' && 'runner on failed' - if (event.failed){ - return { - class: 'HostEvent-status--failed', - status: 'failed' - }; - } - // catch the changed case before ok, because both can be true - if (event.changed){ - return { - class: 'HostEvent-status--changed', - status: 'changed' - }; - } - if (event.event === 'runner_on_ok' || event.event === 'runner_on_async_ok'){ - return { - class: 'HostEvent-status--ok', - status: 'ok' - }; - } - if (event.event === 'runner_on_skipped'){ - return { - class: 'HostEvent-status--skipped', - status: 'skipped' - }; - } - }, - // GET events related to a job run - // e.g. - // ?event=playbook_on_stats - // ?parent=206&event__startswith=runner&page_size=200&order=host_name,counter - getRelatedJobEvents: function(id, params){ - var url = GetBasePath('jobs'); - url = url + id + '/job_events/?' + this.stringifyParams(params); - Rest.setUrl(url); - return Rest.get() - .then((response) => { - return response; - }) - .catch(({data, status}) => { - ProcessErrors($rootScope, data, status, null, { hdr: 'Error!', - msg: 'Call to ' + url + '. GET returned: ' + status }); - }); - }, - stringifyParams: function(params){ - return _.reduce(params, (result, value, key) => { - return result + key + '=' + value + '&'; - }, ''); - }, - // the the API passes through Ansible's event_data response - // we need to massage away the verbose & redundant stdout/stderr properties - processJson: function(data){ - // configure fields to ignore - var ignored = [ - 'type', - 'event_data', - 'related', - 'summary_fields', - 'url', - 'ansible_facts', - ]; - // remove ignored properties - var result = _.chain(data).cloneDeep().forEach(function(value, key, collection){ - if (ignored.indexOf(key) > -1){ - delete collection[key]; - } - }).value(); - return result; - } - }; - return val; -}]; diff --git a/awx/ui/client/src/job-results/main.js b/awx/ui/client/src/job-results/main.js deleted file mode 100644 index f48ac081d8..0000000000 --- a/awx/ui/client/src/job-results/main.js +++ /dev/null @@ -1,26 +0,0 @@ -/************************************************* - * Copyright (c) 2016 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -import hostStatusBar from './host-status-bar/main'; -import jobResultsStdOut from './job-results-stdout/main'; - -import route from './job-results.route.js'; - -import jobResultsController from './job-results.controller'; - -import jobResultsService from './job-results.service'; -import eventQueueService from './event-queue.service'; -import parseStdoutService from './parse-stdout.service'; - -export default - angular.module('jobResults', [hostStatusBar.name, jobResultsStdOut.name, 'angularMoment']) - .run(['$stateExtender', function($stateExtender) { - $stateExtender.addState(route); - }]) - .controller('jobResultsController', jobResultsController) - .service('jobResultsService', jobResultsService) - .service('eventQueue', eventQueueService) - .service('parseStdoutService', parseStdoutService); diff --git a/awx/ui/client/src/job-results/parse-stdout.service.js b/awx/ui/client/src/job-results/parse-stdout.service.js deleted file mode 100644 index 66c969b9c4..0000000000 --- a/awx/ui/client/src/job-results/parse-stdout.service.js +++ /dev/null @@ -1,293 +0,0 @@ -/************************************************* -* Copyright (c) 2016 Ansible, Inc. -* -* All Rights Reserved -*************************************************/ - -export default ['$log', 'moment', 'i18n', function($log, moment, i18n){ - var val = { - // parses stdout string from api and formats various codes to the - // correct dom structure - prettify: function(line, unstyled){ - line = line - .replace(/&/g, "&") - .replace(//g, ">") - .replace(/"/g, """) - .replace(/'/g, "'"); - - // TODO: remove once Chris's fixes to the [K lines comes in - if (line.indexOf("[K") > -1) { - $log.error(line); - } - - if(!unstyled){ - // add span tags with color styling - line = line.replace(/u001b/g, ''); - - // ansi classes - /* jshint ignore:start */ - line = line.replace(/(|)\[1;im/g, ''); - line = line.replace(/(|)\[0;30m/g, ''); - line = line.replace(/(|)\[1;30m/g, ''); - line = line.replace(/(|)\[[0,1];31m/g, ''); - line = line.replace(/(|)\[0;32m(=|)/g, ''); - line = line.replace(/(|)\[0;32m1/g, ''); - line = line.replace(/(|)\[0;33m/g, ''); - line = line.replace(/(|)\[0;34m/g, ''); - line = line.replace(/(|)\[[0,1];35m/g, ''); - line = line.replace(/(|)\[0;36m/g, ''); - line = line.replace(/()\s/g, '$1'); - - //end span - line = line.replace(/(|)\[0m/g, ''); - /* jshint ignore:end */ - } else { - // For the host event modal in the standard out tab, - // the styling isn't necessary - line = line.replace(/u001b/g, ''); - - // ansi classes - /* jshint ignore:start */ - line = line.replace(/(|)\[[0,1];3[0-9]m(1|=|)/g, ''); - line = line.replace(/()\s/g, '$1'); - - //end span - line = line.replace(/(|)\[0m/g, ''); - /* jshint ignore:end */ - } - - return line; - }, - // adds anchor tags and tooltips to host status lines - getAnchorTags: function(event){ - if(event.event_name.indexOf("runner_") === -1){ - return `"`; - } - else{ - return ` JobResultsStdOut-stdoutColumn--clickable" ui-sref="jobResult.host-event.json({eventId: ${event.id}, taskUuid: '${event.event_data.task_uuid}' })" aw-tool-tip="${i18n._("Event ID")}: ${event.id}
${i18n._("Status")}: ${event.event_display}
${i18n._("Click for details")}" data-placement="top"`; - } - - }, - // this adds classes based on event data to the - // .JobResultsStdOut-aLineOfStdOut element - getLineClasses: function(event, line, lineNum) { - var string = ""; - - if (lineNum === event.end_line) { - // used to tell you where to put stuff in the pane - string += ` next_is_${event.end_line + 1}`; - } - - if (event.event_name === "playbook_on_play_start") { - // play header classes - string += " header_play"; - string += " header_play_" + event.event_data.play_uuid; - - // give the actual header class to the line with the - // actual header info (think cowsay) - if (line.indexOf("PLAY") > -1) { - string += " actual_header"; - } - } else if (event.event_name === "playbook_on_task_start") { - // task header classes - string += " header_task"; - string += " header_task_" + event.event_data.task_uuid; - - // give the actual header class to the line with the - // actual header info (think cowsay) - if (line.indexOf("TASK") > -1 || - line.indexOf("RUNNING HANDLER") > -1) { - string += " actual_header"; - } - - // task headers also get classed by their parent play - // if applicable - if (event.event_data.play_uuid) { - string += " play_" + event.event_data.play_uuid; - } - } else if (event.event_name !== "playbook_on_stats"){ - string += " not_skeleton"; - // host status or debug line - - // these get classed by their parent play if applicable - if (event.event_data.play_uuid) { - string += " play_" + event.event_data.play_uuid; - } - // as well as their parent task if applicable - if (event.event_data.task_uuid) { - string += " task_" + event.event_data.task_uuid; - } - } - - // TODO: adding this line_num_XX class is hacky because the - // line number is availabe in children of this dom element - string += " line_num_" + lineNum; - - return string; - }, - getStartTimeBadge: function(event, line){ - // This will return a div with the badge class - // for the start time to show at the right hand - // side of each stdout header line. - // returns an empty string if not a header line - var emptySpan = "", time; - if ((event.event_name === "playbook_on_play_start" || - event.event_name === "playbook_on_task_start") && - line !== "") { - time = moment(event.created).format('HH:mm:ss'); - return `
${time}
`; - } - else if(event.event_name === "playbook_on_stats" && line.indexOf("PLAY") > -1){ - time = moment(event.created).format('HH:mm:ss'); - return `
${time}
`; - } - else { - return emptySpan; - } - - }, - // used to add expand/collapse icon next to line numbers of headers - getCollapseIcon: function(event, line) { - var clickClass, - expanderizerSpecifier; - - var emptySpan = ` -`; - - if ((event.event_name === "playbook_on_play_start" || - event.event_name === "playbook_on_task_start") && - line !== "") { - if (event.event_name === "playbook_on_play_start" && - line.indexOf("PLAY") > -1) { - // play header specific attrs - expanderizerSpecifier = "play"; - clickClass = "play_" + - event.event_data.play_uuid; - } else if (line.indexOf("TASK") > -1 || - line.indexOf("RUNNING HANDLER") > -1) { - // task header specific attrs - expanderizerSpecifier = "task"; - clickClass = "task_" + - event.event_data.task_uuid; - } else { - // header lines that don't have PLAY, TASK, - // or RUNNING HANDLER in them don't get - // expand icon. - // This provides cowsay support. - return emptySpan; - } - - - var expandDom = ` - - - -`; - return expandDom; - } else { - // non-header lines don't get an expander - return emptySpan; - } - }, - distributeColors: function(lines) { - var colorCode; - return lines.map(line => { - - if (colorCode) { - line = colorCode + line; - } - - if (line.indexOf("[0m") === -1) { - if (line.indexOf("[1;31m") > -1) { - colorCode = "[1;31m"; - } else if (line.indexOf("[1;30m") > -1) { - colorCode = "[1;30m"; - } else if (line.indexOf("[0;31m") > -1) { - colorCode = "[0;31m"; - } else if (line.indexOf("[0;32m=") > -1) { - colorCode = "[0;32m="; - } else if (line.indexOf("[0;32m1") > -1) { - colorCode = "[0;32m1"; - } else if (line.indexOf("[0;32m") > -1) { - colorCode = "[0;32m"; - } else if (line.indexOf("[0;33m") > -1) { - colorCode = "[0;33m"; - } else if (line.indexOf("[0;34m") > -1) { - colorCode = "[0;34m"; - } else if (line.indexOf("[0;35m") > -1) { - colorCode = "[0;35m"; - } else if (line.indexOf("[1;35m") > -1) { - colorCode = "[1;35m"; - } else if (line.indexOf("[0;36m") > -1) { - colorCode = "[0;36m"; - } - } else { - colorCode = null; - } - - return line; - }); - }, - getLineArr: function(event) { - let lineNums = _.range(event.start_line + 1, - event.end_line + 1); - - // hack around no-carriage return issues - if (!lineNums.length) { - lineNums = [event.start_line + 1]; - } - - let lines = event.stdout - .replace("\t", " ") - .split("\r\n"); - - if (lineNums.length > lines.length) { - lineNums = lineNums.slice(0, lines.length); - } - - lines = this.distributeColors(lines); - - // hack around no-carriage return issues - if (lineNums.length === lines.length) { - return _.zip(lineNums, lines); - } - - return _.zip(lineNums, lines).slice(0, -1); - }, - actualEndLine: function(event) { - return event.start_line + this.getLineArr(event).length; - }, - // public function that provides the parsed stdout line, given a - // job_event - parseStdout: function(event){ - // this utilizes the start/end lines and stdout blob - // to create an array in the format: - // [ - // [lineNum, lineText], - // [lineNum, lineText], - // ] - var lineArr = this.getLineArr(event); - - // this takes each `[lineNum: lineText]` element and calls the - // relevant helper functions in this service to build the - // parsed line of standard out - lineArr = lineArr - .map(lineArr => { - return ` -
-
${this.getCollapseIcon(event, lineArr[1])}${lineArr[0]}
-
-
-
-
-
-
-
- RESULTS -
-
-
- -
- - -
-
-
- -
-
Name
-
{{ job.module_name }}
-
- -
-
STATUS
-
- - {{ job.status }} -
-
- -
-
STARTED
-
- {{ job.started | longDate }} -
-
- -
-
FINISHED
-
- {{ job.finished | longDate }} -
-
- -
-
ELAPSED
-
- {{ job.elapsed }} seconds -
-
- -
-
Module Args
-
{{ job.module_args }}
-
- -
-
Inventory
- -
- -
-
Credential
- -
- -
-
Launched By
- -
- - -
-
Forks
-
{{ forks }}
-
- -
-
Limit
-
{{ limit }}
-
- - -
-
Verbosity
-
{{ verbosity }}
-
- -
-
- {{ 'Extra Variables' | translate }} - - -
-
- -
-
- -
-
-
-
-
-
-
STANDARD OUT
-
- - - - -
-
- -
-
-
-
-
diff --git a/awx/ui/client/src/standard-out/adhoc/standard-out-adhoc.route.js b/awx/ui/client/src/standard-out/adhoc/standard-out-adhoc.route.js deleted file mode 100644 index 899a98e9f7..0000000000 --- a/awx/ui/client/src/standard-out/adhoc/standard-out-adhoc.route.js +++ /dev/null @@ -1,36 +0,0 @@ -/************************************************* - * Copyright (c) 2016 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -import { templateUrl } from '../../shared/template-url/template-url.factory'; - -export default { - name: 'adHocJobStdout', - route: '/ad_hoc_commands/:id', - templateUrl: templateUrl('standard-out/adhoc/standard-out-adhoc'), - controller: 'JobStdoutController', - ncyBreadcrumb: { - parent: "jobs", - label: "{{ job.module_name }}" - }, - data: { - jobType: 'ad_hoc_commands', - socket: { - "groups": { - "jobs": ["status_changed", "summary"], - "ad_hoc_command_events": [] - } - } - }, - resolve: { - jobData: ['Rest', 'GetBasePath', '$stateParams', function(Rest, GetBasePath, $stateParams) { - Rest.setUrl(GetBasePath('base') + 'ad_hoc_commands/' + $stateParams.id + '/'); - return Rest.get() - .then(({data}) => { - return data; - }); - }] - } -}; diff --git a/awx/ui/client/src/standard-out/inventory-sync/standard-out-inventory-sync.partial.html b/awx/ui/client/src/standard-out/inventory-sync/standard-out-inventory-sync.partial.html deleted file mode 100644 index 48f2d65b7e..0000000000 --- a/awx/ui/client/src/standard-out/inventory-sync/standard-out-inventory-sync.partial.html +++ /dev/null @@ -1,152 +0,0 @@ -
-
-
-
-
-
-
- RESULTS -
-
- - - -
-
-
- - - -
-
STATUS
-
- - {{ job.status }} -
-
- - -
-
EXPLANATION
-
- {{task_detail | limitTo:explanationLimit}} - - ... - Show More - - Show Less -
-
- -
-
LICENSE ERROR
-
- {{ job.license_error }} -
-
- -
-
STARTED
-
- {{ job.started | longDate }} -
-
- -
-
FINISHED
-
- {{ job.finished | longDate }} -
-
- -
-
ELAPSED
-
- {{ job.elapsed }} seconds -
-
- -
-
LAUNCH TYPE
-
- {{ job.launch_type }} -
-
- -
-
CREDENTIAL
- -
- -
-
SOURCE
-
- {{ source }} -
-
- -
-
REGIONS
-
- {{ source_regions }} -
-
- -
-
OVERWRITE
-
- {{ job.overwrite }} -
-
- -
-
OVERWRITE VARS
-
- {{ job.overwrite_vars }} -
-
- -
-
-
-
-
-
-
STANDARD OUT
-
- - - - -
-
- -
-
-
-
-
diff --git a/awx/ui/client/src/standard-out/inventory-sync/standard-out-inventory-sync.route.js b/awx/ui/client/src/standard-out/inventory-sync/standard-out-inventory-sync.route.js deleted file mode 100644 index bdd1a9a2b1..0000000000 --- a/awx/ui/client/src/standard-out/inventory-sync/standard-out-inventory-sync.route.js +++ /dev/null @@ -1,38 +0,0 @@ -/************************************************* - * Copyright (c) 2016 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -import {templateUrl} from '../../shared/template-url/template-url.factory'; - -// TODO: figure out what this route should be - should it be inventory_sync? - -export default { - name: 'inventorySyncStdout', - route: '/inventory_sync/:id', - templateUrl: templateUrl('standard-out/inventory-sync/standard-out-inventory-sync'), - controller: 'JobStdoutController', - ncyBreadcrumb: { - parent: "jobs", - label: "{{ inventory_source_name }}" - }, - data: { - socket: { - "groups":{ - "jobs": ["status_changed", "summary"], - "inventory_update_events": [], - } - }, - jobType: 'inventory_updates' - }, - resolve: { - jobData: ['Rest', 'GetBasePath', '$stateParams', function(Rest, GetBasePath, $stateParams) { - Rest.setUrl(GetBasePath('base') + 'inventory_updates/' + $stateParams.id + '/'); - return Rest.get() - .then(({data}) => { - return data; - }); - }] - } -}; diff --git a/awx/ui/client/src/standard-out/log/main.js b/awx/ui/client/src/standard-out/log/main.js deleted file mode 100644 index bb97a737be..0000000000 --- a/awx/ui/client/src/standard-out/log/main.js +++ /dev/null @@ -1,10 +0,0 @@ -/************************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -import standardOutLog from './standard-out-log.directive'; -export default - angular.module('standardOutLogDirective', []) - .directive('standardOutLog', standardOutLog); diff --git a/awx/ui/client/src/standard-out/log/standard-out-log.controller.js b/awx/ui/client/src/standard-out/log/standard-out-log.controller.js deleted file mode 100644 index 728f8faedf..0000000000 --- a/awx/ui/client/src/standard-out/log/standard-out-log.controller.js +++ /dev/null @@ -1,202 +0,0 @@ -/************************************************* - * Copyright (c) 2016 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -export default ['$log', '$rootScope', '$scope', '$state', '$stateParams', 'ProcessErrors', 'Rest', 'Wait', - function ($log, $rootScope, $scope, $state, $stateParams, ProcessErrors, Rest, Wait) { - - var api_complete = false, - current_range, - loaded_sections = [], - event_queue = 0, - auto_scroll_down=true, // programmatic scroll to bottom - live_event_processing = true, - page_size = 500, - job_id = $stateParams.id; - - $scope.should_apply_live_events = true; - - // Open up a socket for events depending on the type of job - function openSockets() { - if ($state.current.name === 'jobResult') { - $log.debug("socket watching on job_events-" + job_id); - $scope.$on(`ws-job_events-${job_id}`, function() { - $log.debug("socket fired on job_events-" + job_id); - if (api_complete) { - event_queue++; - } - }); - } - if ($state.current.name === 'adHocJobStdout') { - $log.debug("socket watching on ad_hoc_command_events-" + job_id); - $scope.$on(`ws-ad_hoc_command_events-${job_id}`, function() { - $log.debug("socket fired on ad_hoc_command_events-" + job_id); - if (api_complete) { - event_queue++; - } - }); - } - } - - openSockets(); - - // This is a trigger for loading up the standard out - if ($scope.removeLoadStdout) { - $scope.removeLoadStdout(); - } - $scope.removeLoadStdout = $scope.$on('LoadStdout', function() { - if (loaded_sections.length === 0) { - loadStdout(); - } - else if (live_event_processing) { - getNextSection(); - } - }); - - // This interval checks to see whether or not we've gotten a new - // event via sockets. If so, go out and update the standard out - // log. - $rootScope.jobStdOutInterval = setInterval( function() { - if (event_queue > 0) { - // events happened since the last check - $log.debug('checking for stdout...'); - if (loaded_sections.length === 0) { ////this if statement for refresh - $log.debug('calling LoadStdout'); - loadStdout(); - } - else if (live_event_processing) { - $log.debug('calling getNextSection'); - getNextSection(); - } - event_queue = 0; - } - }, 2000); - - // stdoutEndpoint gets passed through in the directive declaration. - // This watcher fires off loadStdout() when the endpoint becomes - // available. - $scope.$watch('stdoutEndpoint', function(newVal, oldVal) { - if(newVal && newVal !== oldVal) { - // Fire off the server call - loadStdout(); - } - }); - - // stdoutText optionall gets passed through in the directive declaration. - $scope.$watch('stdoutText', function(newVal, oldVal) { - if(newVal && newVal !== oldVal) { - $('#pre-container-content').html(newVal); - } - }); - - function loadStdout() { - if (!$scope.stdoutEndpoint) { - return; - } - - Rest.setUrl($scope.stdoutEndpoint + '?format=json&start_line=0&end_line=' + page_size); - Rest.get() - .then(({data}) => { - Wait('stop'); - if (data.content) { - api_complete = true; - $('#pre-container-content').html(data.content); - current_range = data.range; - if (data.content !== "Waiting for results...") { - loaded_sections.push({ - start: (data.range.start < 0) ? 0 : data.range.start, - end: data.range.end - }); - } - - $('#pre-container').scrollTop($('#pre-container').prop("scrollHeight")); - } - else { - api_complete = true; - } - }) - .catch(({data, status}) => { - ProcessErrors($scope, data, status, null, { hdr: 'Error!', - msg: 'Failed to retrieve stdout for job: ' + job_id + '. GET returned: ' + status }); - }); - } - - function getNextSection() { - if (!$scope.stdoutEndpoint) { - return; - } - - // get the next range of data from the API - var start = loaded_sections[loaded_sections.length - 1].end, url; - url = $scope.stdoutEndpoint + '?format=json&start_line=' + start + '&end_line=' + (start + page_size); - $('#stdoutMoreRowsBottom').fadeIn(); - Rest.setUrl(url); - Rest.get() - .then(({data}) => { - if ($('#pre-container-content').html() === "Waiting for results...") { - $('#pre-container-content').html(data.content); - } else { - $('#pre-container-content').append(data.content); - } - loaded_sections.push({ - start: (data.range.start < 0) ? 0 : data.range.start, - end: data.range.end - }); - if ($scope.should_apply_live_events) { - // if user has not disabled live event view by scrolling upward, then scroll down to the new content - current_range = data.range; - auto_scroll_down = true; // prevent auto load from happening - $('#pre-container').scrollTop($('#pre-container').prop("scrollHeight")); - } - $('#stdoutMoreRowsBottom').fadeOut(400); - }) - .catch(({data, status}) => { - ProcessErrors($scope, data, status, null, { hdr: 'Error!', - msg: 'Failed to retrieve stdout for job: ' + job_id + '. GET returned: ' + status }); - }); - } - - // lrInfiniteScroll handler - // grabs the next stdout section - $scope.stdOutGetNextSection = function(){ - if (current_range.absolute_end > current_range.end){ - var url = $scope.stdoutEndpoint + '?format=json&start_line=' + current_range.end + - '&end_line=' + (current_range.end + page_size); - Rest.setUrl(url); - Rest.get() - .then(({data}) => { - $('#pre-container-content').append(data.content); - current_range = data.range; - }) - .catch(({data, status}) => { - ProcessErrors($scope, data, status, null, { hdr: 'Error!', - msg: 'Failed to retrieve stdout for job: ' + job_id + '. GET returned: ' + status }); - }); - } - }; - - // We watch for job status changes here. If the job completes we want to clear out the - // stdout interval and kill the live_event_processing flag. - $scope.$on(`ws-jobs`, function(e, data) { - if (parseInt(data.unified_job_id, 10) === parseInt(job_id,10)) { - if (data.status === 'failed' || data.status === 'canceled' || - data.status === 'error' || data.status === 'successful') { - if ($rootScope.jobStdOutInterval) { - window.clearInterval($rootScope.jobStdOutInterval); - } - if (live_event_processing) { - if (loaded_sections.length === 0) { - loadStdout(); - } - else { - getNextSection(); - } - } - live_event_processing = false; - } - } - }); - -}]; diff --git a/awx/ui/client/src/standard-out/log/standard-out-log.directive.js b/awx/ui/client/src/standard-out/log/standard-out-log.directive.js deleted file mode 100644 index d7d0656441..0000000000 --- a/awx/ui/client/src/standard-out/log/standard-out-log.directive.js +++ /dev/null @@ -1,47 +0,0 @@ -/************************************************* - * Copyright (c) 2016 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -import standardOutLogController from './standard-out-log.controller'; -export default [ 'templateUrl', - function(templateUrl) { - return { - scope: { - stdoutEndpoint: '=', - stdoutText: '=', - jobId: '=' - }, - templateUrl: templateUrl('standard-out/log/standard-out-log'), - restrict: 'E', - controller: standardOutLogController, - link: function(scope) { - // All of our DOM related stuff will go in here - - var lastScrollTop, - direction; - - function detectDirection() { - var st = $('#pre-container').scrollTop(); - if (st > lastScrollTop) { - direction = "down"; - } else { - direction = "up"; - } - lastScrollTop = st; - return direction; - } - - $('#pre-container').bind('scroll', function() { - if (detectDirection() === "up") { - scope.should_apply_live_events = false; - } - - if ($(this).scrollTop() + $(this).height() === $(this).prop("scrollHeight")) { - scope.should_apply_live_events = true; - } - }); - } - }; -}]; diff --git a/awx/ui/client/src/standard-out/log/standard-out-log.partial.html b/awx/ui/client/src/standard-out/log/standard-out-log.partial.html deleted file mode 100644 index 45d9f70cbb..0000000000 --- a/awx/ui/client/src/standard-out/log/standard-out-log.partial.html +++ /dev/null @@ -1,8 +0,0 @@ -
-
-
-
-
- -
-
diff --git a/awx/ui/client/src/standard-out/main.js b/awx/ui/client/src/standard-out/main.js deleted file mode 100644 index 1e0f451014..0000000000 --- a/awx/ui/client/src/standard-out/main.js +++ /dev/null @@ -1,22 +0,0 @@ -/************************************************* - * Copyright (c) 2016 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -import stdoutAdhocRoute from './adhoc/standard-out-adhoc.route'; -import stdoutManagementJobsRoute from './management-jobs/standard-out-management-jobs.route'; -import stdoutInventorySyncRoute from './inventory-sync/standard-out-inventory-sync.route'; -import stdoutScmUpdateRoute from './scm-update/standard-out-scm-update.route'; -import {JobStdoutController} from './standard-out.controller'; -import StandardOutHelper from './standard-out-factories/main'; -import standardOutLogDirective from './log/main'; - -export default angular.module('standardOut', [StandardOutHelper.name, standardOutLogDirective.name]) - .controller('JobStdoutController', JobStdoutController) - .run(['$stateExtender', function($stateExtender) { - $stateExtender.addState(stdoutAdhocRoute); - $stateExtender.addState(stdoutManagementJobsRoute); - $stateExtender.addState(stdoutInventorySyncRoute); - $stateExtender.addState(stdoutScmUpdateRoute); - }]); diff --git a/awx/ui/client/src/standard-out/management-jobs/standard-out-management-jobs.partial.html b/awx/ui/client/src/standard-out/management-jobs/standard-out-management-jobs.partial.html deleted file mode 100644 index 8c9740a5eb..0000000000 --- a/awx/ui/client/src/standard-out/management-jobs/standard-out-management-jobs.partial.html +++ /dev/null @@ -1,84 +0,0 @@ -
-
-
-
-
-
-
- RESULTS -
-
- - -
-
-
- -
-
NAME
-
{{ job.name }}
-
- -
-
STATUS
-
- - {{ job.status }} -
-
- -
-
STARTED
-
- {{ job.started | longDate }} -
-
- -
-
FINISHED
-
- {{ job.finished | longDate }} -
-
- -
-
ELAPSED
-
- {{ job.elapsed }} seconds -
-
- -
-
LAUNCH TYPE
-
- {{ job.launch_type }} -
-
- -
-
EXTRA VARIABLES
-
- -
- -
- -
-
-
-
-
-
-
STANDARD OUT
-
- -
-
- -
-
-
-
-
diff --git a/awx/ui/client/src/standard-out/management-jobs/standard-out-management-jobs.route.js b/awx/ui/client/src/standard-out/management-jobs/standard-out-management-jobs.route.js deleted file mode 100644 index e3a59e884d..0000000000 --- a/awx/ui/client/src/standard-out/management-jobs/standard-out-management-jobs.route.js +++ /dev/null @@ -1,36 +0,0 @@ -/************************************************* - * Copyright (c) 2016 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -import { templateUrl } from '../../shared/template-url/template-url.factory'; - -export default { - name: 'managementJobStdout', - route: '/management_jobs/:id', - templateUrl: templateUrl('standard-out/management-jobs/standard-out-management-jobs'), - controller: 'JobStdoutController', - ncyBreadcrumb: { - parent: "jobs", - label: "{{ job.name }}" - }, - data: { - jobType: 'system_jobs', - socket: { - "groups": { - "jobs": ["status_changed", "summary"], - "system_job_events": [], - } - } - }, - resolve: { - jobData: ['Rest', 'GetBasePath', '$stateParams', function(Rest, GetBasePath, $stateParams) { - Rest.setUrl(GetBasePath('base') + 'system_jobs/' + $stateParams.id + '/'); - return Rest.get() - .then(({data}) => { - return data; - }); - }] - } -}; diff --git a/awx/ui/client/src/standard-out/scm-update/standard-out-scm-update.partial.html b/awx/ui/client/src/standard-out/scm-update/standard-out-scm-update.partial.html deleted file mode 100644 index f9828d4c02..0000000000 --- a/awx/ui/client/src/standard-out/scm-update/standard-out-scm-update.partial.html +++ /dev/null @@ -1,113 +0,0 @@ -
-
-
-
-
-
-
- RESULTS -
-
- - - -
-
-
- - - -
-
STATUS
-
- - {{ job.status }} -
-
- -
-
STARTED
-
- {{ job.started | longDate }} -
-
- -
-
FINISHED
-
- {{ job.finished | longDate }} -
-
- -
-
ELAPSED
-
- {{ job.elapsed }} seconds -
-
- -
-
LAUNCH TYPE
-
- {{ job.launch_type }} -
-
- -
-
PROJECT
- -
- -
-
CREDENTIAL
- -
- -
-
-
-
-
-
-
STANDARD OUT
-
- - - - -
-
- -
-
-
-
-
diff --git a/awx/ui/client/src/standard-out/scm-update/standard-out-scm-update.route.js b/awx/ui/client/src/standard-out/scm-update/standard-out-scm-update.route.js deleted file mode 100644 index 818509ccc7..0000000000 --- a/awx/ui/client/src/standard-out/scm-update/standard-out-scm-update.route.js +++ /dev/null @@ -1,38 +0,0 @@ -/************************************************* - * Copyright (c) 2016 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -import { templateUrl } from '../../shared/template-url/template-url.factory'; - -// TODO: figure out what this route should be - should it be scm_update? - -export default { - name: 'scmUpdateStdout', - route: '/scm_update/:id', - templateUrl: templateUrl('standard-out/scm-update/standard-out-scm-update'), - controller: 'JobStdoutController', - ncyBreadcrumb: { - parent: "jobs", - label: "{{ project_name }}" - }, - data: { - jobType: 'project_updates', - socket: { - "groups": { - "jobs": ["status_changed", "summary"], - "project_update_events": [], - } - }, - }, - resolve: { - jobData: ['Rest', 'GetBasePath', '$stateParams', function(Rest, GetBasePath, $stateParams) { - Rest.setUrl(GetBasePath('base') + 'project_updates/' + $stateParams.id + '/'); - return Rest.get() - .then(({data}) => { - return data; - }); - }] - } -}; diff --git a/awx/ui/client/src/standard-out/standard-out-factories/delete-job.factory.js b/awx/ui/client/src/standard-out/standard-out-factories/delete-job.factory.js deleted file mode 100644 index 6e28362f3a..0000000000 --- a/awx/ui/client/src/standard-out/standard-out-factories/delete-job.factory.js +++ /dev/null @@ -1,145 +0,0 @@ -export default -function DeleteJob($state, Find, Rest, Wait, ProcessErrors, Prompt, Alert, - $filter, i18n) { - return function(params) { - var scope = params.scope, - id = params.id, - job = params.job, - callback = params.callback, - action, jobs, url, action_label, hdr; - - if (!job) { - if (scope.completed_jobs) { - jobs = scope.completed_jobs; - } - else if (scope.running_jobs) { - jobs = scope.running_jobs; - } - else if (scope.queued_jobs) { - jobs = scope.queued_jobs; - } - else if (scope.all_jobs) { - jobs = scope.all_jobs; - } - else if (scope.jobs) { - jobs = scope.jobs; - } - job = Find({list: jobs, key: 'id', val: id }); - } - - if (job.status === 'pending' || job.status === 'running' || job.status === 'waiting') { - url = job.related.cancel; - action_label = 'cancel'; - hdr = i18n._('Cancel'); - } else { - url = job.url; - action_label = 'delete'; - hdr = i18n._('Delete'); - } - - action = function () { - Wait('start'); - Rest.setUrl(url); - if (action_label === 'cancel') { - Rest.post() - .then(() => { - $('#prompt-modal').modal('hide'); - if (callback) { - scope.$emit(callback, action_label); - } - else { - $state.reload(); - Wait('stop'); - } - }) - .catch(({obj, status}) => { - Wait('stop'); - $('#prompt-modal').modal('hide'); - if (status === 403) { - Alert('Error', obj.detail); - } - // Ignore the error. The job most likely already finished. - // ProcessErrors(scope, data, status, null, { hdr: 'Error!', msg: 'Call to ' + url + - // ' failed. POST returned status: ' + status }); - }); - } else { - Rest.destroy() - .then(() => { - $('#prompt-modal').modal('hide'); - if (callback) { - scope.$emit(callback, action_label); - } - else { - let reloadListStateParams = null; - - if(scope.jobs.length === 1 && $state.params.job_search && !_.isEmpty($state.params.job_search.page) && $state.params.job_search.page !== '1') { - reloadListStateParams = _.cloneDeep($state.params); - reloadListStateParams.job_search.page = (parseInt(reloadListStateParams.job_search.page)-1).toString(); - } - - $state.go('.', reloadListStateParams, {reload: true}); - Wait('stop'); - } - }) - .catch(({obj, status}) => { - Wait('stop'); - $('#prompt-modal').modal('hide'); - if (status === 403) { - Alert('Error', obj.detail); - } - // Ignore the error. The job most likely already finished. - //ProcessErrors(scope, data, status, null, { hdr: 'Error!', msg: 'Call to ' + url + - // ' failed. DELETE returned status: ' + status }); - }); - } - }; - - if (scope.removeCancelNotAllowed) { - scope.removeCancelNotAllowed(); - } - scope.removeCancelNotAllowed = scope.$on('CancelNotAllowed', function() { - Wait('stop'); - Alert('Job Completed', 'The request to cancel the job could not be submitted. The job already completed.', 'alert-info'); - }); - - if (scope.removeCancelJob) { - scope.removeCancelJob(); - } - scope.removeCancelJob = scope.$on('CancelJob', function() { - var cancelBody = "
" + i18n._("Are you sure you want to submit the request to cancel this job?") + "
"; - var deleteBody = "
" + i18n._("Are you sure you want to delete this job?") + "
"; - Prompt({ - hdr: hdr, - resourceName: `#${job.id} ` + $filter('sanitize')(job.name), - body: (action_label === 'cancel' || job.status === 'new') ? cancelBody : deleteBody, - action: action, - actionText: (action_label === 'cancel' || job.status === 'new') ? i18n._("OK") : i18n._("DELETE") - }); - }); - - if (action_label === 'cancel') { - Rest.setUrl(url); - Rest.get() - .then(({data}) => { - if (data.can_cancel) { - scope.$emit('CancelJob'); - } - else { - scope.$emit('CancelNotAllowed'); - } - }) - .catch(({data, status}) => { - ProcessErrors(scope, data, status, null, { hdr: 'Error!', msg: 'Call to ' + url + - ' failed. GET returned: ' + status }); - }); - } - else { - scope.$emit('CancelJob'); - } - }; -} - -DeleteJob.$inject = -[ '$state', 'Find', 'Rest', 'Wait', - 'ProcessErrors', 'Prompt', 'Alert', '$filter', 'i18n' -]; diff --git a/awx/ui/client/src/standard-out/standard-out-factories/lookup-name.factory.js b/awx/ui/client/src/standard-out/standard-out-factories/lookup-name.factory.js deleted file mode 100644 index 43b39da58c..0000000000 --- a/awx/ui/client/src/standard-out/standard-out-factories/lookup-name.factory.js +++ /dev/null @@ -1,36 +0,0 @@ -/************************************************* - * Copyright (c) 2016 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - - export default - ['Rest', 'ProcessErrors', 'Empty', function(Rest, ProcessErrors, Empty) { - return function(params) { - var url = params.url, - scope_var = params.scope_var, - scope = params.scope, - callback = params.callback; - Rest.setUrl(url); - Rest.get() - .then(({data}) => { - if (scope_var === 'inventory_source') { - scope.inventory = data.inventory; - } - if (!Empty(data.name)) { - scope[scope_var + '_name'] = data.name; - } - - if (callback) { - scope.$emit(callback, data); - } - }) - .catch(({data, status}) => { - if (status === 403 && params.ignore_403) { - return; - } - ProcessErrors(scope, data, status, null, { hdr: 'Error!', - msg: 'Failed to retrieve ' + url + '. GET returned: ' + status }); - }); - }; - }]; diff --git a/awx/ui/client/src/standard-out/standard-out-factories/main.js b/awx/ui/client/src/standard-out/standard-out-factories/main.js deleted file mode 100644 index 935c8dca37..0000000000 --- a/awx/ui/client/src/standard-out/standard-out-factories/main.js +++ /dev/null @@ -1,13 +0,0 @@ -/************************************************* - * Copyright (c) 2016 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -import lookUpName from './lookup-name.factory'; -import DeleteJob from './delete-job.factory'; - -export default - angular.module('StandardOutHelper', []) - .factory('LookUpName', lookUpName) - .factory('DeleteJob', DeleteJob); diff --git a/awx/ui/client/src/standard-out/standard-out.controller.js b/awx/ui/client/src/standard-out/standard-out.controller.js deleted file mode 100644 index 1a707aa706..0000000000 --- a/awx/ui/client/src/standard-out/standard-out.controller.js +++ /dev/null @@ -1,266 +0,0 @@ -/************************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -/** - * @ngdoc function - * @name controllers.function:JobStdout - * @description This controller's for the standard out page that can be displayed when a job runs -*/ - -export function JobStdoutController ($rootScope, $scope, $state, $stateParams, - GetBasePath, Rest, ProcessErrors, Empty, GetChoices, LookUpName, - ParseTypeChange, ParseVariableString, DeleteJob, Wait, i18n, - fieldChoices, fieldLabels, Project, Alert, InventorySource, - jobData) { - - var job_id = $stateParams.id, - jobType = $state.current.data.jobType; - - // This scope variable controls whether or not the left panel is shown and the right panel - // is expanded to take up the full screen - $scope.stdoutFullScreen = false; - $scope.toggleStdoutFullscreenTooltip = i18n._("Expand Output"); - - $scope.explanationLimit = 150; - - // Listen for job status updates that may come across via sockets. We need to check the payload - // to see whethere the updated job is the one that we're currently looking at. - $scope.$on(`ws-jobs`, function(e, data) { - if (parseInt(data.unified_job_id, 10) === parseInt(job_id,10) && $scope.job) { - $scope.job.status = data.status; - } - - if (data.status === 'failed' || data.status === 'canceled' || data.status === 'error' || data.status === 'successful') { - // Go out and refresh the job details - - Rest.setUrl(GetBasePath('base') + jobType + '/' + job_id + '/'); - Rest.get() - .then(({data}) => { - updateJobObj(data); - }); - } - }); - - $scope.previousTaskFailed = false; - - $scope.$watch('job.job_explanation', function(explanation) { - if (explanation && explanation.split(":")[0] === "Previous Task Failed") { - $scope.previousTaskFailed = true; - - var taskObj = JSON.parse(explanation.substring(explanation.split(":")[0].length + 1)); - // return a promise from the options request with the permission type choices (including adhoc) as a param - var fieldChoice = fieldChoices({ - $scope: $scope, - url: GetBasePath('unified_jobs'), - field: 'type' - }); - - // manipulate the choices from the options request to be set on - // scope and be usable by the list form - fieldChoice.then(function (choices) { - choices = - fieldLabels({ - choices: choices - }); - $scope.explanation_fail_type = choices[taskObj.job_type]; - $scope.explanation_fail_name = taskObj.job_name; - $scope.explanation_fail_id = taskObj.job_id; - $scope.task_detail = $scope.explanation_fail_type + " failed for " + $scope.explanation_fail_name + " with ID " + $scope.explanation_fail_id + "."; - }); - } else { - $scope.previousTaskFailed = false; - } - }); - - // Set the parse type so that CodeMirror knows how to display extra params YAML/JSON - $scope.parseType = 'yaml'; - - function updateJobObj(updatedJobData) { - - // Go out and get the job details based on the job type. jobType gets defined - // in the data block of the route declaration for each of the different types - // of stdout jobs. - - $scope.job = updatedJobData; - $scope.job_template_name = updatedJobData.name; - $scope.created_by = updatedJobData.summary_fields.created_by; - $scope.project_name = (updatedJobData.summary_fields.project) ? updatedJobData.summary_fields.project.name : ''; - $scope.inventory_name = (updatedJobData.summary_fields.inventory) ? updatedJobData.summary_fields.inventory.name : ''; - $scope.job_template_url = '/#/templates/' + updatedJobData.unified_job_template; - if($scope.inventory_name && updatedJobData.inventory && updatedJobData.summary_fields.inventory && updatedJobData.summary_fields.inventory.kind) { - if(updatedJobData.summary_fields.inventory.kind === '') { - $scope.inventory_url = '/#/inventories/inventory' + updatedJobData.inventory; - } - else if(updatedJobData.summary_fields.inventory.kind === 'smart') { - $scope.inventory_url = '/#/inventories/smart_inventory' + updatedJobData.inventory; - } - } - else { - $scope.inventory_url = ''; - } - $scope.project_url = ($scope.project_name && updatedJobData.project) ? '/#/projects/' + updatedJobData.project : ''; - $scope.credential_name = (updatedJobData.summary_fields.credential) ? updatedJobData.summary_fields.credential.name : ''; - $scope.credential_url = (updatedJobData.credential) ? '/#/credentials/' + updatedJobData.credential : ''; - $scope.cloud_credential_url = (updatedJobData.cloud_credential) ? '/#/credentials/' + updatedJobData.cloud_credential : ''; - if(updatedJobData.summary_fields && updatedJobData.summary_fields.source_workflow_job && - updatedJobData.summary_fields.source_workflow_job.id){ - $scope.workflow_result_link = `/#/workflows/${updatedJobData.summary_fields.source_workflow_job.id}`; - } - $scope.playbook = updatedJobData.playbook; - $scope.credential = updatedJobData.credential; - $scope.cloud_credential = updatedJobData.cloud_credential; - $scope.forks = updatedJobData.forks; - $scope.limit = updatedJobData.limit; - $scope.verbosity = updatedJobData.verbosity; - $scope.job_tags = updatedJobData.job_tags; - $scope.job.module_name = updatedJobData.module_name; - if (updatedJobData.extra_vars) { - $scope.variables = ParseVariableString(updatedJobData.extra_vars); - } - - $scope.$on('getInventorySource', function(e, d) { - $scope.inv_manage_group_link = '/#/inventories/inventory/' + d.inventory + '/inventory_sources/edit/' + d.id; - }); - - // If we have a source then we have to go get the source choices from the server - if (!Empty(updatedJobData.source)) { - if ($scope.removeChoicesReady) { - $scope.removeChoicesReady(); - } - $scope.removeChoicesReady = $scope.$on('ChoicesReady', function() { - $scope.source_choices.every(function(e) { - if (e.value === updatedJobData.source) { - $scope.source = e.label; - return false; - } - return true; - }); - }); - // GetChoices can be found in the helper: Utilities.js - // It attaches the source choices to $scope.source_choices. - // Then, when the callback is fired, $scope.source is bound - // to the corresponding label. - GetChoices({ - scope: $scope, - url: GetBasePath('inventory_sources'), - field: 'source', - variable: 'source_choices', - choice_name: 'choices', - callback: 'ChoicesReady' - }); - } - - // LookUpName can be found in the lookup-name.factory - // It attaches the name that it gets (based on the url) - // to the $scope variable defined by the attribute scope_var. - if (!Empty(updatedJobData.credential)) { - LookUpName({ - scope: $scope, - scope_var: 'credential', - url: GetBasePath('credentials') + updatedJobData.credential + '/', - ignore_403: true - }); - } - - if (!Empty(updatedJobData.inventory)) { - LookUpName({ - scope: $scope, - scope_var: 'inventory', - url: GetBasePath('inventory') + updatedJobData.inventory + '/' - }); - } - - if (!Empty(updatedJobData.project)) { - LookUpName({ - scope: $scope, - scope_var: 'project', - url: GetBasePath('projects') + updatedJobData.project + '/' - }); - } - - if (!Empty(updatedJobData.cloud_credential)) { - LookUpName({ - scope: $scope, - scope_var: 'cloud_credential', - url: GetBasePath('credentials') + updatedJobData.cloud_credential + '/', - ignore_403: true - }); - } - - if (!Empty(updatedJobData.inventory_source)) { - LookUpName({ - scope: $scope, - scope_var: 'inventory_source', - url: GetBasePath('inventory_sources') + updatedJobData.inventory_source + '/', - callback: 'getInventorySource' - }); - } - - if (updatedJobData.extra_vars) { - ParseTypeChange({ - scope: $scope, - field_id: 'pre-formatted-variables', - readOnly: true - }); - } - - // If the job isn't running we want to clear out the interval that goes out and checks for stdout updates. - // This interval is defined in the standard out log directive controller. - if (updatedJobData.status === 'successful' || updatedJobData.status === 'failed' || updatedJobData.status === 'error' || updatedJobData.status === 'canceled') { - if ($rootScope.jobStdOutInterval) { - window.clearInterval($rootScope.jobStdOutInterval); - } - } - - } - - if ($scope.removeDeleteFinished) { - $scope.removeDeleteFinished(); - } - $scope.removeDeleteFinished = $scope.$on('DeleteFinished', function(e, action) { - Wait('stop'); - if (action !== 'cancel') { - Wait('stop'); - $state.go('jobs'); - } - }); - - // TODO: this is currently not used but is necessary for cases where sockets - // are not available and a manual refresh trigger is needed. - $scope.refresh = function(){ - $scope.$emit('LoadStdout'); - }; - - // Click binding for the expand/collapse button on the standard out log - $scope.toggleStdoutFullscreen = function() { - $scope.stdoutFullScreen = !$scope.stdoutFullScreen; - - if ($scope.stdoutFullScreen === true) { - $scope.toggleStdoutFullscreenTooltip = i18n._("Collapse Output"); - } else if ($scope.stdoutFullScreen === false) { - $scope.toggleStdoutFullscreenTooltip = i18n._("Expand Output"); - } - }; - - $scope.deleteJob = function() { - DeleteJob({ - scope: $scope, - id: $scope.job.id, - job: $scope.job, - callback: 'DeleteFinished' - }); - }; - - updateJobObj(jobData); - -} - -JobStdoutController.$inject = [ '$rootScope', '$scope', '$state', - '$stateParams', 'GetBasePath', 'Rest', 'ProcessErrors', - 'Empty', 'GetChoices', 'LookUpName', 'ParseTypeChange', - 'ParseVariableString', 'DeleteJob', 'Wait', 'i18n', - 'fieldChoices', 'fieldLabels', 'ProjectModel', 'Alert', 'InventorySourceModel', - 'jobData']; diff --git a/awx/ui/client/src/standard-out/standard-out.block.less b/awx/ui/client/src/workflow-results/standard-out.block.less similarity index 99% rename from awx/ui/client/src/standard-out/standard-out.block.less rename to awx/ui/client/src/workflow-results/standard-out.block.less index 5fd5742e82..980f401c75 100644 --- a/awx/ui/client/src/standard-out/standard-out.block.less +++ b/awx/ui/client/src/workflow-results/standard-out.block.less @@ -169,4 +169,4 @@ standard-out-log { cursor: pointer; border-radius: 5px; font-size: 11px; -} +} \ No newline at end of file diff --git a/awx/ui/test/spec/job-results/job-results.controller-test.js b/awx/ui/test/spec/job-results/job-results.controller-test.js deleted file mode 100644 index c05f2329f7..0000000000 --- a/awx/ui/test/spec/job-results/job-results.controller-test.js +++ /dev/null @@ -1,701 +0,0 @@ -'use strict'; -import moment from 'moment'; - -describe('Controller: jobResultsController', () => { - // Setup - let jobResultsController; - - let jobData, jobDataOptions, jobLabels, jobFinished, count, $scope, ParseTypeChange, ParseVariableString, jobResultsService, eventQueue, $compile, eventResolve, populateResolve, $rScope, q, $log, Dataset, Rest, $state, QuerySet, i18n,fieldChoices, fieldLabels, $interval, workflowResultsService, statusSocket, jobExtraCredentials; - - statusSocket = function() { - var fn = function() {}; - return fn; - }; - jobData = { - related: {}, - summary_fields: { - inventory: { - id: null, - kind: '' - } - } - }; - jobDataOptions = { - actions: { - get: {} - } - }; - jobLabels = {}; - jobFinished = true; - count = { - val: {}, - countFinished: false - }; - eventResolve = { - results: [] - }; - populateResolve = {}; - - Dataset = { - data: {foo: "bar"} - }; - - let provideVals = () => { - angular.mock.module('jobResults', ($provide) => { - ParseTypeChange = jasmine.createSpy('ParseTypeChange'); - ParseVariableString = jasmine.createSpy('ParseVariableString'); - jobResultsService = jasmine.createSpyObj('jobResultsService', [ - 'deleteJob', - 'cancelJob', - 'relaunchJob', - 'getEvents', - 'getJobData', - ]); - eventQueue = jasmine.createSpyObj('eventQueue', [ - 'populate', - 'markProcessed', - 'initialize' - ]); - - Rest = jasmine.createSpyObj('Rest', [ - 'setUrl', - 'get' - ]); - - $state = jasmine.createSpyObj('$state', [ - 'reload' - ]); - - QuerySet = jasmine.createSpyObj('QuerySet', [ - 'encodeQueryset' - ]); - - i18n = { - _: function(txt) { - return txt; - } - }; - - $provide.service('workflowResultsService', () => { - return jasmine.createSpyObj('workflowResultsService', ['createOneSecondTimer', 'destroyTimer']); - }); - - $provide.value('statusSocket', statusSocket); - - $provide.value('jobData', jobData); - $provide.value('jobDataOptions', jobDataOptions); - $provide.value('jobLabels', jobLabels); - $provide.value('jobFinished', jobFinished); - $provide.value('count', count); - $provide.value('ParseTypeChange', ParseTypeChange); - $provide.value('ParseVariableString', ParseVariableString); - $provide.value('jobResultsService', jobResultsService); - $provide.value('eventQueue', eventQueue); - $provide.value('Dataset', Dataset); - $provide.value('Rest', Rest); - $provide.value('$state', $state); - $provide.value('QuerySet', QuerySet); - $provide.value('i18n', i18n); - $provide.value('fieldChoices', fieldChoices); - $provide.value('fieldLabels', fieldLabels); - $provide.value('jobExtraCredentials', jobExtraCredentials); - }); - }; - - let injectVals = () => { - angular.mock.inject((_jobData_, _jobDataOptions_, _jobLabels_, _jobFinished_, _count_, _ParseTypeChange_, _ParseVariableString_, _jobResultsService_, _eventQueue_, _$compile_, $rootScope, $controller, $q, $httpBackend, _$log_, _Dataset_, _Rest_, _$state_, _QuerySet_, _$interval_, _workflowResultsService_, _statusSocket_) => { - // when you call $scope.$apply() (which you need to do to - // to get inside of .then blocks to test), something is - // causing a request for all static files. - // - // this is a hack to just pass those requests through - // - // from googling this is probably due to angular-router - // weirdness - $httpBackend.when("GET", (url) => (url - .indexOf("/static/") !== -1)) - .respond(''); - - $httpBackend - .whenGET('/api') - .respond(200, ''); - - $scope = $rootScope.$new(); - $rScope = $rootScope; - q = $q; - jobData = _jobData_; - jobDataOptions = _jobDataOptions_; - jobLabels = _jobLabels_; - jobFinished = _jobFinished_; - count = _count_; - ParseTypeChange = _ParseTypeChange_; - ParseVariableString = _ParseVariableString_; - ParseVariableString.and.returnValue(jobData.extra_vars); - jobResultsService = _jobResultsService_; - eventQueue = _eventQueue_; - $log = _$log_; - Dataset = _Dataset_; - Rest = _Rest_; - $state = _$state_; - QuerySet = _QuerySet_; - $interval = _$interval_; - workflowResultsService = _workflowResultsService_; - statusSocket = _statusSocket_; - - jobResultsService.getEvents.and - .returnValue(eventResolve); - eventQueue.populate.and - .returnValue(populateResolve); - - jobResultsService.getJobData = function() { - var deferred = $q.defer(); - deferred.resolve({}); - return deferred.promise; - }; - - $compile = _$compile_; - - jobResultsController = $controller('jobResultsController', { - $scope: $scope, - jobData: jobData, - jobDataOptions: jobDataOptions, - jobLabels: jobLabels, - jobFinished: jobFinished, - count: count, - ParseTypeChange: ParseTypeChange, - jobResultsService: jobResultsService, - eventQueue: eventQueue, - $compile: $compile, - $log: $log, - $q: q, - Dataset: Dataset, - Rest: Rest, - $state: $state, - QuerySet: QuerySet, - statusSocket: statusSocket - }); - }); - }; - - beforeEach(angular.mock.module('shared')); - - let bootstrapTest = () => { - provideVals(); - injectVals(); - }; - - describe('bootstrap resolve values on scope', () => { - beforeEach(() => { - bootstrapTest(); - }); - - it('should set values to scope based on resolve', () => { - expect($scope.job).toBe(jobData); - expect($scope.jobOptions).toBe(jobDataOptions.actions.GET); - expect($scope.labels).toBe(jobLabels); - }); - }); - - describe('getLinks()', () => { - beforeEach(() => { - jobData.related = { - "created_by": "api/v2/users/12", - "inventory": "api/v2/inventories/12", - "project": "api/v2/projects/12", - "credential": "api/v2/credentials/12", - "cloud_credential": "api/v2/credentials/13", - "network_credential": "api/v2/credentials/14", - }; - - jobData.summary_fields.inventory = { - id: 12, - kind: '' - }; - - bootstrapTest(); - }); - - it('should transform related links and set to scope var', () => { - expect($scope.created_by_link).toBe('/#/users/12'); - expect($scope.inventory_link).toBe('/#/inventories/inventory/12'); - expect($scope.project_link).toBe('/#/projects/12'); - expect($scope.machine_credential_link).toBe('/#/credentials/12'); - expect($scope.cloud_credential_link).toBe('/#/credentials/13'); - expect($scope.network_credential_link).toBe('/#/credentials/14'); - }); - }); - - describe('getLabels()', () => { - beforeEach(() => { - jobDataOptions.actions.GET = { - status: { - choices: [ - ["new", - "New"] - ] - }, - job_type: { - choices: [ - ["job", - "Playbook Run"] - ] - }, - verbosity: { - choices: [ - [0, - "0 (Normal)"] - ] - } - }; - jobData.status = "new"; - jobData.job_type = "job"; - jobData.verbosity = 0; - - bootstrapTest(); - }); - - it('should set scope variables based on options', () => { - $scope.job_status = jobData.status; - - $scope.$apply(); - expect($scope.status_label).toBe("New"); - expect($scope.type_label).toBe("Playbook Run"); - expect($scope.verbosity_label).toBe("0 (Normal)"); - }); - }); - - describe('elapsed timer', () => { - describe('job running', () => { - beforeEach(() => { - jobData.started = moment(); - jobData.status = 'running'; - - bootstrapTest(); - }); - - it('should start timer', () => { - expect(workflowResultsService.createOneSecondTimer).toHaveBeenCalled(); - }); - }); - - describe('job waiting', () => { - beforeEach(() => { - jobData.started = null; - jobData.status = 'waiting'; - - bootstrapTest(); - }); - - it('should not start timer', () => { - expect(workflowResultsService.createOneSecondTimer).not.toHaveBeenCalled(); - }); - }); - - describe('job transitions to running', () => { - beforeEach(() => { - jobData.started = null; - jobData.status = 'waiting'; - jobData.id = 13; - - bootstrapTest(); - - $rScope.$broadcast('ws-jobs', { unified_job_id: jobData.id, status: 'running' }); - }); - - it('should start timer', () => { - expect(workflowResultsService.createOneSecondTimer).toHaveBeenCalled(); - }); - - describe('job transitions from running to finished', () => { - it('should cleanup timer', () => { - $rScope.$broadcast('ws-jobs', { unified_job_id: jobData.id, status: 'successful' }); - expect(workflowResultsService.destroyTimer).toHaveBeenCalled(); - }); - }); - }); - }); - - describe('extra vars stuff', () => { - let extraVars = "foo"; - - beforeEach(() => { - jobData.extra_vars = extraVars; - - bootstrapTest(); - }); - - it('should have extra vars on scope', () => { - expect($scope.job.extra_vars).toBe(extraVars); - }); - - it('should call ParseVariableString and set to scope', () => { - expect(ParseVariableString) - .toHaveBeenCalledWith(extraVars); - expect($scope.variables).toBe(extraVars); - }); - - it('should set the parse type to yaml', () => { - expect($scope.parseType).toBe('yaml'); - }); - - it('should call ParseTypeChange with proper params', () => { - let params = { - scope: $scope, - field_id: 'pre-formatted-variables', - readOnly: true - }; - - expect(ParseTypeChange) - .toHaveBeenCalledWith(params); - }); - }); - - describe('$scope.toggleStdoutFullscreen', () => { - beforeEach(() => { - bootstrapTest(); - }); - - it('should toggle $scope.stdoutFullScreen', () => { - // essentially set to false - expect($scope.stdoutFullScreen).toBe(false); - - // toggle once to true - $scope.toggleStdoutFullscreen(); - expect($scope.stdoutFullScreen).toBe(true); - - // toggle again to false - $scope.toggleStdoutFullscreen(); - expect($scope.stdoutFullScreen).toBe(false); - }); - }); - - describe('$scope.deleteJob', () => { - beforeEach(() => { - bootstrapTest(); - }); - - it('should delete the job', () => { - let job = $scope.job; - $scope.deleteJob(); - expect(jobResultsService.deleteJob).toHaveBeenCalledWith(job); - }); - }); - - describe('$scope.cancelJob', () => { - beforeEach(() => { - bootstrapTest(); - }); - - it('should cancel the job', () => { - let job = $scope.job; - $scope.cancelJob(); - expect(jobResultsService.cancelJob).toHaveBeenCalledWith(job); - }); - }); - - describe('count stuff', () => { - beforeEach(() => { - count = { - val: { - ok: 1, - skipped: 2, - unreachable: 3, - failures: 4, - changed: 5 - }, - countFinished: true - }; - - bootstrapTest(); - }); - - it('should set count values to scope', () => { - expect($scope.count).toBe(count.val); - expect($scope.countFinished).toBe(true); - }); - - it('should find the hostCount based on the count', () => { - expect($scope.hostCount).toBe(15); - }); - }); - - describe('follow stuff - incomplete', () => { - beforeEach(() => { - jobFinished = false; - - bootstrapTest(); - }); - - it('should set followEngaged based on jobFinished incomplete', () => { - expect($scope.followEngaged).toBe(true); - }); - - it('should set followTooltip based on jobFinished incomplete', () => { - expect($scope.followTooltip).toBe("Currently following standard out as it comes in. Click to unfollow."); - }); - }); - - describe('follow stuff - finished', () => { - beforeEach(() => { - jobFinished = true; - - bootstrapTest(); - }); - - it('should set followEngaged based on jobFinished', () => { - expect($scope.followEngaged).toBe(false); - }); - - it('should set followTooltip based on jobFinished', () => { - expect($scope.followTooltip).toBe("Jump to last line of standard out."); - }); - }); - - describe('event stuff', () => { - beforeEach(() => { - jobData.id = 1; - jobData.related.job_events = "url"; - - bootstrapTest(); - }); - - xit('should make a rest call to get already completed events', () => { - expect(jobResultsService.getEvents).toHaveBeenCalledWith("url"); - }); - - xit('should call processEvent when receiving message', () => { - let eventPayload = {"foo": "bar"}; - $rScope.$broadcast('ws-job_events-1', eventPayload); - expect(eventQueue.populate).toHaveBeenCalledWith(eventPayload); - }); - - it('should set the job status on scope when receiving message', () => { - let eventPayload = { - unified_job_id: 1, - status: 'finished' - }; - $rScope.$broadcast('ws-jobs', eventPayload); - expect($scope.job_status).toBe(eventPayload.status); - }); - }); - - describe('getEvents and populate stuff', () => { - describe('getEvents', () => { - let event1 = { - event: 'foo' - }; - - let event2 = { - event_name: 'bar' - }; - - let event1Processed = { - event_name: 'foo' - }; - - beforeEach(() => { - eventResolve = { - results: [ - event1, - event2 - ] - }; - - bootstrapTest(); - - $scope.$apply(); - }); - - xit('should change the event name to event_name', () => { - expect(eventQueue.populate) - .toHaveBeenCalledWith(event1Processed); - }); - - xit('should pass through the event with event_name', () => { - expect(eventQueue.populate) - .toHaveBeenCalledWith(event2); - }); - - xit('should have called populate twice', () => { - expect(eventQueue.populate.calls.count()).toEqual(2); - }); - - // TODO: can't figure out how to a test of events.next... - // if you set events.next to true it causes the tests to - // stop running - }); - - describe('populate - start time', () => { - beforeEach(() => { - jobData.start = ""; - - populateResolve = { - startTime: 'foo', - changes: ['startTime'] - }; - - bootstrapTest(); - - $scope.$apply(); - }); - - xit('sets start time when passed as a change', () => { - expect($scope.job.start).toBe('foo'); - }); - }); - - describe('populate - start time already set', () => { - beforeEach(() => { - jobData.start = "bar"; - - populateResolve = { - startTime: 'foo', - changes: ['startTime'] - }; - - bootstrapTest(); - - $scope.$apply(); - }); - - xit('does not set start time because already set', () => { - expect($scope.job.start).toBe('bar'); - }); - }); - - describe('populate - count already received', () => { - let receiveCount = { - ok: 2, - skipped: 2, - unreachable: 2, - failures: 2, - changed: 2 - }; - - let alreadyCount = { - ok: 3, - skipped: 3, - unreachable: 3, - failures: 3, - changed: 3 - }; - - beforeEach(() => { - count.countFinished = true; - count.val = alreadyCount; - - populateResolve = { - count: receiveCount, - changes: ['count'] - }; - - bootstrapTest(); - - $scope.$apply(); - }); - - xit('count does not change', () => { - expect($scope.count).toBe(alreadyCount); - expect($scope.hostCount).toBe(15); - }); - }); - - describe('populate - playCount, taskCount and countFinished', () => { - beforeEach(() => { - - populateResolve = { - playCount: 12, - taskCount: 13, - changes: ['playCount', 'taskCount', 'countFinished'] - }; - - bootstrapTest(); - - $scope.$apply(); - }); - - xit('sets playCount', () => { - expect($scope.playCount).toBe(12); - }); - - xit('sets taskCount', () => { - expect($scope.taskCount).toBe(13); - }); - - xit('sets countFinished', () => { - expect($scope.countFinished).toBe(true); - }); - }); - - describe('populate - finishedTime', () => { - beforeEach(() => { - jobData.finished = ""; - - populateResolve = { - finishedTime: "finished_time", - changes: ['finishedTime'] - }; - - bootstrapTest(); - - $scope.$apply(); - }); - - xit('sets finished time and changes follow tooltip', () => { - expect($scope.job.finished).toBe('finished_time'); - expect($scope.jobFinished).toBe(true); - expect($scope.followTooltip) - .toBe("Jump to last line of standard out."); - }); - }); - - describe('populate - finishedTime when already finished', () => { - beforeEach(() => { - jobData.finished = "already_set"; - - populateResolve = { - finishedTime: "finished_time", - changes: ['finishedTime'] - }; - - bootstrapTest(); - - $scope.$apply(); - }); - - xit('does not set finished time because already set', () => { - expect($scope.job.finished).toBe('already_set'); - expect($scope.jobFinished).toBe(true); - expect($scope.followTooltip) - .toBe("Jump to last line of standard out."); - }); - }); - - describe('populate - stdout', () => { - beforeEach(() => { - - populateResolve = { - counter: 12, - stdout: "line", - changes: ['stdout'] - }; - - bootstrapTest(); - - spyOn($log, 'error'); - - $scope.followEngaged = true; - - $scope.$apply(); - }); - - xit('creates new child scope for the event', () => { - expect($scope.events[12].event).toBe(populateResolve); - - // in unit test, followScroll should not be defined as - // directive has not been instantiated - expect($log.error).toHaveBeenCalledWith("follow scroll undefined, standard out directive not loaded yet?"); - }); - }); - }); -}); diff --git a/awx/ui/test/spec/job-results/job-results.service-test.js b/awx/ui/test/spec/job-results/job-results.service-test.js deleted file mode 100644 index 1219f2e450..0000000000 --- a/awx/ui/test/spec/job-results/job-results.service-test.js +++ /dev/null @@ -1,50 +0,0 @@ -'use strict'; - -describe('jobResultsService', () => { - let jobResultsService; - - beforeEach(angular.mock.module('awApp')); - - beforeEach(angular.mock.inject(( _jobResultsService_) => { - jobResultsService = _jobResultsService_; - })); - - describe('getCountsFromStatsEvent()', () => { - it('properly counts hosts based on task state', () => { - let event_data = { - "skipped": { - "skipped-host": 5 // this host skipped all 5 tasks - }, - "ok": { - "ok-host": 5, // this host was ok on all 5 tasks - "changed-host": 4 // this host had 4 ok tasks, had 1 changed task - }, - "changed": { - "changed-host": 1 - }, - "failures": { - "failed-host": 1 // this host had a failed task - }, - "dark": { - "unreachable-host": 1 // this host was unreachable - }, - "processed": { - "ok-host": 1, - "changed-host": 1, - "skipped-host": 1, - "failed-host": 1, - "unreachable-host": 1 - }, - "playbook_uuid": "c23d8872-c92a-4e96-9f78-abe6fef38f33", - "playbook": "some_playbook.yml", - }; - expect(jobResultsService.getCountsFromStatsEvent(event_data)).toEqual({ - 'ok': 1, - 'skipped': 1, - 'unreachable': 1, - 'failures': 1, - 'changed': 1 - }); - }); - }); -}); diff --git a/awx/ui/test/spec/job-results/parse-stdout.service-test.js b/awx/ui/test/spec/job-results/parse-stdout.service-test.js deleted file mode 100644 index 86441c1ffa..0000000000 --- a/awx/ui/test/spec/job-results/parse-stdout.service-test.js +++ /dev/null @@ -1,212 +0,0 @@ -'use strict'; - -describe('parseStdoutService', () => { - let parseStdoutService, - log; - - beforeEach(angular.mock.module('awApp')); - - beforeEach(angular.mock.module('jobResults',($provide) => { - log = jasmine.createSpyObj('$log', [ - 'error' - ]); - - $provide.value('$log', log); - })); - - beforeEach(angular.mock.inject((_$log_, _parseStdoutService_) => { - parseStdoutService = _parseStdoutService_; - })); - - describe('prettify()', () => { - it('returns lines of stdout with styling classes', () => { - let line = "[0;32mok: [host-00]", - styledLine = 'ok: [host-00]'; - expect(parseStdoutService.prettify(line)).toBe(styledLine); - }); - - it('can return lines of stdout without styling classes', () => { - let line = "[0;32mok: [host-00][0m", - unstyled = "unstyled", - unstyledLine = 'ok: [host-00]'; - expect(parseStdoutService.prettify(line, unstyled)).toBe(unstyledLine); - }); - - it('can return empty strings', () => { - expect(parseStdoutService.prettify("")).toBe(""); - }); - }); - - describe('getLineClasses()', () => { - it('creates a string that is used as a class', () => { - let headerEvent = { - event_name: 'playbook_on_task_start', - event_data: { - play_uuid:"0f667a23-d9ab-4128-a735-80566bcdbca0", - task_uuid: "80dd087c-268b-45e8-9aab-1083bcfd9364" - } - }; - let lineNum = 3; - let line = "TASK [setup] *******************************************************************"; - let styledLine = " header_task header_task_80dd087c-268b-45e8-9aab-1083bcfd9364 actual_header play_0f667a23-d9ab-4128-a735-80566bcdbca0 line_num_3"; - expect(parseStdoutService.getLineClasses(headerEvent, line, lineNum)).toBe(styledLine); - }); - }); - - describe('getStartTime()', () => { - // TODO: the problem is that the date here calls moment, and thus - // the date will be timezone'd in the string (this could be - // different based on where you are) - xit('creates returns a badge with the start time of the event', () => { - let headerEvent = { - event_name: 'playbook_on_play_start', - created: "2016-11-22T21:15:54.736Z" - }; - - let line = "PLAY [add hosts to inventory] **************************************************"; - let badgeDiv = '
13:15:54
'; - expect(parseStdoutService.getStartTimeBadge(headerEvent, line)).toBe(badgeDiv); - }); - }); - - describe('getCollapseIcon()', () => { - let emptySpan = ` -`; - - it('returns empty expander for non-header event', () => { - let nonHeaderEvent = { - event_name: 'not_header', - start_line: 0, - end_line: 1, - stdout:"line1" - }; - expect(parseStdoutService.getCollapseIcon(nonHeaderEvent)) - .toBe(emptySpan); - }); - - it('returns collapse/decollapse icons for header events', () => { - let headerEvent = { - event_name: 'playbook_on_task_start', - start_line: 0, - end_line: 1, - stdout:"line1", - event_data: { - task_uuid: '1da9012d-18e6-4562-85cd-83cf10a97f86' - } - }; - let line = "TASK [setup] *******************************************************************"; - let expandSpan = ` - - - -`; - - expect(parseStdoutService.getCollapseIcon(headerEvent, line)) - .toBe(expandSpan); - }); - }); - - describe('getLineArr()', () => { - it('returns stdout in array format', () => { - let mockEvent = { - start_line: 12, - end_line: 14, - stdout: "line1\r\nline2\r\n" - }; - let expectedReturn = [[13, "line1"],[14, "line2"]]; - - let returnedEvent = parseStdoutService.getLineArr(mockEvent); - - expect(returnedEvent).toEqual(expectedReturn); - }); - - it('deals correctly with capped lines', () => { - let mockEvent = { - start_line: 7, - end_line: 11, - stdout: "a\r\nb\r\nc..." - }; - let expectedReturn = [[8, "a"],[9, "b"], [10,"c..."]]; - - let returnedEvent = parseStdoutService.getLineArr(mockEvent); - - expect(returnedEvent).toEqual(expectedReturn); - }); - }); - - describe('parseStdout()', () => { - let mockEvent = {"foo": "bar"}; - - it('calls functions', function() { - spyOn(parseStdoutService, 'getLineArr').and - .returnValue([[13, 'line1'], [14, 'line2']]); - spyOn(parseStdoutService, 'getLineClasses').and - .returnValue(""); - spyOn(parseStdoutService, 'getCollapseIcon').and - .returnValue(""); - spyOn(parseStdoutService, 'getAnchorTags').and - .returnValue(""); - spyOn(parseStdoutService, 'prettify').and - .returnValue("prettified_line"); - spyOn(parseStdoutService, 'getStartTimeBadge').and - .returnValue(""); - - parseStdoutService.parseStdout(mockEvent); - - expect(parseStdoutService.getLineArr) - .toHaveBeenCalledWith(mockEvent); - expect(parseStdoutService.getLineClasses) - .toHaveBeenCalledWith(mockEvent, 'line1', 13); - expect(parseStdoutService.getCollapseIcon) - .toHaveBeenCalledWith(mockEvent, 'line1'); - expect(parseStdoutService.getAnchorTags) - .toHaveBeenCalledWith(mockEvent); - expect(parseStdoutService.prettify) - .toHaveBeenCalledWith('line1'); - expect(parseStdoutService.getStartTimeBadge) - .toHaveBeenCalledWith(mockEvent, 'line1'); - - // get line arr should be called once for the event - expect(parseStdoutService.getLineArr.calls.count()) - .toBe(1); - - // other functions should be called twice (once for each - // line) - expect(parseStdoutService.getLineClasses.calls.count()) - .toBe(2); - expect(parseStdoutService.getCollapseIcon.calls.count()) - .toBe(2); - expect(parseStdoutService.getAnchorTags.calls.count()) - .toBe(2); - expect(parseStdoutService.prettify.calls.count()) - .toBe(2); - }); - - it('returns dom-ified lines', function() { - spyOn(parseStdoutService, 'getLineArr').and - .returnValue([[13, 'line1']]); - spyOn(parseStdoutService, 'getLineClasses').and - .returnValue("line_classes"); - spyOn(parseStdoutService, 'getCollapseIcon').and - .returnValue("collapse_icon_dom"); - spyOn(parseStdoutService, 'getAnchorTags').and - .returnValue(`" anchor_tag_dom`); - spyOn(parseStdoutService, 'prettify').and - .returnValue("prettified_line"); - spyOn(parseStdoutService, 'getStartTimeBadge').and - .returnValue(""); - - var returnedString = parseStdoutService.parseStdout(mockEvent); - - var expectedString = ` -
-
collapse_icon_dom13
-
prettified_line
-
`; - expect(returnedString).toBe(expectedString); - }); - }); -});