[7.x] [ML] Job Selector - remove old job selector directory (#36697) (#36803)

* fix merge conflicts in translation file

* Get changes missed in conflict resolution

* run i18n check to fix internationalization failing check
This commit is contained in:
Melissa Alvarez 2019-05-21 18:33:02 -04:00 committed by GitHub
parent 3452ba97ea
commit 4ef77f554d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 0 additions and 1430 deletions

View file

@ -4,32 +4,6 @@ Copyright 2012-2019 Elasticsearch B.V.
---
This product has relied on ASTExplorer that is licensed under MIT.
---
This product includes code that was extracted from angular-ui-bootstrap@0.13.1
which is available under an "MIT" license
The MIT License
Copyright (c) 2012-2016 the AngularUI Team, http://angular-ui.github.io/bootstrap/
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
---
This product uses Noto fonts that are licensed under the SIL Open
Font License, Version 1.1.

View file

@ -16,7 +16,6 @@ import 'ui/autoload/all';
import 'ui/kbn_top_nav';
import 'plugins/ml/access_denied';
import 'plugins/ml/lib/angular_bootstrap_patch';
import 'plugins/ml/jobs';
import 'plugins/ml/services/calendar_service';
import 'plugins/ml/components/messagebar';

View file

@ -1 +0,0 @@
@import 'job_select_list';

View file

@ -1,343 +0,0 @@
// SASSTODO: This file needs a rewrite. Needs to be variablized and have actual calculations rather than pixels.
// Lots of custom colors in here need to be replaced.
// SASSTODO: EXTREMELY DANGEROUS OVERWRITE
// Little worried about touching it now
navbar {
padding: 10px;
background-color: #EFF0F1;
}
// SASSTODO: EXTREMELY DANGEROUS OVERWRITE
// Little worried about touching it now
navbar button[disabled] {
color: #FFF;
background-color: #0079a5;
}
.ml-job-selector {
margin: -9px -14px;
.header {
padding: 9px 14px;
border-bottom: 1px solid #eeeeee;
}
.footer {
text-align: center;
padding: 9px;
border-top: 1px solid #eee;
.kuiButton--primary:focus {
background-color: #0079a5;
}
}
.select-all-controls {
margin-bottom: 0px;
padding-bottom: 4px;
border-bottom: 1px solid #eee;
& > div {
width: 280px;
display: inline-block;
font-weight: bold;
}
}
// SASSTODO: Replace with proper selector
hr {
margin: 2px 0px;
}
// SASSTODO: Replace with proper selector
label {
margin-bottom: 0px;
font-weight: normal;
}
// SASSTODO: Replace with proper selector
input {
margin-right: 4px;
margin-left: 4px;
}
.apply-time-range {
margin-top: 16px;
position: absolute;
right: 16px;
bottom: 12px;
// SASSTODO: Replace with proper selector
input {
margin-right: 0px;
}
}
.select-list {
max-height: 400px;
overflow-y: auto;
overflow-x: hidden;
background-color: #F5F7FA;
.list-section-title {
margin: 14px;
margin-bottom: 5px;
}
.list-section {
border: 1px solid #e0e0e0;
background-color: #ffffff;
margin: 14px;
margin-top: 5px;
padding: 10px;
border-radius: 3px;
}
.list-section-single {
margin: -1px;
border-radius: 0px;
border: 0px;
}
.group-title {
border-left: none;
border-right: none;
.expand-group-button {
padding-right: 2px;
width: 20px;
}
}
.job-row {
white-space: nowrap;
vertical-align: top;
.use-time-range {
margin-top: 4px;
}
.job-row-inner {
display: inline-block;
font-size: 13px;
font-weight: normal;
color: #444444;
white-space: nowrap;
margin: 0px;
padding: 0px;
line-height: 20px;
// SASSTODO: Replace with proper selector
& > label > div {
width: 280px;
display: inline-block;
vertical-align: top;
height: 30px;
padding-top: 6px;
overflow: hidden;
text-overflow: ellipsis;
padding-right: 4px;
.disabled-job {
color: #cccccc;
}
}
// SASSTODO: Replace with proper selector
& > label > div:nth-child(2) {
width: 310px;
border-left: 1px solid #eee;
border-right: 1px solid #eee;
padding-left: 4px;
padding-top: 6px;
margin-left: -3px;
.disabled-job {
background-color: #cccccc;
}
}
.job-in-group {
padding-left: 10px;
}
}
.button-container {
display: inline-block;
vertical-align: top;
padding-right: 2px;
}
.gant-bar {
background-color: #79adda;
height: 14px;
border-radius: 2px;
}
// SASSTODO: This needs to be rebuilt
.gant-bar-running {
background-image:-webkit-gradient(linear,
0 100%, 100% 0,
color-stop(0.25, rgba(255, 255, 255, 0.15)),
color-stop(0.25, transparent),
color-stop(0.5, transparent),
color-stop(0.5, rgba(255, 255, 255, 0.15)),
color-stop(0.75, rgba(255, 255, 255, 0.15)),
color-stop(0.75, transparent),
to(transparent));
background-image:-webkit-linear-gradient(45deg,
rgba(255, 255, 255, 0.15) 25%,
transparent 25%, transparent 50%,
rgba(255, 255, 255, 0.15) 50%,
rgba(255, 255, 255, 0.15) 75%,
transparent 75%,
transparent);
background-image:-moz-linear-gradient(45deg,
rgba(255, 255, 255, 0.15) 25%,
transparent 25%,
transparent 50%,
rgba(255, 255, 255, 0.15) 50%,
rgba(255, 255, 255, 0.15) 75%,
transparent 75%,
transparent);
background-image:-o-linear-gradient(45deg,
rgba(255, 255, 255, 0.15) 25%,
transparent 25%,
transparent 50%,
rgba(255, 255, 255, 0.15) 50%,
rgba(255, 255, 255, 0.15) 75%,
transparent 75%,
transparent);
background-image:linear-gradient(45deg,
rgba(255, 255, 255, 0.15) 25%,
transparent 25%,
transparent 50%,
rgba(255, 255, 255, 0.15) 50%,
rgba(255, 255, 255, 0.15) 75%,
transparent 75%,
transparent);
-webkit-background-size:40px 40px;
-moz-background-size:40px 40px;
-o-background-size:40px 40px;
background-size:40px 40px;
-webkit-animation:progress-bar-stripes 2s linear infinite;
-moz-animation:progress-bar-stripes 2s linear infinite;
-ms-animation:progress-bar-stripes 2s linear infinite;
-o-animation:progress-bar-stripes 2s linear infinite;
animation:progress-bar-stripes 2s linear infinite;
}
.gant-back-edge {
height: 18px;
border-left: 1px solid #d6d6d6;
border-right: 1px solid #d6d6d6;
margin-bottom: -16px;
padding-top: 9px;
// SASSTODO: Needs a poper selector
div {
height: 1px;
border-top: 1px dashed #d6d6d6;
}
}
}
.job-row:hover {
.job-row-inner {
// SASSTODO: Needs a poper selector
& > label > div {
background-color: #f2f2f2;
}
}
}
}
}
.ml-select-list-on {
color: #ffffff;
background-color: #000099;
}
.ml-select-list-on {
color: #ffffff;
background-color: #000099;
}
.ml-job-select-btn-container {
.popover {
min-width: 640px;
max-width: 660px !important;
left: 9px !important;
.arrow {
left: 150px !important;
}
}
.ml-job-select-btn-label {
background-color:#9c9c9c;
color: #ffffff;
display:inline-block;
padding:0px 10px;
border: 0px solid #ecf0f1;
border-top-style: solid;
border-right-style: none;
border-bottom-style: solid;
border-left-style: solid;
border-top-left-radius: 4px;
border-bottom-left-radius: 4px;
height: 34px;
line-height:33px;
font-size: 12px;
}
.ml-job-select-btn {
width: 300px;
height: 34px;
text-align: left;
color: #444444;
background-color: white;
border: 1px solid #ffffff;
border-left-width: 0px;
border-radius: 0px 4px 4px 0px;
padding: 6px 16px;
padding-left: 10px;
transition: border-color ease-in-out .15s;
font-size: 14px;
line-height: 20px;
cursor: pointer;
.ml-job-select-btn-text {
float: left;
width: 260px;
overflow-x: hidden;
display: inline-flex;
white-space: nowrap;
span {
overflow-x: hidden;
text-overflow: ellipsis;
padding-right: 5px;
}
}
.caret {
float: right;
margin-top: 6px;
display:inline-block;
}
}
.ml-job-select-btn:hover, .ml-job-select-btn:focus, .ml-job-select-btn:active {
color: #444444;
background-color: #ffffff;
border-color: #0079a5;
border-left-width: 1px;
padding-left: 9px;
transform: none;
}
.ml-job-select-btn:focus {
outline: none;
box-shadow: none;
}
}

View file

@ -1,12 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import './job_select_list_directive';
import './job_select_button_directive.js';
import './job_select_service.js';

View file

@ -1,27 +0,0 @@
<div style='display: flex;' class="ml-job-select-btn-container">
<div
class="ml-job-select-btn-label"
i18n-id="xpack.ml.jobSelectButton.jobTitle"
i18n-default-message="Job"
></div>
<div style="flex:0 0 auto;">
<div popover-placement="bottom"
popover-placement="bottom"
popover-html-unsafe="{{unsafeHtml}}"
popover-append-to-body="false"
popover-trigger="click"
class="ml-job-select-btn"
tooltip="{{ 'xpack.ml.jobSelectButton.jobSelectionMenuButtonTooltip' | i18n: {
defaultMessage: '{description} selected',
values: {
description: description.txt,
}
} }}"
aria-label="{{:: 'xpack.ml.jobSelectButton.jobSelectionMenuButtonAriaLabel' | i18n: { defaultMessage: 'Job selection menu' } }}"
ng-click='createMenu()'
kbn-accessible-click>
<span class="ml-job-select-btn-text">{{description.txt}}</span>
<span class="caret"></span>
</div>
</div>
</div>

