Repository URL to install this package:
|
Version:
2.2.1 ▾
|
(function webpackUniversalModuleDefinition(root, factory) {
if(typeof exports === 'object' && typeof module === 'object')
module.exports = factory();
else if(typeof define === 'function' && define.amd)
define([], factory);
else if(typeof exports === 'object')
exports["calendar"] = factory();
else
root["Airflow"] = root["Airflow"] || {}, root["Airflow"]["calendar"] = factory();
})(window, function() {
return /******/ (function(modules) { // webpackBootstrap
/******/ // The module cache
/******/ var installedModules = {};
/******/
/******/ // The require function
/******/ function __webpack_require__(moduleId) {
/******/
/******/ // Check if module is in cache
/******/ if(installedModules[moduleId]) {
/******/ return installedModules[moduleId].exports;
/******/ }
/******/ // Create a new module (and put it into the cache)
/******/ var module = installedModules[moduleId] = {
/******/ i: moduleId,
/******/ l: false,
/******/ exports: {}
/******/ };
/******/
/******/ // Execute the module function
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/
/******/ // Flag the module as loaded
/******/ module.l = true;
/******/
/******/ // Return the exports of the module
/******/ return module.exports;
/******/ }
/******/
/******/
/******/ // expose the modules object (__webpack_modules__)
/******/ __webpack_require__.m = modules;
/******/
/******/ // expose the module cache
/******/ __webpack_require__.c = installedModules;
/******/
/******/ // define getter function for harmony exports
/******/ __webpack_require__.d = function(exports, name, getter) {
/******/ if(!__webpack_require__.o(exports, name)) {
/******/ Object.defineProperty(exports, name, { enumerable: true, get: getter });
/******/ }
/******/ };
/******/
/******/ // define __esModule on exports
/******/ __webpack_require__.r = function(exports) {
/******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
/******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
/******/ }
/******/ Object.defineProperty(exports, '__esModule', { value: true });
/******/ };
/******/
/******/ // create a fake namespace object
/******/ // mode & 1: value is a module id, require it
/******/ // mode & 2: merge all properties of value into the ns
/******/ // mode & 4: return value when already ns object
/******/ // mode & 8|1: behave like require
/******/ __webpack_require__.t = function(value, mode) {
/******/ if(mode & 1) value = __webpack_require__(value);
/******/ if(mode & 8) return value;
/******/ if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;
/******/ var ns = Object.create(null);
/******/ __webpack_require__.r(ns);
/******/ Object.defineProperty(ns, 'default', { enumerable: true, value: value });
/******/ if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));
/******/ return ns;
/******/ };
/******/
/******/ // getDefaultExport function for compatibility with non-harmony modules
/******/ __webpack_require__.n = function(module) {
/******/ var getter = module && module.__esModule ?
/******/ function getDefault() { return module['default']; } :
/******/ function getModuleExports() { return module; };
/******/ __webpack_require__.d(getter, 'a', getter);
/******/ return getter;
/******/ };
/******/
/******/ // Object.prototype.hasOwnProperty.call
/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
/******/
/******/ // __webpack_public_path__
/******/ __webpack_require__.p = "";
/******/
/******/
/******/ // Load entry module and return exports
/******/ return __webpack_require__(__webpack_require__.s = 38);
/******/ })
/************************************************************************/
/******/ ({
/***/ 1:
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return getMetaValue; });
/*!
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
/* global document */
function getMetaValue(name) {
const elem = document.querySelector(`meta[name="${name}"]`);
if (!elem) {
return null;
}
return elem.getAttribute('content');
}
/***/ }),
/***/ 38:
/***/ (function(module, exports, __webpack_require__) {
__webpack_require__(39);
module.exports = __webpack_require__(40);
/***/ }),
/***/ 39:
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
__webpack_require__.r(__webpack_exports__);
// extracted by mini-css-extract-plugin
/***/ }),
/***/ 40:
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony import */ var _meta_value__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(1);
/*!
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
/* global calendarData, statesColors, document, window, $, d3, moment */
const dagId = Object(_meta_value__WEBPACK_IMPORTED_MODULE_0__[/* default */ "a"])('dag_id');
const treeUrl = Object(_meta_value__WEBPACK_IMPORTED_MODULE_0__[/* default */ "a"])('tree_url');
function getTreeViewURL(d) {
return `${treeUrl}?dag_id=${encodeURIComponent(dagId)}&base_date=${encodeURIComponent(d.toISOString())}`;
} // date helpers
function formatDay(d) {
return ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'][d];
}
function toMoment(y, m, d) {
return moment.utc([y, m, d]);
}
function weekOfMonth(y, m, d) {
const monthOffset = toMoment(y, m, 1).day();
const dayOfMonth = toMoment(y, m, d).date();
return Math.floor((dayOfMonth + monthOffset - 1) / 7);
}
function weekOfYear(y, m) {
const yearOffset = toMoment(y, 0, 1).day();
const dayOfYear = toMoment(y, m, 1).dayOfYear();
return Math.floor((dayOfYear + yearOffset - 1) / 7);
}
function daysInMonth(y, m) {
const lastDay = toMoment(y, m, 1).add(1, 'month').subtract(1, 'day');
return lastDay.date();
}
function weeksInMonth(y, m) {
const firstDay = toMoment(y, m, 1);
const monthOffset = firstDay.day();
return Math.floor((daysInMonth(y, m) + monthOffset) / 7) + 1;
}
const dateFormat = 'YYYY-MM-DD';
document.addEventListener('DOMContentLoaded', () => {
$('span.status_square').tooltip({
html: true
}); // JSON.parse is faster for large payloads than an object literal
const rootData = JSON.parse(calendarData);
const dayTip = d3.tip().attr('class', 'tooltip d3-tip').html(toolTipHtml => toolTipHtml); // draw the calendar
function draw() {
// display constants
const leftRightMargin = 32;
const titleHeight = 24;
const yearLabelWidth = 34;
const dayLabelWidth = 14;
const dayLabelPadding = 4;
const yearPadding = 20;
const cellSize = 16;
const yearHeight = cellSize * 7 + 2;
const maxWeeksInYear = 53;
const legendHeight = 30;
const legendSwatchesPadding = 4;
const legendSwtchesTextWidth = 44; // group dag run stats by year -> month -> day -> state
let dagStates = d3.nest().key(dr => moment.utc(dr.date, dateFormat).year()).key(dr => moment.utc(dr.date, dateFormat).month()).key(dr => moment.utc(dr.date, dateFormat).date()).key(dr => dr.state).map(rootData.dag_states); // Make sure we have one year displayed for each year between the start and end dates.
// This also ensures we do not have show an empty calendar view when no dag runs exist.
const startYear = moment.utc(rootData.start_date, dateFormat).year();
const endYear = moment.utc(rootData.end_date, dateFormat).year();
for (let y = startYear; y <= endYear; y += 1) {
dagStates[y] = dagStates[y] || {};
}
dagStates = d3.entries(dagStates).map(keyVal => ({
year: keyVal.key,
dagStates: keyVal.value
})).sort(data => data.year); // root SVG element
const fullWidth = leftRightMargin * 2 + yearLabelWidth + dayLabelWidth + maxWeeksInYear * cellSize;
const yearsHeight = (yearHeight + yearPadding) * dagStates.length + yearPadding;
const fullHeight = titleHeight + legendHeight + yearsHeight;
const svg = d3.select('#calendar-svg').attr('width', fullWidth).attr('height', fullHeight).call(dayTip); // Add the legend
const legend = svg.append('g').attr('transform', `translate(0, ${titleHeight + legendHeight / 2})`);
let legendXOffset = fullWidth - leftRightMargin;
function drawLegend(rightState, leftState, numSwatches = 1, swatchesWidth = cellSize) {
const startColor = statesColors[leftState || rightState];
const endColor = statesColors[rightState];
legendXOffset -= legendSwtchesTextWidth;
legend.append('text').attr('x', legendXOffset).attr('y', cellSize / 2).attr('text-anchor', 'start').attr('class', 'status-label').attr('alignment-baseline', 'middle').text(rightState);
legendXOffset -= legendSwatchesPadding;
legendXOffset -= swatchesWidth;
legend.append('g').attr('transform', `translate(${legendXOffset}, 0)`).selectAll('g').data(d3.range(numSwatches)).enter().append('rect').attr('x', v => v * (swatchesWidth / numSwatches)).attr('width', swatchesWidth / numSwatches).attr('height', cellSize).attr('class', 'day').attr('fill', v => d3.interpolateHsl(startColor, endColor)(v / numSwatches));
legendXOffset -= legendSwatchesPadding;
if (leftState !== undefined) {
legend.append('text').attr('x', legendXOffset).attr('y', cellSize / 2).attr('text-anchor', 'end').attr('class', 'status-label').attr('alignment-baseline', 'middle').text(leftState);
legendXOffset -= legendSwtchesTextWidth;
}
}
drawLegend('no_status');
drawLegend('running');
drawLegend('failed', 'success', 10, 100); // Add the years groups, each holding one year of data.
const years = svg.append('g').attr('transform', `translate(${leftRightMargin}, ${titleHeight + legendHeight})`);
const year = years.selectAll('g').data(dagStates).enter().append('g').attr('transform', (d, i) => `translate(0, ${yearPadding + (yearHeight + yearPadding) * i})`);
year.append('text').attr('x', -yearHeight * 0.5).attr('transform', 'rotate(270)').attr('text-anchor', 'middle').attr('class', 'year-label').text(d => d.year); // write day names
year.append('g').attr('transform', `translate(${yearLabelWidth}, ${dayLabelPadding})`).attr('text-anchor', 'end').selectAll('g').data(d3.range(7)).enter().append('text').attr('y', i => (i + 0.5) * cellSize).attr('class', 'day-label').text(formatDay); // create months groups to old the individual day cells & month outline for each month.
const months = year.append('g').attr('transform', `translate(${yearLabelWidth + dayLabelWidth}, 0)`);
const month = months.append('g').selectAll('g').data(data => d3.range(12).map(i => ({
year: data.year,
month: i,
dagStates: data.dagStates[i] || {}
}))).enter().append('g').attr('transform', data => `translate(${weekOfYear(data.year, data.month) * cellSize}, 0)`);
const tipHtml = data => {
const stateCounts = d3.entries(data.dagStates).map(kv => `${kv.value[0].count} ${kv.key}`);
const date = toMoment(data.year, data.month, data.day);
const daySr = formatDay(date.day());
const dateStr = date.format(dateFormat);
return `<strong>${daySr} ${dateStr}</strong><br>${stateCounts.join('<br>')}`;
}; // Create the day cells
month.selectAll('g').data(data => d3.range(daysInMonth(data.year, data.month)).map(i => {
const day = i + 1;
const dagRunsByState = data.dagStates[day] || {};
return {
year: data.year,
month: data.month,
day,
dagStates: dagRunsByState
};
})).enter().append('rect').attr('x', data => weekOfMonth(data.year, data.month, data.day) * cellSize).attr('y', data => toMoment(data.year, data.month, data.day).day() * cellSize).attr('width', cellSize).attr('height', cellSize).attr('class', 'day').attr('fill', data => {
const runningCount = (data.dagStates.running || [{
count: 0
}])[0].count;
if (runningCount > 0) return statesColors.running;
const successCount = (data.dagStates.success || [{
count: 0
}])[0].count;
const failedCount = (data.dagStates.failed || [{
count: 0
}])[0].count;
if (successCount + failedCount === 0) return statesColors.no_status;
let ratioFailures;
if (failedCount === 0) ratioFailures = 0;else {
// We use a minimum color interpolation floor, so that days with low failures ratios
// don't appear almost as green as days with not failure at all.
const floor = 0.5;
ratioFailures = floor + failedCount / (failedCount + successCount) * (1 - floor);
}
return d3.interpolateHsl(statesColors.success, statesColors.failed)(ratioFailures);
}).on('click', data => {
window.location.href = getTreeViewURL( // add 1 day and subtract 1 ms to not show any run from the next day.
toMoment(data.year, data.month, data.day).add(1, 'day').subtract(1, 'ms'));
}).on('mouseover', function showTip(data) {
const tt = tipHtml(data);
dayTip.direction('n');
dayTip.show(tt, this);
}).on('mouseout', function hideTip(data) {
dayTip.hide(data, this);
}); // add outline (path) around month
month.selectAll('g').data(data => [data]).enter().append('path').attr('class', 'month').style('fill', 'none').attr('d', data => {
const firstDayOffset = toMoment(data.year, data.month, 1).day();
const lastDayOffset = toMoment(data.year, data.month, 1).add(1, 'month').day();
const weeks = weeksInMonth(data.year, data.month);
return d3.svg.line()([[0, firstDayOffset * cellSize], [cellSize, firstDayOffset * cellSize], [cellSize, 0], [weeks * cellSize, 0], [weeks * cellSize, lastDayOffset * cellSize], [(weeks - 1) * cellSize, lastDayOffset * cellSize], [(weeks - 1) * cellSize, 7 * cellSize], [0, 7 * cellSize], [0, firstDayOffset * cellSize]]);
});
}
function update() {
$('#loading').remove();
draw();
}
update();
});
/***/ })
/******/ });
});