Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- import React, { Component } from "react";
- import Classes from "./Nightwing.css";
- import GlobalStyles from "../../global-css/styles";
- // utilities
- import ActionEngine from "./utilities/ActionEngine";
- import Sorting from "./utilities/sorting";
- import MATCH_TERMS_RAW from "./admin/job-type-match-terms.json";
- /* COMPONENTS */
- import GridRow from "../../hoc/GridRow";
- import FilterBar from "./components/FilterBar/FilterBar";
- import Table from "./components/Table/Table";
- import InfoBar from "./components/InfoBar/InfoBar";
- import LoadingBar from "./components/LoadingBar/LoadingBar";
- import ContextMenu from "./components/Table/ContextMenu/ContextMenu";
- import DuplicateWindow from "./components/Table/ContextMenu/DuplicateWindow/DuplicateWindow";
- // sometimes the import comes back as a string, and sometimes as a parsed objected, either way this results in a parsed object
- const MATCH_TERMS = typeof MATCH_TERMS_RAW === "string" ? JSON.parse(MATCH_TERMS_RAW) : MATCH_TERMS_RAW;
- class Nightwing extends Component {
- state = {
- jobList: [],
- jobMap: {},
- filteredJobList: [],
- jobSelection: [],
- sortProperty: "timeScheduled",
- sortDirection: "desc",
- tableOffset: 0,
- tableCellDoubleClicked: null, // { rowNumber: Number, columnName: JobPropertyName }
- contentIDListOpen: false,
- contentIDListValue: "",
- isLoading: false,
- loadingPercent: null,
- scrollBar: {
- draggingScrollHead: false,
- tableOffsetAtStart: null,
- startPoint: null
- },
- contextMenu: {
- visible: false,
- position: {
- left: null,
- top: null
- },
- duplicatedJobDetails: []
- },
- filters: {
- jobTypes: {
- "submissions": true,
- "transcodes": true,
- "deliveries": true
- },
- jobStatuses: {
- "cancelled": true,
- "completed": true,
- "failed": true,
- "hold": true,
- "pending": true,
- "running": true
- },
- timeFrom: 0,
- timeTo: 24 * 7
- }
- };
- /* CONSTANTS */
- // pixel values
- TOP_AND_BOTTOM_BAR_HEIGHT = 60;
- TABLE_ROW_HEIGHT = 24;
- // terms for filtering jobs
- SUBMISSION_WORKFLOW_MATCH_TERMS = MATCH_TERMS.submission;
- TRANSCODE_WORKFLOW_MATCH_TERMS = MATCH_TERMS.transcode;
- DELIVERY_WORKFLOW_MATCH_TERMS = MATCH_TERMS.delivery;
- // handles talking to Action Engine for things like getting job data and manipulating jobs
- AE = new ActionEngine();
- SORT_HIERARCHIES = {
- "timeScheduled": ["timeScheduled", "timeModified", "workflowID"],
- "timeModified": ["timeModified", "timeScheduled", "workflowID"],
- "displayName": ["displayName", "timeScheduled"],
- "workflowID": ["workflowID", "timeScheduled"],
- "log": ["log", "timeScheduled"],
- "contentIDs": ["contentIDs", "timeScheduled"],
- "statusID": ["statusID", "timeScheduled"],
- "jobID": [],
- "priority": ["priority", "timeScheduled"]
- }
- /* EVENT HANDLERS */
- filterUpdateHandler = (filterType, filterName, boolValue) => {
- const newFilters = {...this.state.filters};
- newFilters[filterType][filterName] = boolValue;
- this.setState({filters: newFilters});
- }
- timeFilterUpdateHandler = (whichTime, newTime) => {
- const timeAsNumber = Number(newTime);
- const newFilters = {...this.state.filters};
- newFilters["time" + whichTime] = timeAsNumber;
- this.setState({filters: newFilters});
- }
- tableOffsetUpdate = (scrollDeltaY) => {
- const proposedNewTableOffset = this.state.tableOffset + (scrollDeltaY > 0 ? 1 : -1);
- const newTableOffset = this.validateOffsetAmount(proposedNewTableOffset);
- this.setState({tableOffset: newTableOffset});
- }
- scrollDragStartHandler = (clientY) => {
- this.setState({
- scrollBar: {
- startPoint: clientY,
- draggingScrollHead: true,
- tableOffsetAtStart: this.state.tableOffset
- }
- })
- }
- scrollDragEndHandler = () => {
- this.setState({
- scrollBar: {
- anchorPoint: null,
- draggingScrollHead: false,
- tableOffsetAtStart: null
- }
- });
- }
- scrollDragMoveHandler = (clientY) => {
- if (this.state.scrollBar.draggingScrollHead) {
- const distanceFromStartInPixels = clientY - this.state.scrollBar.startPoint;
- const adjustedDistanceFromStart = distanceFromStartInPixels * (1 - this.determineScrollHeadHeightInPercent());
- const numOfRowsPerPixel = this.determineNumOfRowsPerPixel();
- const proposedNewTableOffset = this.state.scrollBar.tableOffsetAtStart + Math.round(adjustedDistanceFromStart * numOfRowsPerPixel);
- const newTableOffset = this.validateOffsetAmount(proposedNewTableOffset);
- this.setState({
- tableOffset: newTableOffset
- })
- }
- }
- contentIDListVisiblityUpdate = (stateOfVisibility) => {
- this.setState({contentIDListOpen: stateOfVisibility});
- }
- contentIDListValueUpdate = (newValue) => {
- this.setState({contentIDListValue: newValue});
- }
- updateSortHandler = (propertyNameToSortBy) => {
- const currentSortPropertyName = this.state.sortProperty;
- const currentSortDirection = this.state.sortDirection;
- if (currentSortPropertyName === propertyNameToSortBy) {
- const newSortDirection = currentSortDirection === "desc" ? "asc" : "desc";
- this.setState({
- sortProperty: currentSortPropertyName,
- sortDirection: newSortDirection,
- filteredJobList: [...this.state.filteredJobList].reverse()
- });
- } else {
- this.setState({
- sortProperty: propertyNameToSortBy,
- sortDirection: "desc",
- filteredJobList: Sorting.defaultSort([...this.state.filteredJobList], this.SORT_HIERARCHIES[propertyNameToSortBy], "desc")
- });
- }
- }
- filteredListUpdateHandler = () => {
- // validate time values
- if (this.state.filters.timeFrom >= this.state.filters.timeTo) {
- window.alert(`The "from" time cannot be equal to or greater than the "to" time.`);
- return;
- }
- const filters = this.state.filters;
- const checkForMatch = (workflowName, termToMatch) => {
- return workflowName.toLowerCase().includes(termToMatch);
- }
- const typeNameToFileNameMap = {"deliveries": "DELIVERY", "transcodes": "TRANSCODE", "submissions": "SUBMISSION" };
- const jobTypeOrder = ["deliveries", "transcodes", "submissions"]; // this order is optimized for which workflows are most likely
- // create arrays for filtering the jobs
- const activeJobStatuses = Object.keys(filters.jobStatuses).filter((statusName) => filters.jobStatuses[statusName]);
- const contentIDListValues = this.state.contentIDListValue.split("\n").filter((value) => value.trim()).map((str) => Number(str));
- const activeJobTypes = Object.keys(filters.jobTypes).filter((typeName) => filters.jobTypes[typeName]);
- const activeJobTypesInOrder = jobTypeOrder.filter((typeName) => activeJobTypes.includes(typeName));
- if (activeJobStatuses.length === 0) {
- window.alert("You haven't selected any job status filters.");
- return;
- } else if (activeJobTypesInOrder.length === 0) {
- window.alert("You haven't selected any job type filters.");
- return;
- }
- const filteredList = this.state.jobList.filter((job) => {
- // filter out jobs that are too old
- const maximumTime = Date.now() - (filters.timeFrom * 60 * 60 * 1000);
- const minimumTime = Date.now() - (filters.timeTo * 60 * 60 * 1000);
- if (job.timeScheduled > maximumTime) return false;
- if (job.timeScheduled < minimumTime) return false;
- // filter out jobs that fail the job status filter
- const passesJobStatusFilter = activeJobStatuses.find((statusName) => job.statusID === statusName);
- if (!passesJobStatusFilter) return false;
- // filter out jobs that don't match a content ID from the filter list
- const hasMatchingContentID = contentIDListValues.length === 0 || !!job.contentIDs.find((jobContentID) => {
- return contentIDListValues.find((listID) => {
- return jobContentID === listID;
- });
- });
- if (!hasMatchingContentID) return false;
- // filter out jobs that don't match one of the job type filters
- const workflowName = job.workflowID;
- const passesJobTypeFilter = activeJobTypesInOrder.find((typeName) => {
- const matchTerms = this[typeNameToFileNameMap[typeName] + "_WORKFLOW_MATCH_TERMS"];
- const foundBlacklistMatch = !!matchTerms.blacklist.find((term) => checkForMatch(workflowName, term));
- const foundWhitelistMatch = !!matchTerms.whitelist.find((term) => checkForMatch(workflowName, term));
- if (!foundBlacklistMatch && foundWhitelistMatch) return true;
- else return false;
- });
- if (!passesJobTypeFilter) return false;
- // if all the checks are passed, this job passes the filter
- return true;
- });
- this.setState({
- filteredJobList: filteredList,
- jobSelection: [],
- tableOffset: 0,
- tableCellDoubleClicked: null
- });
- }
- tableRowClickToSelectHandler = (event, selectedJobIndex) => {
- const { shiftKey, ctrlKey } = event;
- if (this.state.jobSelection.length === 0) {
- this.setState({jobSelection: [selectedJobIndex]});
- } else {
- if (!ctrlKey && !shiftKey) {
- this.setState({jobSelection: [selectedJobIndex]});
- } else if (ctrlKey && !shiftKey) {
- const newJobSelection = [...this.state.jobSelection];
- const jobIndex = newJobSelection.indexOf(selectedJobIndex);
- jobIndex === -1 ? newJobSelection.push(selectedJobIndex) : newJobSelection.splice(jobIndex, 1);
- this.setState({jobSelection: newJobSelection});
- } else if (shiftKey && !ctrlKey) {
- if (this.state.jobSelection.length) {
- const mostRecentSelectionJobIndex = this.state.jobSelection[this.state.jobSelection.length - 1];
- if (mostRecentSelectionJobIndex === selectedJobIndex) return;
- const newJobSelection = [...this.state.jobSelection];
- let i = mostRecentSelectionJobIndex > selectedJobIndex ? selectedJobIndex : mostRecentSelectionJobIndex + 1;
- let end = mostRecentSelectionJobIndex > selectedJobIndex ? mostRecentSelectionJobIndex : selectedJobIndex + 1;
- while (i !== end) {
- if (!this.state.jobSelection.includes(i)) {
- newJobSelection.push(i);
- }
- i++;
- }
- this.setState({jobSelection: newJobSelection});
- }
- }
- }
- }
- tableSelectAllHandler = () => {
- const newJobSelection = [];
- let i = 0;
- while (i < this.state.filteredJobList.length) {
- newJobSelection.push(i);
- i++;
- }
- this.setState({jobSelection: newJobSelection});
- setTimeout(() => window.getSelection().empty(), 0);
- }
- tableCellDoubleClickToSelectHandler = (rowNumber, columnName) => {
- this.setState({tableCellDoubleClicked: {
- rowNumber, columnName
- }});
- }
- tableCellDoubleClickToSelectRemoveHandler = () => {
- this.setState({tableCellDoubleClicked: null});
- }
- tableRightClickDisplayContextMenu = (event, clickedJobIndex) => {
- event.preventDefault();
- const currentContextMenuState = {...this.state.contextMenu};
- const updatedContextMenuState = {
- ...currentContextMenuState,
- visible: true,
- position: {
- left: event.clientX,
- top: event.clientY,
- }
- };
- if (this.state.jobSelection.includes(clickedJobIndex)) {
- this.setState({
- contextMenu: updatedContextMenuState
- });
- } else {
- this.setState({
- contextMenu: updatedContextMenuState,
- jobSelection: [clickedJobIndex]
- });
- }
- }
- hideContextMenu = () => {
- const currentContextMenuState = {...this.state.contextMenu};
- this.setState({
- contextMenu: {
- ...currentContextMenuState,
- visible: false,
- position: {
- left: null,
- top: null,
- }
- }
- })
- }
- fetchJobXMLStringsHandler = () => {
- const xmlFetchPromises = this.state.jobSelection.map((jobIndex) => {
- return this.AE.getJobXmlString(this.state.filteredJobList[jobIndex].jobID);
- });
- Promise.all(xmlFetchPromises)
- .then((xmlStrings) => {
- const duplicatedJobDetails = this.state.jobSelection.map((jobIndex, i) => {
- return {
- jobID: this.state.filteredJobList[jobIndex].jobID,
- title: this.state.filteredJobList[jobIndex].displayName,
- xmlString: xmlStrings[i]
- };
- });
- this.setState({
- contextMenu: {
- visible: false,
- position: {
- left: null,
- top: null
- },
- duplicatedJobDetails: duplicatedJobDetails
- }
- });
- });
- }
- removeJobFromDuplicatedJobArray = (jobID) => {
- const updatedDuplicatedJobDetails = [...this.state.contextMenu.duplicatedJobDetails];
- const jobIndexInDuplicatedArray = updatedDuplicatedJobDetails.findIndex((jobDetails) => jobDetails.jobID === jobID);
- updatedDuplicatedJobDetails.splice(jobIndexInDuplicatedArray, 1);
- const newContextMenuState = {
- ...this.state.contextMenu,
- duplicatedJobDetails: updatedDuplicatedJobDetails
- };
- this.setState({
- contextMenu: newContextMenuState
- })
- }
- submitDuplicatedJob = (xmlString, jobID) => {
- this.AE.duplicateJob(xmlString, jobID)
- .then(() => {
- this.removeJobFromDuplicatedJobArray(jobID)
- })
- .catch((error) => {
- window.alert(error);
- });
- }
- restartHandler = () => this.state.jobSelection.map((jobIndex) => this.AE.restartJob(this.state.filteredJobList[jobIndex].jobID));
- cancelHandler = () => this.state.jobSelection.map((jobIndex) => this.AE.cancelJob(this.state.filteredJobList[jobIndex].jobID));
- bumpHandler = () => this.state.jobSelection.map((jobIndex) => this.AE.bumpJob(this.state.filteredJobList[jobIndex].jobID));
- continueHandler = () => this.state.jobSelection.map((jobIndex) => this.AE.continueJob(this.state.filteredJobList[jobIndex].jobID));
- jobActionHandler = (handlerFunction, actionName) => {
- const actionPromises = handlerFunction();
- Promise.all(actionPromises)
- .then(() => {
- this.hideContextMenu();
- })
- .catch((error) => {
- window.alert(`One or more jobs failed to ${actionName}. Error:\n\n${error}`);
- });
- }
- /* UTLILITY METHODS */
- countJobsByStatus = (listStatePropertyName) => {
- const countMap = {};
- const jobStatusNames = Object.keys(this.state.filters.jobStatuses);
- jobStatusNames.forEach((statusName) => countMap[statusName] = 0);
- this.state[listStatePropertyName].forEach((job) => countMap[job.statusID]++);
- return countMap;
- }
- determineNumOfRowsThatFitInTable() {
- const tableBodyHeightinPixels = window.innerHeight - ((this.TOP_AND_BOTTOM_BAR_HEIGHT * 2) + this.TABLE_ROW_HEIGHT);
- const numOfJobsToDraw = Math.ceil(tableBodyHeightinPixels / this.TABLE_ROW_HEIGHT);
- return numOfJobsToDraw;
- }
- determineNumOfRowsPerPixel = () => {
- return this.state.filteredJobList.length / this.determineMaxScrollBarHeadOffset();
- }
- determineTableHeightInPixels = () => {
- return (window.innerHeight - (this.TOP_AND_BOTTOM_BAR_HEIGHT * 2));
- }
- determineScrollBarTrackHeightInPixels = () => {
- return this.determineTableHeightInPixels() - this.TABLE_ROW_HEIGHT;
- }
- determineScrollHeadHeightInPercent = () => {
- const visibleJobsPercentOfTotal = this.determineNumOfRowsThatFitInTable() / this.state.filteredJobList.length;
- return visibleJobsPercentOfTotal > 0.05 ? visibleJobsPercentOfTotal : 0.05;
- }
- determineMaxTableOffset = () => {
- return Math.max((this.state.filteredJobList.length - this.determineNumOfRowsThatFitInTable() + 1), 0);
- }
- determineMaxScrollBarHeadOffset = () => {
- return this.determineScrollBarTrackHeightInPixels() * (1 - this.determineScrollHeadHeightInPercent());
- }
- validateOffsetAmount = (proposedValue) => {
- const maxOffset = this.determineMaxTableOffset();
- proposedValue = proposedValue < 0 ? 0 : proposedValue;
- proposedValue = proposedValue <= maxOffset ? proposedValue : maxOffset;
- return proposedValue;
- }
- /* LIFECYCLE METHODS */
- componentDidMount() {
- // after initial mount, reach out to Action Engine to grab all current jobs
- const loadingCallback = (percent) => {
- this.setState({loadingPercent: percent});
- };
- this.AE.getAllJobs(loadingCallback)
- .then((jobs) => {
- // create the initial job map
- const jobMap = {};
- jobs.forEach((job) => jobMap[job.jobID] = job);
- this.setState({
- jobList: Sorting.defaultSort(jobs, this.SORT_HIERARCHIES[this.state.sortProperty], this.state.sortDirection),
- jobMap: jobMap,
- isLoading: false,
- loadingPercent: null
- });
- });
- this.setState({isLoading: true, loadingPercent: 0});
- // set up global listener for ending scroll head drags
- window.addEventListener("mouseup", () => this.scrollDragEndHandler());
- /* KEYBOARD SHORTCUTS */
- // Ctrl + A to select all job rows
- window.addEventListener("keydown", (event) => {
- const textInputIsNotActiveElement = !["input", "textarea"].includes(document.activeElement.tagName.toLowerCase());
- if (event.ctrlKey && event.key.toLowerCase() === "a" && textInputIsNotActiveElement) {
- this.tableSelectAllHandler();
- }
- });
- // PageUp/PageDown to scroll table by one "page"
- window.addEventListener("keydown", (event) => {
- const textInputIsNotActiveElement = !["input", "textarea"].includes(document.activeElement.tagName.toLowerCase());
- if (["pagedown", "pageup"].includes(event.key.toLowerCase()) && textInputIsNotActiveElement) {
- const direction = event.key.toLowerCase().replace("page", "");
- const tableOffsetChange = direction === "up" ? -this.determineNumOfRowsThatFitInTable() : this.determineNumOfRowsThatFitInTable();
- const currentTableOffset = this.state.tableOffset;
- const proposedTableOffset = currentTableOffset + tableOffsetChange;
- const newTableOffset = this.validateOffsetAmount(proposedTableOffset);
- this.setState({
- tableOffset: newTableOffset
- });
- }
- });
- }
- render() {
- const globalAppStyles = {
- color: GlobalStyles.FONT_COLOR,
- gridTemplateRows: `${this.TOP_AND_BOTTOM_BAR_HEIGHT}px 1fr ${this.TOP_AND_BOTTOM_BAR_HEIGHT}px`
- };
- const scrollBarTrackHeightInPixels = this.determineScrollBarTrackHeightInPixels();
- const scrollBarHeadHeightInPercent = Math.min(this.determineScrollHeadHeightInPercent(), 1);
- const scrollBarHeadHeightInPixels = scrollBarHeadHeightInPercent * scrollBarTrackHeightInPixels;
- const numOfRowsThatFitInTable = this.determineNumOfRowsThatFitInTable();
- const scrollDistanceInPercent = this.state.tableOffset === 0 ? 0 : this.state.tableOffset / (this.state.filteredJobList.length - numOfRowsThatFitInTable);
- let scrollOffsetInPixels = scrollDistanceInPercent * this.determineMaxScrollBarHeadOffset();
- // used in the top and bottom bars for positioning buttons and dropdowns
- const TOP_BOTTOM_BAR_COLUMN_PADDING = 10;
- const clickableElementStyles = {
- width: `calc(100% - ${TOP_BOTTOM_BAR_COLUMN_PADDING * 2}px)`,
- marginLeft: TOP_BOTTOM_BAR_COLUMN_PADDING + "px",
- marginTop: TOP_BOTTOM_BAR_COLUMN_PADDING + "px",
- height: this.TOP_AND_BOTTOM_BAR_HEIGHT - (TOP_BOTTOM_BAR_COLUMN_PADDING * 2) + "px"
- };
- const contextMenu = this.state.contextMenu.visible ?
- <ContextMenu
- AE={this.AE}
- position={this.state.contextMenu.position}
- jobSelection={this.state.jobSelection.map((jobIndex) => this.state.filteredJobList[jobIndex])}
- fetchJobXMLStringsHandler={this.fetchJobXMLStringsHandler}
- jobActionHandler={this.jobActionHandler}
- restartHandler={this.restartHandler}
- cancelHandler={this.cancelHandler}
- bumpHandler={this.bumpHandler}
- continueHandler={this.continueHandler} />
- : null
- const duplicateJobWindows = this.state.contextMenu.duplicatedJobDetails.map((jobDetails) => {
- return (
- <DuplicateWindow
- xmlString={jobDetails.xmlString}
- jobID={jobDetails.jobID}
- title={jobDetails.title}
- closeWindowHandler={this.removeJobFromDuplicatedJobArray}
- submitHandler={this.submitDuplicatedJob} />
- );
- });
- return (
- <div
- className={Classes.Nightwing} style={globalAppStyles}
- onMouseMove={(event) => this.scrollDragMoveHandler(event.clientY)}>
- {contextMenu}
- {duplicateJobWindows}
- <GridRow row="1">
- <FilterBar
- clickableElementStyles={clickableElementStyles}
- height={this.TOP_AND_BOTTOM_BAR_HEIGHT}
- filters={this.state.filters}
- contentIDListOpen={this.state.contentIDListOpen}
- contentIDListVisiblityUpdate={this.contentIDListVisiblityUpdate}
- contentIDListValue={this.state.contentIDListValue}
- contentIDListValueUpdateHandler={this.contentIDListValueUpdate}
- filterUpdateHandler={this.filterUpdateHandler}
- timeFilterUpdateHandler={this.timeFilterUpdateHandler}
- filteredListUpdateHandler={this.filteredListUpdateHandler} />
- </GridRow>
- <GridRow row="2">
- <Table
- jobsToDraw={this.state.filteredJobList.slice(this.state.tableOffset, this.state.tableOffset + numOfRowsThatFitInTable)}
- tableOffset={this.state.tableOffset}
- tableRowClickToSelectHandler={this.tableRowClickToSelectHandler}
- tableCellDoubleClicked={this.state.tableCellDoubleClicked}
- tableCellDoubleClickToSelectHandler={this.tableCellDoubleClickToSelectHandler}
- tableCellDoubleClickToSelectRemoveHandler={this.tableCellDoubleClickToSelectRemoveHandler}
- tableOffsetUpdateHandler={this.tableOffsetUpdate}
- scrollDragStartHandler={this.scrollDragStartHandler}
- scrollBarState={this.state.scrollBar}
- rowHeight={this.TABLE_ROW_HEIGHT}
- scrollBarTrackHeight={scrollBarTrackHeightInPixels}
- scrollBarHeadHeight={scrollBarHeadHeightInPixels}
- scrollOffset={scrollOffsetInPixels}
- jobSelection={this.state.jobSelection}
- contextMenu={this.state.contextMenu}
- tableRightClickDisplayContextMenu={this.tableRightClickDisplayContextMenu}
- tableClickHideContextMenu={this.hideContextMenu}
- sortProperty={this.state.sortProperty}
- sortDirection={this.state.sortDirection}
- updateSortHandler={this.updateSortHandler}>
- {this.state.isLoading ? <LoadingBar loadingPercent={this.state.loadingPercent} /> : null}
- </Table>
- </GridRow>
- <GridRow row="3">
- <InfoBar
- clickableElementStyles={clickableElementStyles}
- height={this.TOP_AND_BOTTOM_BAR_HEIGHT}
- selectedCount={this.state.jobSelection.length}
- resultCount={this.state.filteredJobList.length}
- failureCount={this.countJobsByStatus("filteredJobList").failed || 0}
- missingCount={23} />
- </GridRow>
- </div>
- );
- }
- }
- export default Nightwing;
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement