parent
519f0857c9
commit
0065fc5043
|
@ -1,6 +0,0 @@
|
|||
@import 'calendars_list/index';
|
||||
@import 'new_calendar/index';
|
||||
|
||||
@import 'components/events_list/index';
|
||||
@import 'components/import_events_modal/index';
|
||||
@import 'components/new_event_modal/index';
|
|
@ -1,29 +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 ngMock from 'ng_mock';
|
||||
import expect from 'expect.js';
|
||||
|
||||
xdescribe('ML - Calendars List Controller', () => {
|
||||
beforeEach(() => {
|
||||
ngMock.module('kibana');
|
||||
});
|
||||
|
||||
it('Initialize Calendars List Controller', (done) => {
|
||||
ngMock.inject(function ($rootScope, $controller) {
|
||||
const scope = $rootScope.$new();
|
||||
|
||||
expect(() => {
|
||||
$controller('MlCalendarsList', { $scope: scope });
|
||||
}).to.not.throwError();
|
||||
|
||||
expect(scope.permissions.canCreateCalendar).to.eql(false);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,20 +0,0 @@
|
|||
ml-calendars-list{
|
||||
font-size: $euiFontSizeS;
|
||||
|
||||
header {
|
||||
margin: $euiSize 0;
|
||||
}
|
||||
.calendars-list-container {
|
||||
width: 100%;
|
||||
margin-right: auto;
|
||||
margin-left: auto;
|
||||
padding-left: $euiSize;
|
||||
padding-right: $euiSize;
|
||||
}
|
||||
|
||||
.actions-col {
|
||||
// SASSTODO: Proper calcs
|
||||
width: 150px;
|
||||
min-width: 150px;
|
||||
}
|
||||
}
|
|
@ -1 +0,0 @@
|
|||
@import 'calendars_list';
|
|
@ -1,60 +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 { createPermissionFailureMessage } from 'plugins/ml/privilege/check_privilege';
|
||||
|
||||
export function buttonsEnabledChecks(permissions, mlNodesAvailable) {
|
||||
const NO_ML_NODES_ERROR = 'No ML nodes available';
|
||||
|
||||
function newButtonWrapperClass() {
|
||||
return (permissions.canCreateCalendar && mlNodesAvailable) ? 'button-wrapper' : ['button-wrapper', 'disabled'];
|
||||
}
|
||||
|
||||
function newButtonWrapperTooltip() {
|
||||
let tooltip = undefined;
|
||||
if (permissions.canCreateCalendar === false) {
|
||||
tooltip = createPermissionFailureMessage('canCreateCalendar');
|
||||
} else if (mlNodesAvailable === false) {
|
||||
tooltip = NO_ML_NODES_ERROR;
|
||||
}
|
||||
return tooltip;
|
||||
}
|
||||
|
||||
function newButtonDisabled() {
|
||||
return (
|
||||
(permissions.canCreateCalendar === false) || mlNodesAvailable === false
|
||||
);
|
||||
}
|
||||
|
||||
function deleteButtonWrapperClass() {
|
||||
return (permissions.canDeleteCalendar && mlNodesAvailable) ? 'button-wrapper' : ['button-wrapper', 'disabled'];
|
||||
}
|
||||
|
||||
function deleteButtonWrapperTooltip() {
|
||||
let tooltip = undefined;
|
||||
if (permissions.canDeleteCalendar === false) {
|
||||
tooltip = createPermissionFailureMessage('canDeleteCalendar');
|
||||
} else if (mlNodesAvailable === false) {
|
||||
tooltip = NO_ML_NODES_ERROR;
|
||||
}
|
||||
return tooltip;
|
||||
}
|
||||
|
||||
function deleteButtonDisabled() {
|
||||
return (permissions.canDeleteCalendar && mlNodesAvailable) === false;
|
||||
}
|
||||
|
||||
return {
|
||||
newButtonWrapperClass,
|
||||
newButtonWrapperTooltip,
|
||||
newButtonDisabled,
|
||||
deleteButtonWrapperClass,
|
||||
deleteButtonWrapperTooltip,
|
||||
deleteButtonDisabled,
|
||||
};
|
||||
}
|
|
@ -1,136 +0,0 @@
|
|||
<ml-nav-menu name="settings"></ml-nav-menu>
|
||||
<ml-calendars-list ng-controller="MlCalendarsList">
|
||||
<ml-message-bar ></ml-message-bar>
|
||||
|
||||
<div class="calendars-list-container">
|
||||
<div>
|
||||
<div class="kuiViewContent kuiViewContent--constrainedWidth">
|
||||
<div class="kuiViewContentItem kuiControlledTable kuiVerticalRhythm">
|
||||
<div class="kuiToolBar">
|
||||
<div class="kuiToolBarSearch">
|
||||
<tool-bar-search-box
|
||||
ng-disabled="true"
|
||||
filter="query"
|
||||
on-filter="onQueryChange">
|
||||
</tool-bar-search-box>
|
||||
</div>
|
||||
|
||||
<div class="kuiToolBarSection">
|
||||
<div
|
||||
ng-class="buttonChecks.newButtonWrapperClass()"
|
||||
ng-attr-tooltip="{{buttonChecks.newButtonWrapperTooltip()}}"
|
||||
>
|
||||
<button class="kuiButton kuiButton--primary"
|
||||
ng-click="newCalendarClick()"
|
||||
aria-label="Create new calendar"
|
||||
tooltip="Create new calendar"
|
||||
ng-disabled='buttonChecks.newButtonDisabled()'
|
||||
tooltip-append-to-body="true">
|
||||
<span aria-hidden="true" class="kuiButton__icon kuiIcon fa-plus"></span>
|
||||
New calendar
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="kuiToolBarSection">
|
||||
<tool-bar-pager-text
|
||||
start-item="pager.startItem"
|
||||
end-item="pager.endItem"
|
||||
total-items="pager.totalItems">
|
||||
</tool-bar-pager-text>
|
||||
<tool-bar-pager-buttons
|
||||
has-previous-page="pager.hasPreviousPage"
|
||||
has-next-page="pager.hasNextPage"
|
||||
on-page-next="onPageNext"
|
||||
on-page-previous="onPagePrevious">
|
||||
</tool-bar-pager-buttons>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<table class="kuiTable">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col" class="kuiTableHeaderCell">
|
||||
<sortable-column field="{{SORT_FIELD_LABELS.CALENDAR_ID}}" on-sort-change="onSortChange" sort-field="sortField" sort-reverse="sortReverse">
|
||||
ID
|
||||
</sortable-column>
|
||||
</th>
|
||||
<th scope="col" class="kuiTableHeaderCell">
|
||||
<sortable-column field="{{SORT_FIELD_LABELS.JOB_IDS}}" on-sort-change="onSortChange" sort-field="sortField" sort-reverse="sortReverse">
|
||||
Jobs
|
||||
</sortable-column>
|
||||
</th>
|
||||
<th scope="col" class="kuiTableHeaderCell">
|
||||
<sortable-column field="{{SORT_FIELD_LABELS.EVENT_COUNT}}" on-sort-change="onSortChange" sort-field="sortField" sort-reverse="sortReverse">
|
||||
Events
|
||||
</sortable-column>
|
||||
</th>
|
||||
<th scope="col" class="kuiTableHeaderCell actions-col"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
<tr ng-repeat="calendar in displayCalendars" class="kuiTableRow" ng-class="{ 'kuiTableRow-isBeingDeleted': calendar.isBeingDeleted }">
|
||||
|
||||
<td class="kuiTableRowCell">
|
||||
<div class="kuiTableRowCell__liner">
|
||||
<span title="{{calendar.calendar_id}}">
|
||||
{{calendar.calendar_id}}
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
|
||||
<td class="kuiTableRowCell">
|
||||
<div class="kuiTableRowCell__liner">
|
||||
<span title="{{calendar.job_ids.join(', ')}}">
|
||||
{{calendar.job_ids.join(', ')}}
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
<td class="kuiTableRowCell">
|
||||
<div class="kuiTableRowCell__liner">
|
||||
<span>
|
||||
{{calendar.events.length}} {{calendar.events.length === 1 ? 'event' : 'events'}}
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
<td class="kuiTableRowCell">
|
||||
<div class="kuiTableRowCell__liner">
|
||||
<div class="kuiMenuButtonGroup kuiMenuButtonGroup--alignRight">
|
||||
<div
|
||||
ng-class="buttonChecks.newButtonWrapperClass()"
|
||||
ng-attr-tooltip="{{buttonChecks.newButtonWrapperTooltip()}}">
|
||||
<button
|
||||
class="kuiMenuButton kuiMenuButton--basic kuiMenuButton--iconText"
|
||||
aria-label="Edit {{calendar.calendar_id}}"
|
||||
ng-disabled='buttonChecks.newButtonDisabled()'
|
||||
ng-click="editCalendarClick(calendar.calendar_id)">
|
||||
<span class="kuiMenuButton__icon kuiIcon fa-pencil"></span>
|
||||
<span>Edit</span>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
ng-class="buttonChecks.deleteButtonWrapperClass()"
|
||||
ng-attr-tooltip="{{buttonChecks.deleteButtonWrapperTooltip()}}">
|
||||
<button
|
||||
class="kuiMenuButton kuiMenuButton--danger kuiMenuButton--iconText"
|
||||
aria-label="Delete {{calendar.calendar_id}}"
|
||||
ng-disabled='buttonChecks.deleteButtonDisabled()'
|
||||
ng-click="deleteCalendarClick(calendar.calendar_id)"
|
||||
href="" >
|
||||
<span class="kuiMenuButton__icon kuiIcon fa-trash"></span>
|
||||
<span>Delete</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</ml-calendars-list>
|
|
@ -1,168 +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 _ from 'lodash';
|
||||
|
||||
import 'ui/pager_control';
|
||||
import 'ui/pager';
|
||||
import 'ui/sortable_column';
|
||||
|
||||
import uiRoutes from 'ui/routes';
|
||||
import { checkFullLicense } from 'plugins/ml/license/check_license';
|
||||
import { checkGetJobsPrivilege, checkPermission } from 'plugins/ml/privilege/check_privilege';
|
||||
import { getMlNodeCount, mlNodesAvailable } from 'plugins/ml/ml_nodes_check/check_ml_nodes';
|
||||
import { buttonsEnabledChecks } from 'plugins/ml/settings/scheduled_events/calendars_list/buttons_enabled_checks';
|
||||
import { ml } from 'plugins/ml/services/ml_api_service';
|
||||
import { initPromise } from 'plugins/ml/util/promise';
|
||||
|
||||
import template from './calendars_list.html';
|
||||
|
||||
import { timefilter } from 'ui/timefilter';
|
||||
|
||||
uiRoutes
|
||||
.when('/settings/calendars_list', {
|
||||
template,
|
||||
resolve: {
|
||||
CheckLicense: checkFullLicense,
|
||||
privileges: checkGetJobsPrivilege,
|
||||
mlNodeCount: getMlNodeCount,
|
||||
initPromise: initPromise(true)
|
||||
}
|
||||
});
|
||||
|
||||
import { uiModules } from 'ui/modules';
|
||||
const module = uiModules.get('apps/ml', ['ui.bootstrap']);
|
||||
|
||||
module.controller('MlCalendarsList',
|
||||
function (
|
||||
$scope,
|
||||
$filter,
|
||||
$route,
|
||||
$location,
|
||||
pagerFactory,
|
||||
mlConfirmModalService) {
|
||||
|
||||
$scope.permissions = {
|
||||
canCreateCalendar: checkPermission('canCreateCalendar'),
|
||||
canDeleteCalendar: checkPermission('canDeleteCalendar'),
|
||||
};
|
||||
|
||||
timefilter.disableTimeRangeSelector(); // remove time picker from top of page
|
||||
timefilter.disableAutoRefreshSelector(); // remove time picker from top of page
|
||||
const mlConfirm = mlConfirmModalService;
|
||||
|
||||
let calendars = []; // Complete list of calendars received from the ML endpoint.
|
||||
$scope.displayCalendars = []; // List of calendars being displayed after filtering and sorting.
|
||||
$scope.buttonChecks = buttonsEnabledChecks($scope.permissions, mlNodesAvailable());
|
||||
|
||||
// Create a pager object to page through long lists of calendars.
|
||||
const PAGE_SIZE = 20;
|
||||
const limitTo = $filter('limitTo');
|
||||
$scope.pager = pagerFactory.create(0, PAGE_SIZE, 1);
|
||||
|
||||
// Sort field labels, to identify column used for sorting the list.
|
||||
const SORT_FIELD_LABELS = {
|
||||
CALENDAR_ID: 'calendar_id',
|
||||
JOB_IDS: 'job_ids',
|
||||
EVENT_COUNT: 'event_count'
|
||||
};
|
||||
$scope.SORT_FIELD_LABELS = SORT_FIELD_LABELS;
|
||||
$scope.sortField = SORT_FIELD_LABELS.CALENDAR_ID;
|
||||
$scope.sortReverse = false;
|
||||
|
||||
$scope.onSortChange = function (field, reverse) {
|
||||
$scope.sortField = field;
|
||||
$scope.sortReverse = reverse;
|
||||
};
|
||||
|
||||
// Listen for changes to the search text and build Regexp.
|
||||
$scope.onQueryChange = function (query) {
|
||||
if (query) {
|
||||
$scope.filterRegexp = new RegExp(`(${query})`, 'gi');
|
||||
} else {
|
||||
$scope.filterRegexp = undefined;
|
||||
}
|
||||
};
|
||||
|
||||
$scope.onPageNext = function () {
|
||||
$scope.pager.nextPage();
|
||||
};
|
||||
|
||||
$scope.onPagePrevious = function () {
|
||||
$scope.pager.previousPage();
|
||||
};
|
||||
|
||||
$scope.$watchMulti([
|
||||
'sortField',
|
||||
'sortReverse',
|
||||
'filterRegexp',
|
||||
'pager.currentPage'
|
||||
], applyTableSettings);
|
||||
|
||||
$scope.newCalendarClick = function () {
|
||||
$location.path('settings/calendars_list/new_calendar');
|
||||
};
|
||||
|
||||
$scope.editCalendarClick = function (id) {
|
||||
$location.path(`settings/calendars_list/edit_calendar/${id}`);
|
||||
};
|
||||
|
||||
$scope.deleteCalendarClick = function (calendarId) {
|
||||
mlConfirm.open({
|
||||
message: `Confirm deletion of ${calendarId}?`,
|
||||
title: `Delete calendar`
|
||||
})
|
||||
.then(() => {
|
||||
ml.deleteCalendar({ calendarId })
|
||||
.then(loadCalendars)
|
||||
.catch((error) => {
|
||||
console.log(error);
|
||||
});
|
||||
})
|
||||
.catch(() => {});
|
||||
};
|
||||
|
||||
function loadCalendars() {
|
||||
ml.calendars()
|
||||
.then((resp) => {
|
||||
calendars = resp;
|
||||
$scope.pager = pagerFactory.create(calendars.length, PAGE_SIZE, 1);
|
||||
applyTableSettings();
|
||||
});
|
||||
}
|
||||
|
||||
function applyTableSettings() {
|
||||
// Applies the settings from the table controls (search filter and sorting).
|
||||
let filteredCalendars = _.filter(calendars, (calendar) => {
|
||||
// If search text has been entered, look for match in the calendar ID or job IDs.
|
||||
return $scope.filterRegexp === undefined ||
|
||||
calendar.calendar_id.match($scope.filterRegexp) ||
|
||||
calendar.job_ids.join(' ').match($scope.filterRegexp);
|
||||
});
|
||||
|
||||
filteredCalendars = _.sortBy(filteredCalendars, (calendar) => {
|
||||
switch ($scope.sortField) {
|
||||
case SORT_FIELD_LABELS.JOB_IDS:
|
||||
return calendar.job_ids.join();
|
||||
case SORT_FIELD_LABELS.EVENT_COUNT:
|
||||
return calendar.events.length;
|
||||
case SORT_FIELD_LABELS.CALENDAR_ID:
|
||||
default:
|
||||
return calendar[$scope.sortField];
|
||||
}
|
||||
});
|
||||
if ($scope.sortReverse === true) {
|
||||
filteredCalendars = filteredCalendars.reverse();
|
||||
}
|
||||
|
||||
$scope.displayCalendars = limitTo(filteredCalendars, $scope.pager.pageSize, $scope.pager.startIndex);
|
||||
$scope.pager.setTotalItems(filteredCalendars.length);
|
||||
}
|
||||
|
||||
loadCalendars();
|
||||
});
|
|
@ -1,9 +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 './calendars_list_controller';
|
|
@ -1,19 +0,0 @@
|
|||
.events-list {
|
||||
.actions-col {
|
||||
width: 90px;
|
||||
min-width: 90px;
|
||||
}
|
||||
.asterisk {
|
||||
color: $euiColorDanger;
|
||||
font-weight: $euiFontWeightBold;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.footer-cell {
|
||||
border-top: $euiBorderThin;
|
||||
padding: $euiSizeS;
|
||||
font-size: $euiFontSizeS;
|
||||
font-style: italic;
|
||||
text-align: right;
|
||||
}
|
||||
}
|
|
@ -1 +0,0 @@
|
|||
@import 'events_list';
|
|
@ -1,131 +0,0 @@
|
|||
<div class="events-list">
|
||||
<div
|
||||
class="kuiVerticalRhythm"
|
||||
ng-class="{'kuiControlledTable': showControls}">
|
||||
<div ng-if="showControls" class="kuiToolBar">
|
||||
|
||||
<div class="kuiToolBarSection">
|
||||
<button
|
||||
class="kuiButton kuiButton--primary"
|
||||
href=""
|
||||
aria-label="Create new event"
|
||||
tooltip="Create new event"
|
||||
tooltip-append-to-body="true"
|
||||
ng-disabled="lockControls === true"
|
||||
ng-click="clickNewEvent()">
|
||||
<span aria-hidden="true" class="kuiButton__icon kuiIcon fa-plus"></span>
|
||||
New event
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="kuiButton kuiButton--primary"
|
||||
href=""
|
||||
aria-label="Import events"
|
||||
tooltip="Import events"
|
||||
tooltip-append-to-body="true"
|
||||
ng-disabled="lockControls === true"
|
||||
ng-click="clickImportEvents()">
|
||||
<span aria-hidden="true" class="kuiButton__icon kuiIcon fa-upload"></span>
|
||||
Import events
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="kuiToolBarSection">
|
||||
<tool-bar-pager-text
|
||||
start-item="pager.startItem"
|
||||
end-item="pager.endItem"
|
||||
total-items="pager.totalItems">
|
||||
</tool-bar-pager-text>
|
||||
<tool-bar-pager-buttons
|
||||
has-previous-page="pager.hasPreviousPage"
|
||||
has-next-page="pager.hasNextPage"
|
||||
on-page-next="onPageNext"
|
||||
on-page-previous="onPagePrevious">
|
||||
</tool-bar-pager-buttons>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<table class="kuiTable" ng-show="pageOfEvents.length">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col" class="kuiTableHeaderCell">
|
||||
<sortable-column field="description" on-sort-change="onSortChange" sort-field="sortField" sort-reverse="sortReverse">
|
||||
Description
|
||||
</sortable-column>
|
||||
</th>
|
||||
<th scope="col" class="kuiTableHeaderCell">
|
||||
<sortable-column field="start_time" on-sort-change="onSortChange" sort-field="sortField" sort-reverse="sortReverse">
|
||||
Start
|
||||
</sortable-column>
|
||||
</th>
|
||||
<th scope="col" class="kuiTableHeaderCell">
|
||||
<sortable-column field="end_time" on-sort-change="onSortChange" sort-field="sortField" sort-reverse="sortReverse">
|
||||
End
|
||||
</sortable-column>
|
||||
</th>
|
||||
<th scope="col" class="kuiTableHeaderCell actions-col"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
<tr ng-repeat="event in pageOfEvents track by $index" class="kuiTableRow" ng-class="{ 'kuiTableRow-isBeingDeleted': event.isBeingDeleted }">
|
||||
|
||||
<td class="kuiTableRowCell">
|
||||
<div class="kuiTableRowCell__liner">
|
||||
<span>
|
||||
{{event.description}}
|
||||
</span>
|
||||
<span
|
||||
ng-if="event.asterisk"
|
||||
class="asterisk"
|
||||
tooltip='{{asteriskText}}'>*</span>
|
||||
</div>
|
||||
</td>
|
||||
|
||||
<td class="kuiTableRowCell">
|
||||
<div class="kuiTableRowCell__liner">
|
||||
<span>
|
||||
{{formatTime(event.start_time)}}
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
|
||||
<td class="kuiTableRowCell">
|
||||
<div class="kuiTableRowCell__liner">
|
||||
<span>
|
||||
{{formatTime(event.end_time)}}
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
<td class="kuiTableRowCell">
|
||||
<div class="kuiTableRowCell__liner">
|
||||
<div class="kuiMenuButtonGroup kuiMenuButtonGroup--alignRight">
|
||||
<button
|
||||
class="kuiMenuButton kuiMenuButton--danger kuiMenuButton--iconText"
|
||||
aria-label="Delete {{event.description}}"
|
||||
ng-disabled="lockControls === true"
|
||||
ng-click="deleteEvent(event)"
|
||||
href="">
|
||||
<span class="kuiMenuButton__icon kuiIcon fa-trash"></span>
|
||||
<span>Delete</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
|
||||
</tr>
|
||||
</tbody>
|
||||
|
||||
<tfoot>
|
||||
<tr>
|
||||
<td colspan="4" class="kuiTableHeaderCell footer-cell">
|
||||
Times are displayed in {{dateFormatTz === 'Browser' ? 'the browser' : dateFormatTz}} timezone
|
||||
</td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
|
||||
</table>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
|
@ -1,107 +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 'plugins/ml/settings/scheduled_events/components/new_event_modal';
|
||||
import 'plugins/ml/settings/scheduled_events/components/import_events_modal';
|
||||
|
||||
import template from './events_list.html';
|
||||
import moment from 'moment';
|
||||
|
||||
import { uiModules } from 'ui/modules';
|
||||
const module = uiModules.get('apps/ml');
|
||||
|
||||
module.directive('mlEventsList', function (
|
||||
$filter,
|
||||
pagerFactory,
|
||||
mlNewEventService,
|
||||
mlImportEventsService,
|
||||
config) {
|
||||
return {
|
||||
restrict: 'AE',
|
||||
replace: true,
|
||||
transclude: true,
|
||||
template,
|
||||
scope: {
|
||||
events: '=',
|
||||
showControls: '=',
|
||||
lockControls: '=',
|
||||
asteriskText: '='
|
||||
},
|
||||
controller: function ($scope) {
|
||||
|
||||
$scope.pageOfEvents = []; // Current page of events displayed in the list.
|
||||
$scope.dateFormatTz = config.get('dateFormat:tz');
|
||||
|
||||
const TIME_FORMAT = 'YYYY-MM-DD HH:mm:ss';
|
||||
const PAGE_SIZE = 20;
|
||||
|
||||
// Create objects for sorting and paging through the events.
|
||||
const orderBy = $filter('orderBy');
|
||||
const limitTo = $filter('limitTo');
|
||||
$scope.pager = pagerFactory.create($scope.events.length, PAGE_SIZE, 1);
|
||||
$scope.sortField = 'description';
|
||||
$scope.sortReverse = false;
|
||||
|
||||
$scope.onSortChange = function (field, reverse) {
|
||||
$scope.sortField = field;
|
||||
$scope.sortReverse = reverse;
|
||||
};
|
||||
|
||||
$scope.onPageNext = function () {
|
||||
$scope.pager.nextPage();
|
||||
};
|
||||
|
||||
$scope.onPagePrevious = function () {
|
||||
$scope.pager.previousPage();
|
||||
};
|
||||
|
||||
$scope.$watchMulti([
|
||||
'events',
|
||||
'sortField',
|
||||
'sortReverse',
|
||||
'pager.currentPage'
|
||||
], applyTableSettings);
|
||||
|
||||
$scope.clickNewEvent = function () {
|
||||
mlNewEventService.openNewEventWindow()
|
||||
.then((event) => {
|
||||
$scope.events.push(event);
|
||||
applyTableSettings();
|
||||
})
|
||||
.catch(() => {});
|
||||
};
|
||||
|
||||
$scope.formatTime = function (timeMs) {
|
||||
const time = moment(timeMs);
|
||||
return time.format(TIME_FORMAT);
|
||||
};
|
||||
|
||||
$scope.deleteEvent = function (eventToDelete) {
|
||||
$scope.events.splice($scope.events.indexOf(eventToDelete), 1);
|
||||
applyTableSettings();
|
||||
};
|
||||
|
||||
$scope.clickImportEvents = function () {
|
||||
mlImportEventsService.openImportEventsWindow()
|
||||
.then((events) => {
|
||||
$scope.events.push(...events);
|
||||
applyTableSettings();
|
||||
})
|
||||
.catch(() => {});
|
||||
};
|
||||
|
||||
function applyTableSettings() {
|
||||
// Apply sorting and paging to the complete list of events.
|
||||
const pageOfEvents = orderBy($scope.events, $scope.sortField, $scope.sortReverse);
|
||||
$scope.pageOfEvents = limitTo(pageOfEvents, $scope.pager.pageSize, $scope.pager.startIndex);
|
||||
$scope.pager.setTotalItems($scope.events.length);
|
||||
}
|
||||
|
||||
}
|
||||
};
|
||||
});
|
|
@ -1,9 +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 './events_list_directive';
|
|
@ -1,35 +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 ngMock from 'ng_mock';
|
||||
import expect from 'expect.js';
|
||||
|
||||
const mockModalInstance = { close: function () { }, dismiss: function () { } };
|
||||
|
||||
xdescribe('ML - Import Events Modal Controller', () => {
|
||||
beforeEach(() => {
|
||||
ngMock.module('kibana');
|
||||
});
|
||||
|
||||
it('Initialize Import Events Modal Controller', (done) => {
|
||||
ngMock.inject(function ($rootScope, $controller) {
|
||||
const scope = $rootScope.$new();
|
||||
|
||||
expect(() => {
|
||||
$controller('MlImportEventsModal', {
|
||||
$scope: scope,
|
||||
$modalInstance: mockModalInstance,
|
||||
params: {}
|
||||
});
|
||||
}).to.not.throwError();
|
||||
|
||||
expect(scope.loadingLock).to.be(false);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,13 +0,0 @@
|
|||
.import-events-modal {
|
||||
font-size: $euiFontSizeS;
|
||||
padding: $euiSizeL;
|
||||
cursor: auto;
|
||||
|
||||
.no-shadow {
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.recurring-warning {
|
||||
color: $euiColorDanger;
|
||||
}
|
||||
}
|
|
@ -1 +0,0 @@
|
|||
@import 'import_events_modal';
|
|
@ -1,91 +0,0 @@
|
|||
<div class="import-events-modal">
|
||||
<ml-message-bar ></ml-message-bar>
|
||||
<h3 class="euiTitle">Import events</h3>
|
||||
<div class="euiSpacer euiSpacer--s"></div>
|
||||
|
||||
<div>
|
||||
Import events from an ICS file
|
||||
</div>
|
||||
<div class="euiSpacer euiSpacer--m"></div>
|
||||
|
||||
<div class="import-events-container">
|
||||
|
||||
<div class="kuiFormSection no-shadow euiPanel euiPanel--paddingMedium">
|
||||
<input
|
||||
type='file'
|
||||
ng-disabled="(loadingLock === true)"
|
||||
ml-file-input-on-change="fileNameChanged"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div
|
||||
ng-if="fileLoaded===true && newEvents.length >= 0"
|
||||
class="kuiFormSection">
|
||||
<span class="kuiFormLabel">Events to import: {{newEvents.length}}</span>
|
||||
</div>
|
||||
|
||||
<div
|
||||
ng-if="error===true"
|
||||
class="kuiFormSection euiCallOut euiCallOut--danger">
|
||||
<div class="euiCallOutHeader">
|
||||
<span class="euiCallOutHeader__title">{{errorMessage}}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
ng-if="loadingLock === true"
|
||||
class="kuiFormSection">
|
||||
<ml-loading-indicator
|
||||
label="Importing events"
|
||||
is-loading="true"
|
||||
height="50"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div ng-show="fileLoaded===true && newEvents.length >= 0">
|
||||
<ml-events-list
|
||||
events='newEvents'
|
||||
show-controls='false'
|
||||
asterisk-text='RECURRING_WARNING'
|
||||
>
|
||||
</ml-events-list>
|
||||
|
||||
<div class="euiSpacer euiSpacer--m"></div>
|
||||
|
||||
<div ng-if="showRecurringWarning" class="recurring-warning">
|
||||
*{{RECURRING_WARNING}}
|
||||
<div class="euiSpacer euiSpacer--m"></div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class='kuiCheckBoxLabel kuiVerticalRhythm'>
|
||||
<input
|
||||
ng-model='includePastEvents'
|
||||
type="checkbox"
|
||||
class='kuiCheckBox'
|
||||
ng-click="createEventsList()"/>
|
||||
<span class='kuiCheckBoxLabel__text'>Include past events</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="clearfix"></div>
|
||||
|
||||
<hr class="euiHorizontalRule euiHorizontalRule--full euiHorizontalRule--marginMedium" />
|
||||
|
||||
<button
|
||||
ng-click="save()"
|
||||
ng-disabled="loadingLock === true"
|
||||
class="kuiButton kuiButton--primary" >
|
||||
Import
|
||||
</button>
|
||||
<button
|
||||
ng-click="cancel()"
|
||||
ng-disabled="loadingLock === true"
|
||||
class="kuiButton kuiButton--primary"
|
||||
aria-label="Cancel">
|
||||
Cancel
|
||||
</button>
|
||||
|
||||
</div>
|
|
@ -1,164 +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.
|
||||
*/
|
||||
|
||||
|
||||
|
||||
const icalendar = require('icalendar');
|
||||
import $ from 'jquery';
|
||||
import moment from 'moment';
|
||||
|
||||
import 'plugins/ml/settings/scheduled_events/components/events_list';
|
||||
|
||||
import { uiModules } from 'ui/modules';
|
||||
const module = uiModules.get('apps/ml');
|
||||
|
||||
module.controller('MlImportEventsModal', function (
|
||||
$scope,
|
||||
$timeout,
|
||||
$modalInstance) {
|
||||
|
||||
$scope.loadingLock = false;
|
||||
$scope.allNewEvents = [];
|
||||
$scope.newEvents = [];
|
||||
$scope.fileLoaded = false;
|
||||
$scope.file = undefined;
|
||||
$scope.includePastEvents = false;
|
||||
$scope.showRecurringWarning = false;
|
||||
$scope.RECURRING_WARNING = 'Recurring events not supported. Only the first event will be imported.';
|
||||
|
||||
$timeout(() => {
|
||||
$('.modal-dialog').width(750);
|
||||
}, 0);
|
||||
|
||||
const MAX_FILE_SIZE_MB = 100;
|
||||
// called when a file is selected using the file browser
|
||||
$scope.fileNameChanged = function (event) {
|
||||
$scope.$apply(() => {
|
||||
if (event.target.files.length) {
|
||||
reset();
|
||||
$scope.file = event.target.files[0];
|
||||
if ($scope.file.size <= (MAX_FILE_SIZE_MB * 1000000)) {
|
||||
readFile($scope.file)
|
||||
.then((resp) => {
|
||||
try {
|
||||
$scope.allNewEvents = parseICSFile(resp.data);
|
||||
$scope.createEventsList();
|
||||
$scope.fileLoaded = true;
|
||||
$scope.loadingLock = false;
|
||||
} catch (error) {
|
||||
$scope.error = true;
|
||||
$scope.errorMessage = 'Could not parse ICS file';
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error(error);
|
||||
$scope.loadingLock = false;
|
||||
});
|
||||
} else {
|
||||
$scope.fileLoaded = false;
|
||||
$scope.loadingLock = false;
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
function readFile(file) {
|
||||
return new Promise((resolve, reject) => {
|
||||
$scope.loadingLock = true;
|
||||
|
||||
if (file && file.size) {
|
||||
const reader = new FileReader();
|
||||
reader.readAsText(file);
|
||||
|
||||
reader.onload = (() => {
|
||||
return () => {
|
||||
$scope.loadingLock = false;
|
||||
const data = reader.result;
|
||||
if (data === '') {
|
||||
reject();
|
||||
} else {
|
||||
resolve({ data });
|
||||
}
|
||||
};
|
||||
})(file);
|
||||
} else {
|
||||
reject();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function reset() {
|
||||
$scope.file = undefined;
|
||||
$scope.fileLoaded = false;
|
||||
$scope.loadingLock = false;
|
||||
$scope.newEvents = [];
|
||||
$scope.error = false;
|
||||
$scope.errorMessage = '';
|
||||
}
|
||||
|
||||
function parseICSFile(data) {
|
||||
const cal = icalendar.parse_calendar(data);
|
||||
return createEvents(cal);
|
||||
}
|
||||
|
||||
function createEvents(ical) {
|
||||
const events = ical.events();
|
||||
const mlEvents = [];
|
||||
|
||||
events.forEach((e) => {
|
||||
if (e.element === 'VEVENT') {
|
||||
const description = e.properties.SUMMARY;
|
||||
const start = e.properties.DTSTART;
|
||||
const end = e.properties.DTEND;
|
||||
const recurring = (e.properties.RRULE !== undefined);
|
||||
|
||||
if (description && start && end && description.length && start.length && end.length) {
|
||||
mlEvents.push({
|
||||
description: description[0].value,
|
||||
start_time: start[0].value.valueOf(),
|
||||
end_time: end[0].value.valueOf(),
|
||||
asterisk: recurring
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
return mlEvents;
|
||||
}
|
||||
|
||||
// populate the newEvents list
|
||||
// filtering out past events if the checkbox is ticked
|
||||
$scope.createEventsList = function () {
|
||||
if ($scope.includePastEvents) {
|
||||
$scope.newEvents = [...$scope.allNewEvents];
|
||||
} else {
|
||||
const now = moment().valueOf();
|
||||
$scope.newEvents = $scope.allNewEvents.filter(e => e.start_time > now);
|
||||
}
|
||||
|
||||
$scope.showRecurringWarning = ($scope.newEvents.find(e => e.asterisk) !== undefined);
|
||||
};
|
||||
|
||||
$scope.save = function () {
|
||||
$modalInstance.close($scope.newEvents);
|
||||
reset();
|
||||
};
|
||||
|
||||
$scope.cancel = function () {
|
||||
$modalInstance.dismiss('cancel');
|
||||
};
|
||||
}).directive('mlFileInputOnChange', function () {
|
||||
return {
|
||||
restrict: 'A',
|
||||
link: function (scope, element, attrs) {
|
||||
const onChangeHandler = scope.$eval(attrs.mlFileInputOnChange);
|
||||
element.on('change', onChangeHandler);
|
||||
element.on('$destroy', () => {
|
||||
element.off();
|
||||
});
|
||||
|
||||
}
|
||||
};
|
||||
});
|
|
@ -1,31 +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 template from './import_events_modal.html';
|
||||
|
||||
import { uiModules } from 'ui/modules';
|
||||
const module = uiModules.get('apps/ml');
|
||||
|
||||
module.service('mlImportEventsService', function ($modal) {
|
||||
|
||||
this.openImportEventsWindow = function () {
|
||||
return new Promise((resolve, reject) => {
|
||||
const modal = $modal.open({
|
||||
template,
|
||||
controller: 'MlImportEventsModal',
|
||||
backdrop: 'static',
|
||||
keyboard: false
|
||||
});
|
||||
|
||||
modal.result
|
||||
.then(resolve)
|
||||
.catch(reject);
|
||||
});
|
||||
};
|
||||
|
||||
});
|
|
@ -1,10 +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 './import_events_service';
|
||||
import './import_events_modal_controller';
|
|
@ -1,35 +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 ngMock from 'ng_mock';
|
||||
import expect from 'expect.js';
|
||||
|
||||
const mockModalInstance = { close: function () { }, dismiss: function () { } };
|
||||
|
||||
xdescribe('ML - New Event Modal Controller', () => {
|
||||
beforeEach(() => {
|
||||
ngMock.module('kibana');
|
||||
});
|
||||
|
||||
it('Initialize New Event Modal Controller', (done) => {
|
||||
ngMock.inject(function ($rootScope, $controller) {
|
||||
const scope = $rootScope.$new();
|
||||
|
||||
expect(() => {
|
||||
$controller('MlNewEventModal', {
|
||||
$scope: scope,
|
||||
$modalInstance: mockModalInstance,
|
||||
params: {}
|
||||
});
|
||||
}).to.not.throwError();
|
||||
|
||||
expect(scope.event.description).to.be('');
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1 +0,0 @@
|
|||
@import 'new_event_modal';
|
|
@ -1,60 +0,0 @@
|
|||
.new-event-modal {
|
||||
font-size: $euiFontSizeS;
|
||||
padding: $euiSizeL;
|
||||
cursor: auto;
|
||||
|
||||
.date_container {
|
||||
width: 200px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.ml-new-event-contents {
|
||||
margin-top: $euiSizeXS;
|
||||
|
||||
.main-container {
|
||||
display: flex;
|
||||
.tabs-container {
|
||||
// SASSTODO: Proper selectors
|
||||
ul {
|
||||
padding: 0px;
|
||||
li {
|
||||
a {
|
||||
border-radius: $euiSizeXS 0px 0px $euiSizeXS;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.calendar-container {
|
||||
border: 2px solid $euiColorPrimary;
|
||||
flex-grow: 1;
|
||||
border-radius: $euiBorderRadius;
|
||||
|
||||
// SASSTODO: Proper calcs
|
||||
.calendar {
|
||||
width: 235px;
|
||||
margin: 20px;
|
||||
float: left;
|
||||
.calendar-input-container {
|
||||
margin-right: -33px;
|
||||
}
|
||||
.partition {
|
||||
border-right: 1px solid $euiColorPrimary;
|
||||
position: absolute;
|
||||
height: 275px;
|
||||
left: 270px;
|
||||
top: 0px;
|
||||
}
|
||||
.partition-large {
|
||||
height: 325px;
|
||||
}
|
||||
}
|
||||
}
|
||||
.top-tab-selected {
|
||||
border-radius: 0 $euiSizeXS $euiSizeXS $euiSizeXS;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,10 +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 './new_event_service';
|
||||
import './new_event_modal_controller';
|
|
@ -1,129 +0,0 @@
|
|||
<div class="new-event-modal">
|
||||
<ml-message-bar ></ml-message-bar>
|
||||
<h3 class="euiTitle">Create new event</h3>
|
||||
<div class="euiSpacer euiSpacer--s"></div>
|
||||
|
||||
<div class="ml-new-event-contents" >
|
||||
|
||||
<div class="kuiFormSection" aria-hidden="false">
|
||||
<label for="id" class="kuiFormLabel">
|
||||
Description
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
ng-disabled="isNewCalendar===false"
|
||||
id="id"
|
||||
name="id"
|
||||
ng-model="ui.description"
|
||||
ng-trim="false"
|
||||
class="kuiTextInput fullWidth"
|
||||
autofocus
|
||||
required />
|
||||
</div>
|
||||
<div class="main-container row1">
|
||||
<div class="tabs-container">
|
||||
<ul class="nav nav-pills nav-stacked">
|
||||
<li ng-class="{ active: ui.rangeType === '0' }"><a href="#" ng-click="ui.setRangeType('0', $event)"> Single day</a></li>
|
||||
<li ng-class="{ active: ui.rangeType === '1' }"><a href="#" ng-click="ui.setRangeType('1', $event)"> Day range</a></li>
|
||||
<li ng-class="{ active: ui.rangeType === '2' }"><a href="#" ng-click="ui.setRangeType('2', $event)"> Time range</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div
|
||||
class="calendar-container"
|
||||
ng-class="{ 'top-tab-selected': ui.rangeType === '0' }" >
|
||||
|
||||
<div
|
||||
ng-show="ui.rangeType === RANGE_TYPE.SINGLE_DAY"
|
||||
class="calendar">
|
||||
<datepicker
|
||||
offset-timezone
|
||||
ng-model="ui.timepicker.start"
|
||||
show-weeks="false">
|
||||
</datepicker>
|
||||
</div>
|
||||
|
||||
<div ng-show="ui.rangeType === RANGE_TYPE.DAY_RANGE">
|
||||
<div class="row">
|
||||
<div class="col-md-6 calendar">
|
||||
<label class="kuiFormLabel">
|
||||
From:
|
||||
</label>
|
||||
<datepicker
|
||||
offset-timezone
|
||||
ng-model="ui.timepicker.start"
|
||||
max-date="ui.timepicker.end"
|
||||
show-weeks="false">
|
||||
</datepicker>
|
||||
<div class="partition"></div>
|
||||
</div>
|
||||
<div class="col-md-6 calendar">
|
||||
<label class="kuiFormLabel">
|
||||
To:
|
||||
</label>
|
||||
<datepicker
|
||||
offset-timezone
|
||||
ng-model="ui.timepicker.end"
|
||||
min-date="ui.timepicker.start"
|
||||
show-weeks="false">
|
||||
</datepicker>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div ng-show="ui.rangeType === RANGE_TYPE.TIME_RANGE">
|
||||
<div class="row">
|
||||
<div class="col-md-6 calendar">
|
||||
<div class="calendar-input-container">
|
||||
<label class="kuiFormLabel">
|
||||
From:
|
||||
</label>
|
||||
<input type="text" class="form-control" input-datetime="YYYY-MM-DD HH:mm:ss" ng-model="ui.timepicker.start">
|
||||
</div>
|
||||
<datepicker
|
||||
offset-timezone
|
||||
ng-model="ui.timepicker.start"
|
||||
max-date="ui.timepicker.end"
|
||||
show-weeks="false">
|
||||
</datepicker>
|
||||
<div class="partition partition-large"></div>
|
||||
</div>
|
||||
<div class="col-md-6 calendar">
|
||||
<label class="kuiFormLabel">
|
||||
From:
|
||||
</label>
|
||||
<div class="calendar-input-container">
|
||||
<input type="text" class="form-control" input-datetime="YYYY-MM-DD HH:mm:ss" ng-model="ui.timepicker.end">
|
||||
</div>
|
||||
<datepicker
|
||||
offset-timezone
|
||||
ng-model="ui.timepicker.end"
|
||||
min-date="ui.timepicker.start"
|
||||
show-weeks="false">
|
||||
</datepicker>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="clearfix"></div>
|
||||
|
||||
<hr class="euiHorizontalRule euiHorizontalRule--full euiHorizontalRule--marginMedium" />
|
||||
|
||||
<button
|
||||
ng-click="save()"
|
||||
ng-disabled="ui.saveEnabled() === false"
|
||||
class="kuiButton kuiButton--primary" >
|
||||
Add
|
||||
</button>
|
||||
<button
|
||||
ng-click="cancel()"
|
||||
ng-disabled="(saveLock === true)"
|
||||
class="kuiButton kuiButton--primary"
|
||||
aria-label="Cancel">
|
||||
Cancel
|
||||
</button>
|
||||
|
||||
</div>
|
|
@ -1,102 +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 moment from 'moment';
|
||||
import $ from 'jquery';
|
||||
|
||||
import { uiModules } from 'ui/modules';
|
||||
const module = uiModules.get('apps/ml');
|
||||
|
||||
module.controller('MlNewEventModal', function (
|
||||
$scope,
|
||||
$modalInstance,
|
||||
$timeout) {
|
||||
|
||||
const RANGE_TYPE = {
|
||||
SINGLE_DAY: '0',
|
||||
DAY_RANGE: '1',
|
||||
TIME_RANGE: '2',
|
||||
};
|
||||
$scope.RANGE_TYPE = RANGE_TYPE;
|
||||
const MODAL_WIDTH_SMALL = 425;
|
||||
const MODAL_WIDTH_LARGE = 700;
|
||||
|
||||
$scope.event = {
|
||||
description: '',
|
||||
start: '',
|
||||
end: ''
|
||||
};
|
||||
|
||||
$scope.ui = {
|
||||
saveEnabled() {
|
||||
return ($scope.ui.description !== '' && $scope.ui.description !== undefined);
|
||||
},
|
||||
description: '',
|
||||
rangeType: RANGE_TYPE.SINGLE_DAY,
|
||||
timepicker: {
|
||||
start: moment().startOf('day'),
|
||||
end: moment().startOf('day').add(1, 'days')
|
||||
},
|
||||
setRangeType(i, event) {
|
||||
if (event && event.preventDefault) {
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
$scope.ui.rangeType = i;
|
||||
let width = MODAL_WIDTH_SMALL;
|
||||
if (i === RANGE_TYPE.SINGLE_DAY) {
|
||||
width = MODAL_WIDTH_SMALL;
|
||||
} else if (i === RANGE_TYPE.DAY_RANGE || i === RANGE_TYPE.TIME_RANGE) {
|
||||
width = MODAL_WIDTH_LARGE;
|
||||
}
|
||||
$('.modal-dialog').width(width);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
$timeout(() => {
|
||||
$scope.ui.setRangeType('0');
|
||||
$('.ml-new-event-contents #id').focus();
|
||||
}, 0);
|
||||
|
||||
function extractForm() {
|
||||
let start = null;
|
||||
let end = null;
|
||||
|
||||
const startMoment = moment($scope.ui.timepicker.start);
|
||||
if ($scope.ui.rangeType === RANGE_TYPE.SINGLE_DAY) {
|
||||
const endMoment = moment($scope.ui.timepicker.start);
|
||||
start = startMoment.startOf('day');
|
||||
end = endMoment.startOf('day').add(1, 'days');
|
||||
}
|
||||
else if ($scope.ui.rangeType === RANGE_TYPE.DAY_RANGE) {
|
||||
const endMoment = moment($scope.ui.timepicker.end);
|
||||
start = startMoment.startOf('day');
|
||||
end = endMoment.startOf('day').add(1, 'days');
|
||||
}
|
||||
else if ($scope.ui.rangeType === RANGE_TYPE.TIME_RANGE) {
|
||||
start = startMoment;
|
||||
end = moment($scope.ui.timepicker.end);
|
||||
}
|
||||
|
||||
return{
|
||||
description: $scope.ui.description,
|
||||
start_time: start.valueOf(),
|
||||
end_time: end.valueOf()
|
||||
};
|
||||
}
|
||||
|
||||
$scope.save = function () {
|
||||
const event = extractForm();
|
||||
$modalInstance.close(event);
|
||||
};
|
||||
|
||||
$scope.cancel = function () {
|
||||
$modalInstance.dismiss('cancel');
|
||||
};
|
||||
});
|
|
@ -1,31 +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 template from './new_event_modal.html';
|
||||
|
||||
import { uiModules } from 'ui/modules';
|
||||
const module = uiModules.get('apps/ml');
|
||||
|
||||
module.service('mlNewEventService', function ($modal) {
|
||||
|
||||
this.openNewEventWindow = function () {
|
||||
return new Promise((resolve, reject) => {
|
||||
const modal = $modal.open({
|
||||
template,
|
||||
controller: 'MlNewEventModal',
|
||||
backdrop: 'static',
|
||||
keyboard: false
|
||||
});
|
||||
|
||||
modal.result
|
||||
.then(resolve)
|
||||
.catch(reject);
|
||||
});
|
||||
};
|
||||
|
||||
});
|
|
@ -1,35 +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.
|
||||
*/
|
||||
|
||||
|
||||
|
||||
// Calendar ID requires the same format as a Job ID, therefore isJobIdValid can be used
|
||||
|
||||
import { isJobIdValid } from 'plugins/ml/../common/util/job_utils';
|
||||
import _ from 'lodash';
|
||||
|
||||
export function validateCalendarId(calendarId, checks) {
|
||||
let valid = true;
|
||||
|
||||
_.each(checks, item => item.valid = true);
|
||||
|
||||
if (calendarId === '' || calendarId === undefined) {
|
||||
checks.calendarId.valid = false;
|
||||
} else if (isJobIdValid(calendarId) === false) {
|
||||
checks.calendarId.valid = false;
|
||||
let msg = 'Calendar ID can contain lowercase alphanumeric (a-z and 0-9), hyphens or underscores; ';
|
||||
msg += 'must start and end with an alphanumeric character';
|
||||
checks.calendarId.message = msg;
|
||||
}
|
||||
|
||||
_.each(checks, (item) => {
|
||||
if (item.valid === false) {
|
||||
valid = false;
|
||||
}
|
||||
});
|
||||
|
||||
return valid;
|
||||
}
|
|
@ -1,10 +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 './calendars_list';
|
||||
import './new_calendar';
|
|
@ -1,36 +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 ngMock from 'ng_mock';
|
||||
import expect from 'expect.js';
|
||||
|
||||
xdescribe('ML - Create Calendar Controller', () => {
|
||||
beforeEach(() => {
|
||||
ngMock.module('kibana');
|
||||
});
|
||||
|
||||
it('Initialize Create Calendar Controller', (done) => {
|
||||
ngMock.inject(function ($rootScope, $controller) {
|
||||
const scope = $rootScope.$new();
|
||||
|
||||
expect(() => {
|
||||
$controller('MlCreateCalendar', {
|
||||
$route: {
|
||||
current: {
|
||||
params: {}
|
||||
}
|
||||
},
|
||||
$scope: scope
|
||||
});
|
||||
}).to.not.throwError();
|
||||
|
||||
expect(scope.isNewCalendar).to.eql(true);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1 +0,0 @@
|
|||
@import 'new_calendar';
|
|
@ -1,21 +0,0 @@
|
|||
ml-new-calendar{
|
||||
font-size: $euiFontSizeS;
|
||||
|
||||
// SASSTODO: Proper selector
|
||||
h3 {
|
||||
margin-top: 0px;
|
||||
}
|
||||
|
||||
.new-calendar-container {
|
||||
width: 100%;
|
||||
margin-right: auto;
|
||||
margin-left: auto;
|
||||
padding-left: $euiSize;
|
||||
padding-right: $euiSize;
|
||||
|
||||
.validation-error {
|
||||
color: $euiColorDanger;
|
||||
font-size: $euiFontSizeXS;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,102 +0,0 @@
|
|||
<ml-nav-menu name="calendars_list"></ml-nav-menu>
|
||||
<ml-new-calendar ng-controller="MlCreateCalendar">
|
||||
<ml-message-bar></ml-message-bar>
|
||||
<div class="new-calendar-container">
|
||||
<div class="kuiViewContent kuiViewContent--constrainedWidth">
|
||||
<h3 class="euiTitle">{{pageTitle}}</h3>
|
||||
<div class="kuiViewContentItem kuiVerticalRhythm">
|
||||
<div class="kuiBar">
|
||||
<div class="kuiBarSection">
|
||||
<div class="kuiButtonGroup">
|
||||
<button
|
||||
class="kuiButton kuiButton--primary"
|
||||
ng-disabled="saveEnabled() === false"
|
||||
ng-click="save()" >
|
||||
Save
|
||||
</button>
|
||||
<button
|
||||
class="kuiButton kuiButton--basic"
|
||||
ng-click="cancel()"
|
||||
ng-disabled="saveLock === true"
|
||||
data-test-subj="btnCancel">
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="kuiViewContentItem kuiVerticalRhythm">
|
||||
<div class="kuiFormSection" aria-hidden="false">
|
||||
<label for="id" class="kuiFormLabel">
|
||||
Calendar ID
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
ng-disabled="isNewCalendar===false"
|
||||
id="id"
|
||||
name="id"
|
||||
ng-model="calendarId"
|
||||
ng-trim="true"
|
||||
class="kuiTextInput fullWidth"
|
||||
autofocus />
|
||||
<div ng-hide="validation.checks.calendarId.valid" class="validation-error">
|
||||
<div class="euiSpacer euiSpacer--s"></div>
|
||||
{{ ( validation.checks.calendarId.message || "Enter a name for the calendar" ) }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="kuiFormSection" aria-hidden="false">
|
||||
<label for="description" class="kuiFormLabel">
|
||||
Description
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
ng-disabled="isNewCalendar===false"
|
||||
id="description"
|
||||
name="description"
|
||||
ng-model="description"
|
||||
class="kuiTextInput fullWidth" />
|
||||
</div>
|
||||
<div class="kuiFormSection">
|
||||
<label class="kuiFormLabel">
|
||||
Jobs
|
||||
</label>
|
||||
<ml-item-select
|
||||
item-ids='jobIds'
|
||||
all-items='allJobs'
|
||||
placeholder='"Jobs"'
|
||||
external-update-function='updateJobsList'
|
||||
>
|
||||
</ml-item-select>
|
||||
</div>
|
||||
<div class="kuiFormSection">
|
||||
<label class="kuiFormLabel">
|
||||
Groups
|
||||
</label>
|
||||
<ml-item-select
|
||||
item-ids='groupIds'
|
||||
all-items='allGroups'
|
||||
placeholder='"Groups"'
|
||||
allow-tagging='true'
|
||||
tagging-text='"(new group)"'
|
||||
external-update-function='updateGroupsList'
|
||||
>
|
||||
</ml-item-select>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="kuiViewContentItem kuiVerticalRhythm">
|
||||
<label class="kuiFormLabel">
|
||||
Events
|
||||
</label>
|
||||
<ml-events-list
|
||||
events='events'
|
||||
show-controls='true'
|
||||
lock-controls='saveLock'
|
||||
/>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</ml-calendars-list>
|
|
@ -1,163 +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 $ from 'jquery';
|
||||
import _ from 'lodash';
|
||||
|
||||
import 'plugins/ml/components/item_select';
|
||||
import 'plugins/ml/settings/scheduled_events/components/events_list';
|
||||
|
||||
import { validateCalendarId } from 'plugins/ml/settings/scheduled_events/components/utils/validate_calendar';
|
||||
|
||||
import uiRoutes from 'ui/routes';
|
||||
import { checkFullLicense } from 'plugins/ml/license/check_license';
|
||||
import { checkGetJobsPrivilege } from 'plugins/ml/privilege/check_privilege';
|
||||
import { checkMlNodesAvailable } from 'plugins/ml/ml_nodes_check/check_ml_nodes';
|
||||
import { mlJobService } from 'plugins/ml/services/job_service';
|
||||
import { mlCalendarService } from 'plugins/ml/services/calendar_service';
|
||||
import { mlMessageBarService } from 'plugins/ml/components/messagebar/messagebar_service';
|
||||
import { initPromise } from 'plugins/ml/util/promise';
|
||||
import { ml } from 'plugins/ml/services/ml_api_service';
|
||||
|
||||
import template from './create_calendar.html';
|
||||
|
||||
uiRoutes
|
||||
.when('/settings/calendars_list/new_calendar', {
|
||||
template,
|
||||
resolve: {
|
||||
CheckLicense: checkFullLicense,
|
||||
privileges: checkGetJobsPrivilege,
|
||||
checkMlNodesAvailable,
|
||||
initPromise: initPromise(true)
|
||||
}
|
||||
})
|
||||
.when('/settings/calendars_list/edit_calendar/:calendarId', {
|
||||
template,
|
||||
resolve: {
|
||||
CheckLicense: checkFullLicense,
|
||||
privileges: checkGetJobsPrivilege,
|
||||
checkMlNodesAvailable,
|
||||
initPromise: initPromise(true)
|
||||
}
|
||||
});
|
||||
|
||||
import { uiModules } from 'ui/modules';
|
||||
const module = uiModules.get('apps/ml', ['ui.bootstrap']);
|
||||
|
||||
module.controller('MlCreateCalendar',
|
||||
function (
|
||||
$scope,
|
||||
$route,
|
||||
$location) {
|
||||
const msgs = mlMessageBarService;
|
||||
msgs.clear();
|
||||
|
||||
const calendarId = $route.current.params.calendarId;
|
||||
$scope.isNewCalendar = (calendarId === undefined);
|
||||
|
||||
$scope.pageTitle = $scope.isNewCalendar ? 'Create new calendar' : `Edit calendar ${calendarId}`;
|
||||
|
||||
$scope.calendarId = calendarId || '';
|
||||
$scope.description = '';
|
||||
$scope.events = [];
|
||||
$scope.jobIds = [];
|
||||
$scope.allJobs = [];
|
||||
$scope.groupIds = [];
|
||||
$scope.allGroups = [];
|
||||
$scope.updateJobsList = {};
|
||||
$scope.updateGroupsList = {};
|
||||
$scope.saveLock = false;
|
||||
$scope.validation = {
|
||||
checks: {
|
||||
calendarId: { valid: true },
|
||||
}
|
||||
};
|
||||
|
||||
mlJobService.loadJobs()
|
||||
.then(() => {
|
||||
const jobs = mlJobService.jobs;
|
||||
$scope.allJobs = jobs.map(j => ({ id: j.job_id }));
|
||||
$scope.allGroups = mlJobService.getJobGroups().map(g => ({ id: g.id }));
|
||||
|
||||
mlCalendarService.loadCalendars(jobs)
|
||||
.then((resp) => {
|
||||
const calendars = resp.calendars;
|
||||
const calGroups = mlCalendarService.getCalendarGroups();
|
||||
// append calendar groups to the list of all groups and deduplicate
|
||||
$scope.allGroups.push(...calGroups);
|
||||
$scope.allGroups = _.uniq($scope.allGroups, 'id');
|
||||
$scope.allGroups = _.sortBy($scope.allGroups, 'id');
|
||||
|
||||
calendars.forEach((calendar) => {
|
||||
// if we're editing an existing calendar
|
||||
if (calendarId !== undefined && calendarId === calendar.calendar_id) {
|
||||
$scope.events = calendar.events || [];
|
||||
$scope.description = calendar.description || '';
|
||||
|
||||
calendar.job_ids.forEach(id => {
|
||||
if ($scope.allJobs.find((j) => j.id === id)) {
|
||||
// if the job exists, add it to the job list
|
||||
$scope.jobIds.push(id);
|
||||
} else if ($scope.allGroups.find((g) => g.id === id)) {
|
||||
// otherwise, it could be a group,
|
||||
// if it exists as a group, add it to the group list
|
||||
$scope.groupIds.push(id);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
$scope.updateJobsList.update($scope.jobIds);
|
||||
$scope.updateGroupsList.update($scope.groupIds);
|
||||
});
|
||||
$('.new-calendar-container #id').focus();
|
||||
});
|
||||
|
||||
$scope.save = function () {
|
||||
msgs.clear();
|
||||
// Just pass the three event properties used by the endpoint, ignoring UI-specific properties
|
||||
// such as 'asterisk' which is used to flag recurring events from an ICS file import.
|
||||
const events = $scope.events.map((event) => {
|
||||
return {
|
||||
description: event.description,
|
||||
start_time: event.start_time,
|
||||
end_time: event.end_time
|
||||
};
|
||||
});
|
||||
|
||||
const calendar = {
|
||||
calendarId: $scope.calendarId,
|
||||
description: $scope.description,
|
||||
events,
|
||||
job_ids: [...$scope.jobIds, ...$scope.groupIds],
|
||||
};
|
||||
|
||||
if (validateCalendarId(calendar.calendarId, $scope.validation.checks)) {
|
||||
$scope.saveLock = true;
|
||||
const saveFunc = $scope.isNewCalendar ? (c => ml.addCalendar(c)) : (c => ml.updateCalendar(c));
|
||||
saveFunc(calendar)
|
||||
.then(() => {
|
||||
$location.path('settings/calendars_list');
|
||||
$scope.saveLock = false;
|
||||
})
|
||||
.catch((error) => {
|
||||
msgs.error('Save calendar failed: ', error);
|
||||
$scope.saveLock = false;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
$scope.cancel = function () {
|
||||
$location.path('settings/calendars_list');
|
||||
};
|
||||
|
||||
$scope.saveEnabled = function () {
|
||||
return ($scope.calendarId !== '' && $scope.calendarId !== undefined && $scope.saveLock === false);
|
||||
};
|
||||
|
||||
});
|
|
@ -1,9 +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 './create_calendar_controller';
|
Loading…
Reference in a new issue