View file

@ -1,53 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
/*
* ml-job-select-list directive for rendering a multi-select control for selecting
* one or more jobs from the list of configured jobs.
*/
import template from './job_select_button.html';
import 'ui/accessibility/kbn_accessible_click';
import { uiModules } from 'ui/modules';
const module = uiModules.get('apps/ml');
import { JobSelectServiceProvider } from 'plugins/ml/components/job_select_list/job_select_service';
module.directive('jobSelectButton', function (Private) {
const mlJobSelectService = Private(JobSelectServiceProvider);
function link(scope) {
scope.selectJobBtnJobIdLabel = '';
scope.unsafeHtml = '';
scope.description = scope.singleSelection ? mlJobSelectService.singleJobDescription : mlJobSelectService.description;
scope.createMenu = function () {
let txt = '<ml-job-select-list ';
if (scope.timeseriesonly) {
txt += 'timeseriesonly="true" ';
}
if (scope.singleSelection) {
txt += 'single-selection="true" ';
}
txt += '></ml-job-select-list>';
scope.unsafeHtml = txt;
};
}
return {
scope: {
timeseriesonly: '=',
singleSelection: '='
},
link,
replace: true,
template
};
});

View file

@ -1,147 +0,0 @@
<div class='ml-job-selector'>
<div
class="header"
i18n-id="xpack.ml.jobSelectList.jobSelectionTitle"
i18n-default-message="Job Selection"
></div>
<ml-loading-indicator
label="{{ ::'xpack.ml.jobSelectList.loadingJobsLabel' | i18n: { defaultMessage: 'Loading jobs'} }}"
is-loading="noJobsCreated===undefined"
/>
<div class="select-list" ng-show='noJobsCreated!==undefined'>
<div ng-if="groups.length && singleSelection !== true">
<div class="list-section-title">
<input
ng-if="singleSelection !== true"
type="checkbox"
ng-checked="allGroupsSelected"
aria-label="{{ 'xpack.ml.jobSelectList.selectAllGroupsCheckboxAriaLabel' | i18n: {
defaultMessage: 'Select all groups checkbox. Total count {selectedGroupsLength}.',
values: { selectedGroupsLength: selected.groups.length }
} }}"
ng-click="toggleAllGroupsSelection()" />
{{ ::'xpack.ml.jobSelectList.groupsTitle' | i18n: { defaultMessage: 'Groups' } }}
</div>
<div class="list-section">
<div class='group' ng-repeat="group in selected.groups">
<div class='group-title job-row'>
<div class="job-row-inner">
<label class="kuiFormLabel">
<div >
<input
aria-label="{{ 'xpack.ml.jobSelectList.groupIdAriaLabel' | i18n: {
defaultMessage: 'Group ID {groupId}',
values: { groupId: group.id }
} }}"
ng-if="singleSelection !== true && group.selectable === true" type="checkbox"
ng-model="group.selected"
ng-click="toggleGroupSelection()" />
{{group.id}}
</div>
<div ng-if="group.selectable !== false">
<div class='gant-back-edge'>
<div></div>
</div>
<div
class='gant-bar'
ng-class="{'disabled-job': job.disabled, 'gant-bar-running': job.running}"
tooltip='{{group.timeRange.label}}'
tooltip-placement="bottom"
tooltip-append-to-body="true"
ng-attr-style='margin-left:{{group.timeRange.fromPx}}px; width: {{group.timeRange.widthPx}}px;'>
</div>
</div>
</label>
</div>
</div>
</div>
</div>
</div>
<div class="list-section-title" ng-if="singleSelection !== true">
<input type="checkbox"
ng-checked="allJobsSelected"
aria-label="{{ 'xpack.ml.jobSelectList.selectAllJobsCheckboxAriaLabel' | i18n: {
defaultMessage: 'Select all jobs checkbox. Total count {selectedJobsLength}.',
values: { selectedJobsLength: selected.jobs.length }
} }}"
ng-click="toggleAllJobsSelection()" />
{{ ::'xpack.ml.jobSelectList.jobsTitle' | i18n: { defaultMessage: 'Jobs' } }}
</div>
<div class="list-section" ng-class="{'list-section-single': singleSelection}">
<div class='job-row' ng-repeat="job in selected.jobs">
<div class='job-row-inner'>
<label class="kuiFormLabel">
<div>
<span>
<input ng-if="singleSelection !== true" type="checkbox"
ng-model="job.selected"
ng-disabled='job.disabled'
ng-click="toggleSelection()" />
<input ng-if="singleSelection === true" type="radio"
name="job-group"
value="{{job.id}}"
ng-model="$parent.$parent.selectedJobRadio"
ng-disabled='job.disabled' />
<span
ng-class="{'disabled-job': job.disabled}"
aria-label="{{ 'xpack.ml.jobSelectList.jobIdAriaLabel' | i18n: {
defaultMessage: 'Job ID {jobId}',
values: { jobId: job.id }
} }}"
>{{job.id}}</span>
</span>
</div>
<div>
<div class='gant-back-edge' >
<div ></div>
</div>
<div
class='gant-bar'
aria-label="{{ 'xpack.ml.jobSelectList.timeRangeAriaLabel' | i18n: {
defaultMessage: 'time range {jobTimeRangeLabel}',
values: { jobTimeRangeLabel: job.timeRange.label }
} }}"
ng-class="{'disabled-job': job.disabled, 'gant-bar-running': job.running}"
tooltip='{{job.timeRange.label}}'
tooltip-placement="bottom"
tooltip-append-to-body="true"
ng-attr-style='margin-left:{{job.timeRange.fromPx}}px; width: {{job.timeRange.widthPx}}px;'>
</div>
</div>
</label>
</div>
</div>
</div>
</div>
<div class='footer'>
<button
ng-click="apply()"
class="kuiButton kuiButton--primary"
aria-label="{{ ::'xpack.ml.jobSelectList.applyButtonAriaLabel' | i18n: { defaultMessage: 'Apply'} }}"
ng-disabled="selectedCount===0"
i18n-id="xpack.ml.jobSelectList.applyButtonLabel"
i18n-default-message="Apply"
></button>
<button
ng-click="closePopover()"
class="kuiButton kuiButton--primary"
aria-label="{{ ::'xpack.ml.jobSelectList.cancelButtonAriaLabel' | i18n: { defaultMessage: 'Cancel'} }}"
i18n-id="xpack.ml.jobSelectList.cancelButtonLabel"
i18n-default-message="Cancel"
></button>
</div>
<div class='apply-time-range'>
<label class="kuiFormLabel">
<input type="checkbox"
ng-model="applyTimeRange"/>
{{ ::'xpack.ml.jobSelectList.applyTimeRangeLabel' | i18n: { defaultMessage: 'Also apply time range' } }}
</label>
</div>
</div>

View file

@ -1,407 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
/*
* ml-job-select-list directive for rendering a multi-select control for selecting
* one or more jobs from the list of configured jobs.
*/
import _ from 'lodash';
import $ from 'jquery';
import moment from 'moment';
import d3 from 'd3';
import template from './job_select_list.html';
import { isTimeSeriesViewJob } from 'plugins/ml/../common/util/job_utils';
import { mlJobService } from 'plugins/ml/services/job_service';
import { JobSelectServiceProvider } from 'plugins/ml/components/job_select_list/job_select_service';
import { timefilter } from 'ui/timefilter';
import { uiModules } from 'ui/modules';
const module = uiModules.get('apps/ml');
module.directive('mlJobSelectList', function (Private) {
return {
restrict: 'AE',
replace: true,
transclude: true,
template,
controller: function ($scope, i18n) {
const mlJobSelectService = Private(JobSelectServiceProvider);
$scope.jobs = [];
$scope.groups = [];
$scope.homelessJobs = [];
$scope.singleSelection = false;
$scope.timeSeriesOnly = false;
$scope.noJobsCreated = undefined;
$scope.applyTimeRange = mlJobSelectService.jobSelectListState.applyTimeRange;
$scope.urlSelectedIds = {};
$scope.selected = {};
$scope.allGroupsSelected = false;
$scope.allJobsSelected = false;
$scope.selectedJobRadio = '';
$scope.selectedCount = 0;
mlJobService.loadJobs()
.then((resp) => {
if (resp.jobs.length > 0) {
$scope.noJobsCreated = false;
const jobs = [];
resp.jobs.forEach(job => {
if (job.groups && job.groups.length) {
job.groups.forEach(group => {
jobs.push(createJob(`${group}.${job.job_id}`, group, job));
});
} else {
jobs.push(createJob(job.job_id, null, job));
}
});
normalizeTimes(jobs);
$scope.jobs = jobs;
const { groups, homeless } = createGroups($scope.jobs);
$scope.groups = groups;
$scope.homelessJobs = homeless;
$scope.selected = {
groups: [],
jobs: []
};
// count all jobs, including duplicates in groups.
// if it's the same as the number of ids passed in, tick all jobs
const jobCount = resp.jobs.reduce((sum, job) => (sum + ((job.groups === undefined) ? 1 : job.groups.length)), 0);
const selectAll = (jobCount === $scope.urlSelectedIds.jobs.length);
// create the groups and jobs which are used in the menu
groups.forEach(group => {
$scope.selected.groups.push({
id: group.id,
selected: group.selected,
// TODO: is the selectable property of a group still needed?
selectable: group.selectable,
timeRange: group.timeRange,
isGroup: true,
});
});
jobs.forEach(job => {
if ($scope.selected.jobs.find(j => j.id === job.name) === undefined) {
$scope.selected.jobs.push({
id: job.name,
selected: selectAll || job.selected,
disabled: job.disabled,
timeRange: job.timeRange,
running: job.running,
isGroup: false
});
}
});
$scope.allJobsSelected = areAllJobsSelected();
$scope.allGroupsSelected = areAllGroupsSelected();
createSelectedCount();
// if in single selection mode, set the radio button controller ($scope.selectedJobRadio)
// to the selected job id
if ($scope.singleSelection === true) {
$scope.jobs.forEach(j => {
if (j.selected) {
$scope.selectedJobRadio = j.name;
}
});
}
} else {
$scope.noJobsCreated = true;
}
$scope.$applyAsync();
}).catch((resp) => {
console.log('mlJobSelectList controller - error getting job info from ES:', resp);
});
function createJob(jobId, groupId, job) {
return {
id: jobId,
name: job.job_id,
group: groupId,
isGroup: false,
selected: _.includes($scope.urlSelectedIds.jobs, job.job_id),
disabled: !($scope.timeSeriesOnly === false || isTimeSeriesViewJob(job) === true),
running: (job.datafeed_config && job.datafeed_config.state === 'started'),
timeRange: {
to: job.data_counts.latest_record_timestamp,
from: job.data_counts.earliest_record_timestamp,
fromPx: 0,
toPx: 0,
widthPx: 0,
label: ''
}
};
}
function createGroups(jobsIn) {
const jobGroups = {};
const homeless = [];
// first pull all of the groups out of all of the jobs
// keeping homeless (groupless) jobs in a separate list
jobsIn.forEach(job => {
if (job.group !== null) {
if (jobGroups[job.group] === undefined) {
jobGroups[job.group] = [job];
} else {
jobGroups[job.group].push(job);
}
} else {
homeless.push(job);
}
});
const groups = _.map(jobGroups, (jobs, id) => {
const group = {
id,
selected: false,
selectable: true,
expanded: false,
isGroup: true,
jobs
};
// check to see whether all of the groups jobs have been selected,
// if they have, select the group
if ($scope.singleSelection === false) {
group.selected = _.includes($scope.urlSelectedIds.groups, id);
}
// create an over all time range for the group
const timeRange = {
to: null,
toMoment: null,
from: null,
fromMoment: null,
fromPx: null,
toPx: null,
widthPx: null,
};
jobs.forEach(job => {
job.group = group;
if (timeRange.to === null || job.timeRange.to > timeRange.to) {
timeRange.to = job.timeRange.to;
timeRange.toMoment = job.timeRange.toMoment;
}
if (timeRange.from === null || job.timeRange.from < timeRange.from) {
timeRange.from = job.timeRange.from;
timeRange.fromMoment = job.timeRange.fromMoment;
}
if (timeRange.toPx === null || job.timeRange.toPx > timeRange.toPx) {
timeRange.toPx = job.timeRange.toPx;
}
if (timeRange.fromPx === null || job.timeRange.fromPx < timeRange.fromPx) {
timeRange.fromPx = job.timeRange.fromPx;
}
});
timeRange.widthPx = timeRange.toPx - timeRange.fromPx;
timeRange.toMoment = moment(timeRange.to);
timeRange.fromMoment = moment(timeRange.from);
const fromString = timeRange.fromMoment.format('MMM Do YYYY, HH:mm');
const toString = timeRange.toMoment.format('MMM Do YYYY, HH:mm');
timeRange.label = i18n('xpack.ml.jobSelectList.groupTimeRangeLabel', {
defaultMessage: '{fromString} to {toString}',
values: {
fromString,
toString,
}
});
group.timeRange = timeRange;
return group;
});
return {
groups,
homeless
};
}
// apply the selected jobs
$scope.apply = function () {
// if in single selection mode, get the job id from $scope.selectedJobRadio
const selectedJobs = [];
if ($scope.singleSelection) {
selectedJobs.push(...$scope.selected.jobs.filter(j => j.id === $scope.selectedJobRadio));
} else {
selectedJobs.push(...$scope.selected.jobs.filter(j => j.selected));
selectedJobs.push(...$scope.selected.groups.filter(g => g.selected));
}
if (areAllJobsSelected()) {
// if all jobs have been selected, just store '*' in the url
mlJobSelectService.setJobIds(['*']);
} else {
const jobIds = selectedJobs.map(j => (j.isGroup ? `${j.id}.*` : j.id));
mlJobSelectService.setJobIds(jobIds);
}
// if the apply time range checkbox is ticked,
// find the min and max times for all selected jobs
// and apply them to the timefilter
if ($scope.applyTimeRange) {
const times = [];
selectedJobs.forEach(job => {
if (job.timeRange.from !== undefined) {
times.push(job.timeRange.from);
}
if (job.timeRange.to !== undefined) {
times.push(job.timeRange.to);
}
});
if (times.length) {
const min = _.min(times);
const max = _.max(times);
timefilter.setTime({
from: moment(min).toISOString(),
to: moment(max).toISOString()
});
}
}
mlJobSelectService.jobSelectListState.applyTimeRange = $scope.applyTimeRange;
$scope.closePopover();
};
// ticking a job
$scope.toggleSelection = function () {
// check to see if all jobs are now selected
$scope.allJobsSelected = areAllJobsSelected();
$scope.allGroupsSelected = areAllGroupsSelected();
createSelectedCount();
};
// ticking the all jobs checkbox
$scope.toggleAllJobsSelection = function () {
const allJobsSelected = areAllJobsSelected();
$scope.allJobsSelected = !allJobsSelected;
$scope.selected.jobs.forEach(job => {
job.selected = $scope.allJobsSelected;
});
createSelectedCount();
};
// ticking a group
$scope.toggleGroupSelection = function () {
$scope.allGroupsSelected = areAllGroupsSelected();
createSelectedCount();
};
// ticking the all jobs checkbox
$scope.toggleAllGroupsSelection = function () {
const allGroupsSelected = areAllGroupsSelected();
$scope.allGroupsSelected = !allGroupsSelected;
$scope.selected.groups.forEach(group => {
group.selected = $scope.allGroupsSelected;
});
createSelectedCount();
};
// check to see whether all jobs in the list have been selected
function areAllJobsSelected() {
let allSelected = true;
$scope.selected.jobs.forEach(job => {
if (job.selected === false) {
allSelected = false;
}
});
return allSelected;
}
// check to see whether all groups in the list have been selected
function areAllGroupsSelected() {
let allSelected = true;
$scope.selected.groups.forEach(group => {
if (group.selected === false) {
allSelected = false;
}
});
return allSelected;
}
function createSelectedCount() {
$scope.selectedCount = 0;
$scope.selected.jobs.forEach(job => {
if (job.selected) {
$scope.selectedCount++;
}
});
$scope.selected.groups.forEach(group => {
if (group.selected) {
$scope.selectedCount++;
}
});
}
// create the data used for the gant charts
function normalizeTimes(jobs) {
const min = _.min(jobs, job => +job.timeRange.from);
const max = _.max(jobs, job => +job.timeRange.to);
const gantScale = d3.scale.linear().domain([min.timeRange.from, max.timeRange.to]).range([1, 299]);
jobs.forEach(job => {
if (job.timeRange.to !== undefined && job.timeRange.from !== undefined) {
job.timeRange.fromPx = gantScale(job.timeRange.from);
job.timeRange.toPx = gantScale(job.timeRange.to);
job.timeRange.widthPx = job.timeRange.toPx - job.timeRange.fromPx;
job.timeRange.toMoment = moment(job.timeRange.to);
job.timeRange.fromMoment = moment(job.timeRange.from);
const fromString = job.timeRange.fromMoment.format('MMM Do YYYY, HH:mm');
const toString = job.timeRange.toMoment.format('MMM Do YYYY, HH:mm');
job.timeRange.label = i18n('xpack.ml.jobSelectList.jobTimeRangeLabel', {
defaultMessage: '{fromString} to {toString}',
values: {
fromString,
toString,
}
});
}
});
}
$scope.useTimeRange = function (job) {
timefilter.setTime({
from: job.timeRange.fromMoment.toISOString(),
to: job.timeRange.toMoment.toISOString()
});
};
},
link: function (scope, element, attrs) {
const mlJobSelectService = Private(JobSelectServiceProvider);
scope.timeSeriesOnly = false;
if (attrs.timeseriesonly === 'true') {
scope.timeSeriesOnly = true;
}
if (attrs.singleSelection === 'true') {
scope.singleSelection = true;
}
// Make a copy of the list of jobs ids
// '*' is passed to indicate 'All jobs'.
scope.urlSelectedIds = {
groups: [...mlJobSelectService.groupIds],
jobs: [...mlJobSelectService.jobIdsWithGroup],
};
// Giving the parent div focus fixes checkbox tick UI selection on IE.
$('.ml-select-list', element).focus();
}
};
});

View file

@ -1,310 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
// Service with functions used for broadcasting job picker changes
import _ from 'lodash';
import { toastNotifications } from 'ui/notify';
import { mlJobService } from 'plugins/ml/services/job_service';
let jobSelectService = undefined;
export function JobSelectServiceProvider($rootScope, globalState, i18n) {
function checkGlobalState() {
if (globalState.ml === undefined) {
globalState.ml = {};
globalState.save();
}
}
checkGlobalState();
function loadJobIdsFromGlobalState() {
const jobIds = [];
if (globalState.ml && globalState.ml.jobIds) {
let tempJobIds = [];
if (typeof globalState.ml.jobIds === 'string') {
tempJobIds.push(globalState.ml.jobIds);
} else {
tempJobIds = globalState.ml.jobIds;
}
tempJobIds = tempJobIds.map(id => String(id));
const invalidIds = getInvalidJobIds(removeGroupIds(tempJobIds));
warnAboutInvalidJobIds(invalidIds);
let validIds = _.difference(tempJobIds, invalidIds);
// if there are no valid ids, warn and then select the first job
if (validIds.length === 0) {
toastNotifications.addWarning(i18n('xpack.ml.jobSelect.noJobsSelectedWarningMessage', {
defaultMessage: 'No jobs selected, auto selecting first job',
}));
if (mlJobService.jobs.length) {
validIds = [mlJobService.jobs[0].job_id];
}
}
jobIds.push(...validIds);
// replace the job ids in the url with the ones which are valid
storeJobIdsInGlobalState(jobIds);
} else {
checkGlobalState();
// no jobs selected, use the first in the list
if (mlJobService.jobs.length) {
jobIds.push(mlJobService.jobs[0].job_id);
}
storeJobIdsInGlobalState(jobIds);
}
return jobIds;
}
function storeJobIdsInGlobalState(jobIds) {
globalState.ml.jobIds = jobIds;
globalState.save();
}
// check that the ids read from the url exist by comparing them to the
// jobs loaded via mlJobsService.
function getInvalidJobIds(ids) {
return ids.filter(id => {
const job = _.find(mlJobService.jobs, { 'job_id': id });
return (job === undefined && id !== '*');
});
}
function removeGroupIds(jobIds) {
return jobIds.map(id => {
const splitId = id.split('.');
return (splitId.length > 1) ? splitId[1] : splitId[0];
});
}
function warnAboutInvalidJobIds(invalidIds) {
if (invalidIds.length > 0) {
toastNotifications.addWarning(i18n('xpack.ml.jobSelect.requestedJobsDoesNotExistWarningMessage', {
defaultMessage: `Requested
{invalidIdsLength, plural, one {job {invalidIds} does not exist} other {jobs {invalidIds} do not exist}}`,
values: {
invalidIdsLength: invalidIds.length,
invalidIds,
}
}));
}
}
function createDescription(jobs) {
let txt = '';
// add up the number of jobs including duplicates if they belong to multiple groups
const jobCount = mlJobService.jobs.length;
// add up how many jobs belong to groups and how many don't
const selectedGroupJobs = [];
const groupCounts = {};
let groupLessJobs = 0;
const splitJobs = jobs.map(job => {
const obj = splitJobId(job);
if (obj.group) {
// keep track of selected jobs from group selection
selectedGroupJobs.push(obj.job);
}
return obj;
});
splitJobs.forEach(jobObj => {
if (jobObj.group) {
groupCounts[jobObj.group] = (groupCounts[jobObj.group] || 0) + 1;
} else {
// if job has already been included via group selection don't add as groupless job
if (selectedGroupJobs.includes(jobObj.job) === false) {
groupLessJobs++;
}
}
});
// All jobs have been selected
if ((_.uniq(selectedGroupJobs).length + groupLessJobs) === jobCount) {
txt = i18n('xpack.ml.jobSelect.allJobsDescription', {
defaultMessage: 'All jobs',
});
} else {
const wholeGroups = [];
const groups = mlJobService.getJobGroups();
// work out how many groups have all of their jobs selected
groups.forEach(group => {
const groupCount = groupCounts[group.id];
if (groupCount !== undefined && groupCount === group.jobs.length) {
// this group has all of it's jobs selected
wholeGroups.push(group.id);
} else {
if (groupCount !== undefined) {
// this job doesn't so add it to the count of groupless jobs
groupLessJobs += groupCount;
}
}
});
// show the whole groups first
if (wholeGroups.length) {
txt = wholeGroups[0];
if (wholeGroups.length > 1 || groupLessJobs > 0) {
const total = (wholeGroups.length - 1) + groupLessJobs;
txt = i18n('xpack.ml.jobSelect.wholeGroupDescription', {
defaultMessage: `{wholeGroup} (with {count, plural, zero {# job} one {# job} other {# jobs}}) and
{total, plural, zero {# other} one {# other} other {# others}}`,
values: {
count: groupCounts[wholeGroups[0]],
wholeGroup: wholeGroups[0],
total,
}
});
}
} else {
// otherwise just list the job ids
txt = splitJobId(jobs[0]).job;
if (jobs.length > 1) {
txt = i18n('xpack.ml.jobSelect.jobDescription', {
defaultMessage: '{jobId} and {jobsAmount, plural, zero {# other} one {# other} other {# others}}',
values: {
jobId: splitJobId(jobs[0]).job,
jobsAmount: jobs.length - 1,
}
});
}
}
}
return txt;
}
// function to split the group from the job and return both or just the job
function splitJobId(jobId) {
let obj = {};
const splitId = jobId.split('.');
if (splitId.length === 2) {
obj = { group: splitId[0], job: splitId[1] };
} else {
obj = { job: jobId };
}
return obj;
}
this.splitJobId = splitJobId;
// expands `*` into groupId.jobId list
// expands `groupId.*` into `groupId.jobId` list
// returns list of expanded job ids
function expandGroups(jobIds) {
const newJobIds = [];
const groups = mlJobService.getJobGroups();
jobIds.forEach(jobId => {
if (jobId === '*') {
mlJobService.jobs.forEach(job => {
if (job.groups === undefined) {
newJobIds.push(job.job_id);
} else {
newJobIds.push(...job.groups.map(g => `${g}.${job.job_id}`));
}
});
} else {
const splitId = splitJobId(jobId);
if (splitId.group !== undefined && splitId.job === '*') {
const groupId = splitId.group;
const group = groups.find(g => g.id === groupId);
group.jobs.forEach(j => {
newJobIds.push(`${groupId}.${j.job_id}`);
});
}
else {
newJobIds.push(jobId);
}
}
});
return newJobIds;
}
function getGroupIds(jobIds) {
const groupIds = [];
jobIds.forEach(jobId => {
const splitId = splitJobId(jobId);
if (splitId.group !== undefined && splitId.job === '*') {
groupIds.push(splitId.group);
}
});
return groupIds;
}
// takes an array of ids.
// this could be a mixture of job ids, group ids or a *.
// stores an expanded list of job ids (i.e. groupId.jobId) and a list of jobs ids only.
// creates the description text used on the job picker button.
function processIds(service, ids) {
const expandedJobIds = expandGroups(ids);
service.jobIdsWithGroup.length = 0;
service.jobIdsWithGroup.push(...expandedJobIds);
service.groupIds = getGroupIds(ids);
service.jobIds.length = 0;
service.jobIds.push(...removeGroupIds(expandedJobIds));
service.description.txt = createDescription(service.jobIdsWithGroup);
service.singleJobDescription.txt = ids[0];
setBrowserTitle(service.description.txt);
}
// display the job id in the tab title
function setBrowserTitle(title) {
document.title = `${title} - Kibana`;
}
class JobSelectService {
constructor() {
this.jobIds = [];
this.groupIds = [];
this.description = { txt: '' };
this.singleJobDescription = { txt: '' };
this.jobSelectListState = {
applyTimeRange: true
};
this.jobIdsWithGroup = [];
this.splitJobId = splitJobId;
}
// Broadcasts that a change has been made to the selected jobs.
broadcastJobSelectionChange() {
$rootScope.$broadcast('jobSelectionChange', this.getSelectedJobIds());
}
// Add a listener for changes to the selected jobs.
listenJobSelectionChange(scope, callback) {
const handler = $rootScope.$on('jobSelectionChange', callback);
scope.$on('$destroy', handler);
}
// called externally to retrieve the selected jobs ids.
// passing in `true` will load the jobs ids from the URL first
getSelectedJobIds(loadFromURL) {
if (loadFromURL) {
processIds(this, loadJobIdsFromGlobalState());
}
return this.jobIds;
}
// called externally to set the job ids.
// job ids are added to the URL and an event is broadcast for anything listening.
// e.g. the anomaly explorer or time series explorer.
// currently only called by the jobs selection menu.
setJobIds(jobIds) {
processIds(this, jobIds);
storeJobIdsInGlobalState(jobIds);
this.broadcastJobSelectionChange();
}
}
if (jobSelectService === undefined) {
jobSelectService = new JobSelectService();
}
return jobSelectService;
}

View file

@ -1,82 +0,0 @@
/* eslint-disable @kbn/eslint/require-license-header */
/**
* @notice
*
* This product includes code that was extracted from angular-ui-bootstrap@0.13.1
* which is available under an "MIT" license
*
* The MIT License
*
* Copyright (c) 2012-2016 the AngularUI Team, http://angular-ui.github.io/bootstrap/
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
// This file contains a section of code taken from angular-ui-bootstrap@0.13.1
// and adds it to kibana's included version of 0.12.1
// It adds the ability to allow html to be used as the content of the popover component
import 'ui/angular-bootstrap';
import { uiModules } from 'ui/modules';
const module = uiModules.get('apps/ml');
module
.directive('popover', [ '$tooltip', function ($tooltip) {
return $tooltip('popover', 'popover', 'click');
}])
.directive('popoverHtmlUnsafePopup', function ($compile) {
let template = '<div class="popover {{placement}}" ng-class="{ in: isOpen(), fade: animation() }">';
template += '<div class="arrow"></div>';
template += '<div class="popover-inner">';
template += '<h3 class="popover-title" bind-html-unsafe="title" ng-show="title"></h3>';
template += '<div class="popover-content" bind-html-unsafe="content" ></div>';
template += '</div></div>';
return {
restrict: 'EA',
replace: true,
scope: {
title: '@',
content: '@',
placement: '@',
animation: '&',
isOpen: '&'
},
template: template,
link: function (scope, element) {
// The content of the popup is added as a string and does not run through angular's templating system.
// therefore {{stuff}} substitutions don't happen.
// we have to manually apply the template, compile it with this scope and then set it as the html
scope.$apply();
const cont = $compile(scope.content)(scope);
element.find('.popover-content').html(cont);
// function to force the popover to close
scope.closePopover = function () {
scope.$parent.$parent.isOpen = false;
scope.$parent.$parent.$applyAsync();
element.remove();
};
}
};
})
.directive('popoverHtmlUnsafe', ['$tooltip', function ($tooltip) {
return $tooltip('popoverHtmlUnsafe', 'popover', 'click');
}]);

View file

@ -6145,30 +6145,9 @@
"xpack.ml.jobsBreadcrumbs.populationLabel": "填充",
"xpack.ml.jobsBreadcrumbs.selectIndexOrSearchLabel": "选择索引或搜索",
"xpack.ml.jobsBreadcrumbs.singleMetricLabel": "单一指标",
"xpack.ml.jobSelect.allJobsDescription": "所有作业",
"xpack.ml.jobSelect.jobDescription": "{jobId} 和 {jobsAmount, plural, zero {# 个其他作业} one {# 个其他作业} other {# 个其他作业}}",
"xpack.ml.jobSelect.noJobsSelectedWarningMessage": "未选择作业,将自动选择第一个作业",
"xpack.ml.jobSelect.requestedJobsDoesNotExistWarningMessage": "已请求\n{invalidIdsLength, plural, one { 个作业 {invalidIds} 不存在} other { 个作业 {invalidIds} 不存在}}",
"xpack.ml.jobSelect.wholeGroupDescription": "{wholeGroup}(具有 {count, plural, zero {# 个作业} one {# 个作业} other {# 个作业}})以及\n {total, plural, zero {# 个其他} one {# 个其他} other {# 个其他}}",
"xpack.ml.jobSelectButton.jobSelectionMenuButtonAriaLabel": "作业选择菜单",
"xpack.ml.jobSelectButton.jobSelectionMenuButtonTooltip": "已选择 {description}",
"xpack.ml.jobSelectButton.jobTitle": "作业",
"xpack.ml.jobSelectList.applyButtonAriaLabel": "应用",
"xpack.ml.jobSelectList.applyButtonLabel": "应用",
"xpack.ml.jobSelectList.applyTimeRangeLabel": "同时应用时间范围",
"xpack.ml.jobSelectList.cancelButtonAriaLabel": "取消",
"xpack.ml.jobSelectList.cancelButtonLabel": "取消",
"xpack.ml.jobSelectList.groupIdAriaLabel": "组 ID {groupId}",
"xpack.ml.jobSelectList.groupsTitle": "组",
"xpack.ml.jobSelectList.groupTimeRangeLabel": "{fromString} 到 {toString}",
"xpack.ml.jobSelectList.jobIdAriaLabel": "作业 ID {jobId}",
"xpack.ml.jobSelectList.jobSelectionTitle": "作业选择",
"xpack.ml.jobSelectList.jobsTitle": "作业",
"xpack.ml.jobSelectList.jobTimeRangeLabel": "{fromString} 到 {toString}",
"xpack.ml.jobSelectList.loadingJobsLabel": "正在加载作业",
"xpack.ml.jobSelectList.selectAllGroupsCheckboxAriaLabel": "选中所有组复选框。总计数 {selectedGroupsLength}。",
"xpack.ml.jobSelectList.selectAllJobsCheckboxAriaLabel": "选中所有作业复选框。总计数 {selectedJobsLength}。",
"xpack.ml.jobSelectList.timeRangeAriaLabel": "时间范围 {jobTimeRangeLabel}",
"xpack.ml.jobSelector.applyFlyoutButton": "应用",
"xpack.ml.jobSelector.applyTimerangeSwitchLabel": "应用时间范围",
"xpack.ml.jobSelector.clearAllFlyoutButton": "全部清除",