diff --git a/tez-ui/src/main/resources/META-INF/LICENSE.txt b/tez-ui/src/main/resources/META-INF/LICENSE.txt index 354d7457a9..608dc614c8 100644 --- a/tez-ui/src/main/resources/META-INF/LICENSE.txt +++ b/tez-ui/src/main/resources/META-INF/LICENSE.txt @@ -232,7 +232,6 @@ The Apache TEZ tez-ui bundles the following files under the MIT License: - more-js v0.8.2 (https://github.com/sreenaths/snippet-ss) - snippet-ss v1.11.0 (https://github.com/sreenaths/snippet-ss) - em-tgraph v0.0.4 (https://github.com/sreenaths/em-tgraph) - - em-table v0.3.12 (https://github.com/sreenaths/em-table) - ember-cli-app-version v1.0.0 (https://github.com/EmberSherpa/ember-cli-app-version) - Authored by Taras Mankovski - ember-cli-auto-register v1.1.0 (https://github.com/williamsbdev/ember-cli-auto-register) - Copyright © 2015 Brandon Williams http://williamsbdev.com - ember-cli-content-security-policy v0.4.0 (https://github.com/rwjblue/ember-cli-content-security-policy) diff --git a/tez-ui/src/main/webapp/app/components/em-table-cell.js b/tez-ui/src/main/webapp/app/components/em-table-cell.js new file mode 100644 index 0000000000..d4e6a54f53 --- /dev/null +++ b/tez-ui/src/main/webapp/app/components/em-table-cell.js @@ -0,0 +1,124 @@ +/** + * 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. + */ + +import Ember from 'ember'; +import layout from '../templates/components/em-table-cell'; + +export default Ember.Component.extend({ + layout: layout, + + classNames: ['table-cell'], + classNameBindings: ['innerCell', 'isWaiting'], + + innerCell: Ember.computed('index', function () { + if(this.get('index')) { + return 'inner'; + } + }), + + row: null, + columnDefinition: null, + + isWaiting: false, + + _value: null, + _observedPath: null, + _comment: null, + _cellContent: Ember.computed({ + set: function (key, value, prevValue) { + if(value !== prevValue) { + this.highlightCell(); + } + return value; + } + }), + + _addObserver: function (path) { + this._removeObserver(); + this.get('row').addObserver(path, this, this._onValueChange); + this.set('_observedPath', path); + }, + + _removeObserver: function () { + var path = this.get('_observedPath'); + if(path) { + this.get('row').removeObserver(path, this, this._onValueChange); + this.set('_observedPath', null); + } + }, + + _pathObserver: Ember.on('init', Ember.observer('row', 'columnDefinition.contentPath', 'columnDefinition.observePath', function () { + var path = this.get('columnDefinition.contentPath'); + if(path && this.get('columnDefinition.observePath')) { + this._addObserver(path); + } + })), + + _onValueChange: function (row, path) { + this.set('_value', row.get(path)); + }, + + setContent: function (content) { + var comment; + + if(content && content.hasOwnProperty("content")) { + comment = content.comment; + content = content.content; + } + + this.setProperties({ + _comment: comment, + _cellContent: content, + isWaiting: false + }); + }, + + _cellContentObserver: Ember.on('init', Ember.observer('row', 'columnDefinition', '_value', function () { + var cellContent = this.get('columnDefinition').getCellContent(this.get('row'), this.get("_value")), + that = this; + + if(cellContent && cellContent.then) { + cellContent.then(function (content) { + that.setContent(content); + }); + this.set('isWaiting', true); + } + else if(cellContent === undefined && this.get('columnDefinition.observePath')) { + this.set('isWaiting', true); + } + else { + this.setContent(cellContent); + } + })), + + highlightCell: function () { + var element = this.$(); + if(element) { + element.removeClass("bg-transition"); + element.addClass("highlight"); + Ember.run.later(function () { + element.addClass("bg-transition"); + element.removeClass("highlight"); + }, 100); + } + }, + + willDestroy: function () { + this._removeObserver(); + } +}); diff --git a/tez-ui/src/main/webapp/app/components/em-table-column.js b/tez-ui/src/main/webapp/app/components/em-table-column.js new file mode 100644 index 0000000000..a3cb5a9072 --- /dev/null +++ b/tez-ui/src/main/webapp/app/components/em-table-column.js @@ -0,0 +1,109 @@ +/** + * 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. + */ + +import Ember from 'ember'; + +import layout from '../templates/components/em-table-column'; + +export default Ember.Component.extend({ + layout: layout, + + definition: null, + rows: null, + index: 0, + + tableDefinition: null, + dataProcessor: null, + adjustedWidth: null, + defaultWidth: "", + + classNames: ['table-column'], + classNameBindings: ['inner', 'extraClassNames'], + + inner: Ember.computed('index', function () { + return !!this.get('index'); + }), + + extraClassNames: Ember.computed("definition.classNames", function () { + var classNames = this.get("definition.classNames"); + if(classNames) { + return classNames.join(" "); + } + }), + + didInsertElement: function () { + Ember.run.scheduleOnce('afterRender', this, function() { + this.setWidth(); + this.setMinWidth(); + }); + }, + + setMinWidth: Ember.observer("definition.minWidth", function () { + this.$().css("minWidth", this.get('definition.minWidth')); + }), + + setWidth: Ember.observer("adjustedWidth", "defaultWidth", function () { + var thisElement = this.$(); + thisElement.css("width", this.get('adjustedWidth') || this.get('defaultWidth')); + Ember.run.scheduleOnce('afterRender', this, function() { + this.get('parentView').send('columnWidthChanged', thisElement.width(), this.get("definition"), this.get("index")); + }); + }), + + _onColResize: function (event) { + var data = event.data, + width; + + if(!data.startEvent) { + data.startEvent = event; + } + + width = data.startWidth + event.clientX - data.startEvent.clientX; + data.thisObj.set('adjustedWidth', width); + }, + + _endColResize: function (event) { + var thisObj = event.data.thisObj; + Ember.$(document).off('mousemove', thisObj._onColResize); + Ember.$(document).off('mouseup', thisObj._endColResize); + }, + + actions: { + sort: function () { + var definition = this.get('definition'), + beforeSort = definition.get('beforeSort'); + + if(!beforeSort || beforeSort.call(definition, definition)) { + let columnId = this.get('definition.id'), + sortOrder = this.get('tableDefinition.sortOrder') === 'desc' ? 'asc' : 'desc'; + + this.get('parentView').send('sort', columnId, sortOrder); + } + }, + startColResize: function () { + var mouseTracker = { + thisObj: this, + startWidth: this.$().width(), + startEvent: null + }; + + Ember.$(document).on('mousemove', mouseTracker, this._onColResize); + Ember.$(document).on('mouseup', mouseTracker, this._endColResize); + } + } +}); diff --git a/tez-ui/src/main/webapp/app/components/em-table-facet-panel-values.js b/tez-ui/src/main/webapp/app/components/em-table-facet-panel-values.js new file mode 100644 index 0000000000..ec88181f4d --- /dev/null +++ b/tez-ui/src/main/webapp/app/components/em-table-facet-panel-values.js @@ -0,0 +1,199 @@ +/** + * 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. + */ + +import Ember from 'ember'; +import layout from '../templates/components/em-table-facet-panel-values'; + +const LIST_LIMIT = 7; + +export default Ember.Component.extend({ + layout: layout, + + data: null, + checkedCount: null, + + tableDefinition: null, + dataProcessor: null, + + tmpFacetConditions: null, + + hideValues: true, + + currentPage: 1, + + classNames: ['em-table-facet-panel-values'], + classNameBindings: ['hideValues', 'hideFilter', 'hideSelectAll'], + + filterText: null, + allButtonTitle: Ember.computed("filterText", function () { + let filterText = this.get("filterText"); + return filterText ? `Select all with substring '${filterText}'` : "Select all"; + }), + isVisible: Ember.computed("data.facets.length", "tableDefinition.minValuesToDisplay", function () { + return this.get("data.facets.length") >= this.get("tableDefinition.minValuesToDisplay"); + }), + hideFilter: Ember.computed("allFacets.length", function () { + return this.get("allFacets.length") < LIST_LIMIT; + }), + hideSelectAll: Ember.computed("fieldFacetConditions", "checkedCount", "data.facets", function () { + return this.get("fieldFacetConditions.in.length") === this.get("data.facets.length"); + }), + + fieldFacetConditions: Ember.computed("tmpFacetConditions", "data.column.id", function () { + var columnID = this.get("data.column.id"), + conditions = this.get(`tmpFacetConditions.${columnID}`), + facets = this.get("data.facets") || []; + + if(!conditions) { + conditions = { + in: facets.map(facet => facet.value) + }; + this.set(`tmpFacetConditions.${columnID}`, conditions); + } + + return conditions; + }), + + allFacets: Ember.computed("data.facets", "fieldFacetConditions", function () { + var facets = this.get("data.facets") || [], + + checkedValues = this.get("fieldFacetConditions.in"), + selectionHash = {}; + + if(checkedValues) { + checkedValues.forEach(function (valueText) { + selectionHash[valueText] = 1; + }); + } + + return Ember.A(facets.map(function (facet) { + facet = Ember.Object.create(facet); + facet.set("checked", selectionHash[facet.value]); + + if(!facet.get("displayText")) { + facet.set("displayText", facet.get("value")); + } + + return facet; + })); + }), + + filteredFacets: Ember.computed("allFacets", "filterText", function () { + var allFacets = this.get("allFacets"), + filterText = this.get("filterText"), + filteredFacets; + + if(filterText) { + filteredFacets = allFacets.filter(function (facet) { + return facet.get("value").match(filterText); + }); + } + else { + filteredFacets = allFacets; + } + + return filteredFacets; + }), + + _filterObserver: Ember.observer("filterText", function () { + this.set("currentPage", 1); + }), + + totalPages: Ember.computed("filteredFacets.length", "tableDefinition.facetValuesPageSize", function () { + return Math.ceil(this.get("filteredFacets.length") / this.get("tableDefinition.facetValuesPageSize")); + }), + showPagination: Ember.computed("totalPages", function () { + return this.get("totalPages") > 1; + }), + showPrevious: Ember.computed("currentPage", function () { + return this.get("currentPage") > 1; + }), + showNext: Ember.computed("currentPage", "totalPages", function () { + return this.get("currentPage") < this.get("totalPages"); + }), + + paginatedFacets: Ember.computed("filteredFacets", "currentPage", "tableDefinition.facetValuesPageSize", function () { + let currentPage = this.get("currentPage"), + pageSize = this.get("tableDefinition.facetValuesPageSize"); + return this.get("filteredFacets").slice( + (currentPage - 1) * pageSize, + currentPage * pageSize); + }), + + actions: { + changePage: function (factor) { + var newPage = this.get("currentPage") + factor; + if(newPage > 0 && newPage <= this.get("totalPages")) { + this.set("currentPage", newPage); + } + }, + toggleValueDisplay: function () { + this.toggleProperty("hideValues"); + this.get("parentView").sendAction("toggleValuesDisplayAction", !this.get("hideValues"), this.get("data")); + }, + clickedCheckbox: function (facet) { + var checkedValues = this.get("fieldFacetConditions.in"), + value = facet.get("value"), + valueIndex = checkedValues.indexOf(value); + + facet.toggleProperty("checked"); + + if(facet.get("checked")) { + if(valueIndex === -1) { + checkedValues.push(value); + } + } + else if(valueIndex !== -1) { + checkedValues.splice(valueIndex, 1); + } + + this.set("checkedCount", checkedValues.length); + }, + + selectAll: function () { + var filteredFacets = this.get("filteredFacets"), + checkedValues = this.get("fieldFacetConditions.in"); + + filteredFacets.forEach(function (facet) { + if(!facet.get("checked")) { + checkedValues.push(facet.get("value")); + } + + facet.set("checked", true); + }); + + this.set("fieldFacetConditions.in", checkedValues); + this.set("checkedCount", checkedValues.length); + }, + clickedOnly: function (facet) { + var allFacets = this.get("allFacets"), + checkedValues = []; + + allFacets.forEach(function (facet) { + facet.set("checked", false); + }); + + facet.set("checked", true); + checkedValues.push(facet.get("value")); + + this.set("fieldFacetConditions.in", checkedValues); + this.set("checkedCount", checkedValues.length); + } + } + +}); diff --git a/tez-ui/src/main/webapp/app/components/em-table-facet-panel.js b/tez-ui/src/main/webapp/app/components/em-table-facet-panel.js new file mode 100644 index 0000000000..fdbd8f5b89 --- /dev/null +++ b/tez-ui/src/main/webapp/app/components/em-table-facet-panel.js @@ -0,0 +1,86 @@ +/** + * 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. + */ + +import Ember from 'ember'; +import layout from '../templates/components/em-table-facet-panel'; + +export default Ember.Component.extend({ + layout: layout, + + classNames: ["em-table-facet-panel"], + classNameBindings: ['isEmpty', 'hideFilter'], + + isVisible: Ember.computed.alias('tableDefinition.enableFaceting'), + + tableDefinition: null, + dataProcessor: null, + tmpFacetConditions: {}, + + filterText: null, + isEmpty: Ember.computed("dataProcessor.facetedFields.length", function () { + return this.get("dataProcessor.facetedFields.length") === 0; + }), + hideFilter: Ember.computed("dataProcessor.facetedFields.length", "tableDefinition.minFieldsForFilter", function () { + return this.get("dataProcessor.facetedFields.length") < this.get("tableDefinition.minFieldsForFilter"); + }), + + didInsertElement: Ember.observer("filterText", "dataProcessor.facetedFields", function () { + var fields = this.get("dataProcessor.facetedFields"), + filterText = this.get("filterText"), + filterRegex = new RegExp(filterText, "i"), + elements = Ember.$(this.get("element")).find(".field-list>li"); + + elements.each(function (index, element) { + var foundMatch = !filterText || Ember.get(fields, `${index}.column.headerTitle`).match(filterRegex); + Ember.$(element)[foundMatch ? "show" : "hide"](); + }); + }), + + _facetConditionsObserver: Ember.observer("tableDefinition.facetConditions", "dataProcessor.processedRows.[]", function () { + var facetConditions = Ember.$.extend({}, this.get("tableDefinition.facetConditions")); + this.set("tmpFacetConditions", facetConditions); + }), + + actions: { + applyFilters: function () { + var tmpFacetConditions = this.get("tmpFacetConditions"), + facetedFields = this.get("dataProcessor.facetedFields"), + normalizedTmpFacetConditions = {}; + + facetedFields.forEach(function (field) { + var column = field.column, + columnId = column.get("id"), + facetType = column.get("facetType"), + normalizedConditions; + + if(facetType) { + normalizedConditions = facetType.normaliseConditions(tmpFacetConditions[columnId], field.facets); + if(normalizedConditions) { + normalizedTmpFacetConditions[columnId] = normalizedConditions; + } + } + }); + + this.set("tableDefinition.facetConditions", normalizedTmpFacetConditions); + }, + clearFilters: function () { + this.set("tmpFacetConditions", {}); + this.set("tableDefinition.facetConditions", {}); + }, + } +}); diff --git a/tez-ui/src/main/webapp/app/components/em-table-header-cell.js b/tez-ui/src/main/webapp/app/components/em-table-header-cell.js new file mode 100644 index 0000000000..c0a8e12379 --- /dev/null +++ b/tez-ui/src/main/webapp/app/components/em-table-header-cell.js @@ -0,0 +1,64 @@ +/** + * 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. + */ + +import Ember from 'ember'; +import layout from '../templates/components/em-table-header-cell'; + +export default Ember.Component.extend({ + layout: layout, + + title: null, // Header cell Name + attributeBindings: ['title'], + + definition: null, + tableDefinition: null, + dataProcessor: null, + + classNames: ['table-header-cell'], + classNameBindings: ['isSorting'], + + isSorting: Ember.computed("dataProcessor.isSorting", function () { + return this.get("dataProcessor.isSorting") && this.get('tableDefinition.sortColumnId') === this.get('definition.id'); + }), + + sortIconCSS: Ember.computed('tableDefinition.sortOrder', 'tableDefinition.sortColumnId', function () { + if(this.get('tableDefinition.sortColumnId') === this.get('definition.id')) { + return this.get('tableDefinition.sortOrder'); + } + }), + + sortToggledTitle: Ember.computed('tableDefinition.sortOrder', 'tableDefinition.sortColumnId', function () { + if(this.get('tableDefinition.sortColumnId') === this.get('definition.id')) { + switch(this.get('tableDefinition.sortOrder')) { + case "asc": + return "descending"; + case "desc": + return "ascending"; + } + } + }), + + actions: { + sort: function () { + this.get('parentView').send('sort'); + }, + startColResize: function () { + this.get('parentView').send('startColResize'); + } + } +}); diff --git a/tez-ui/src/main/webapp/app/components/em-table-linked-cell.js b/tez-ui/src/main/webapp/app/components/em-table-linked-cell.js new file mode 100644 index 0000000000..c42c56613b --- /dev/null +++ b/tez-ui/src/main/webapp/app/components/em-table-linked-cell.js @@ -0,0 +1,64 @@ +/** + * 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. + */ + +import Ember from 'ember'; +import layout from '../templates/components/em-table-linked-cell'; + +export default Ember.Component.extend({ + layout: layout, + + definition: null, + content: null, + + normalizedLinks: Ember.computed("content", function () { + var content = this.get("content"), + links; + + if(content) { + if(!Array.isArray(content)) { + content = [content]; + } + + links = content.map(function (link) { + var model, + text = Ember.get(link, "text") || Ember.get(link, "displayText"); + + if(text) { + link = Ember.Object.create(link, { + text: text + }); + + if(link.get("model") === undefined) { + link.set("model", link.get("id")); + } + + model = link.get("model"); + link.set("withModel", model !== undefined); + + return link; + } + }); + + links = links.filter(function (link) { + return link; + }); + } + + return links; + }) +}); diff --git a/tez-ui/src/main/webapp/app/components/em-table-pagination-ui.js b/tez-ui/src/main/webapp/app/components/em-table-pagination-ui.js new file mode 100644 index 0000000000..858928b60b --- /dev/null +++ b/tez-ui/src/main/webapp/app/components/em-table-pagination-ui.js @@ -0,0 +1,98 @@ +/** + * 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. + */ + +import Ember from 'ember'; +import layout from '../templates/components/em-table-pagination-ui'; + +export default Ember.Component.extend({ + layout: layout, + + tableDefinition: null, + dataProcessor: null, + + classNames: ['pagination-ui'], + isVisible: Ember.computed.alias('tableDefinition.enablePagination'), + + showFirst: Ember.computed('_possiblePages', function () { + return this.get("dataProcessor.totalPages") && this.get('_possiblePages.0.pageNum') !== 1; + }), + + showLast: Ember.computed('_possiblePages', 'dataProcessor.totalPages', function () { + var possiblePages = this.get("_possiblePages"); + if(possiblePages.length) { + return possiblePages[possiblePages.length - 1].pageNum !== this.get("dataProcessor.totalPages"); + } + }), + + rowCountOptions: Ember.computed('tableDefinition.rowCountOptions', 'tableDefinition.rowCount', function () { + var options = this.get('tableDefinition.rowCountOptions'), + rowCount = this.get('tableDefinition.rowCount'); + + return options.map(function (option) { + return { + value: option, + selected: option === rowCount + }; + }); + }), + + _possiblePages: Ember.computed('tableDefinition.pageNum', 'dataProcessor.totalPages', function () { + var pageNum = this.get('tableDefinition.pageNum'), + totalPages = this.get('dataProcessor.totalPages'), + possiblePages = [], + startPage = 1, + endPage = totalPages, + delta = 0; + + if(totalPages > 5) { + startPage = pageNum - 2; + endPage = pageNum + 2; + + if(startPage < 1) { + delta = 1 - startPage; + } + else if(endPage > totalPages) { + delta = totalPages - endPage; + } + + startPage += delta; + endPage += delta; + } + + while(startPage <= endPage) { + possiblePages.push({ + isCurrent: startPage === pageNum, + pageNum: startPage++ + }); + } + + return possiblePages; + }), + + actions: { + rowSelected: function (value) { + value = parseInt(value); + if(this.get('tableDefinition.rowCount') !== value) { + this.get('parentView').send('rowChanged', value); + } + }, + changePage: function (value) { + this.get('parentView').send('pageChanged', value); + } + } +}); diff --git a/tez-ui/src/main/webapp/app/components/em-table-progress-cell.js b/tez-ui/src/main/webapp/app/components/em-table-progress-cell.js new file mode 100644 index 0000000000..32f75c473a --- /dev/null +++ b/tez-ui/src/main/webapp/app/components/em-table-progress-cell.js @@ -0,0 +1,46 @@ +/** + * 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. + */ + +import Ember from 'ember'; +import layout from '../templates/components/em-table-progress-cell'; + +export default Ember.Component.extend({ + layout: layout, + + content: null, + + message: Ember.computed("content", function () { + var content = this.get("content"); + + if(content === undefined || content === null) { + return "Not Available!"; + } + else if(isNaN(parseFloat(content))){ + return "Invalid Data!"; + } + }), + + _definition: Ember.computed("definition", function () { + return Ember.Object.extend({ + valueMin: 0, + valueMax: 1, + striped: true, + style: null + }).create(this.get("definition")); + }) +}); diff --git a/tez-ui/src/main/webapp/app/components/em-table-search-ui.js b/tez-ui/src/main/webapp/app/components/em-table-search-ui.js new file mode 100644 index 0000000000..58c4f75cdf --- /dev/null +++ b/tez-ui/src/main/webapp/app/components/em-table-search-ui.js @@ -0,0 +1,95 @@ +/** + * 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. + */ + +import Ember from 'ember'; +import layout from '../templates/components/em-table-search-ui'; + +export default Ember.Component.extend({ + layout: layout, + + tableDefinition: null, + dataProcessor: null, + + classNames: ['search-ui'], + classNameBindings: ['hasError'], + isVisible: Ember.computed.alias('tableDefinition.enableSearch'), + + searchTypes: ["Regex", "SQL"], + actualSearchType: null, + + text: Ember.computed.oneWay('tableDefinition.searchText'), + + _actualSearchTypeDecider: Ember.observer("tableDefinition.searchType", "text", function () { + var searchType = this.get("tableDefinition.searchType"), + actualSearchType = this.get("actualSearchType"); + + switch(searchType) { + case "SQL": + case "Regex": + actualSearchType = searchType; + break; + + case "manual": + if(!actualSearchType) { + actualSearchType = "Regex"; + } + // Will be set from the template + break; + + case "auto": + var text = this.get("text"), + columns = this.get('tableDefinition.columns'); + + if(text) { + actualSearchType = this.get("dataProcessor.sql").validateClause(text, columns) ? "SQL" : "Regex"; + } + else { + actualSearchType = null; + } + break; + } + + this.set("actualSearchType", actualSearchType); + }), + + hasError: Ember.computed("text", "actualSearchType", "tableDefinition.searchType", function () { + var text = this.get("text"), + columns = this.get('tableDefinition.columns'), + actualSearchType = this.get("actualSearchType"); + + if(text) { + switch(actualSearchType) { + case "SQL": + return !this.get("dataProcessor.sql").validateClause(text, columns); + case "Regex": + try { + new RegExp(text); + } + catch(e) { + return true; + } + } + } + }), + + actions: { + search: function () { + this.get('parentView').send('search', this.get('text'), this.get("actualSearchType")); + } + } +}); diff --git a/tez-ui/src/main/webapp/app/components/em-table-status-cell.js b/tez-ui/src/main/webapp/app/components/em-table-status-cell.js index 7751719865..7f1fee8e35 100644 --- a/tez-ui/src/main/webapp/app/components/em-table-status-cell.js +++ b/tez-ui/src/main/webapp/app/components/em-table-status-cell.js @@ -17,8 +17,10 @@ */ import Ember from 'ember'; +import layout from '../templates/components/em-table-status-cell'; export default Ember.Component.extend({ + layout: layout, content: null, diff --git a/tez-ui/src/main/webapp/app/components/em-table.js b/tez-ui/src/main/webapp/app/components/em-table.js new file mode 100644 index 0000000000..79aae3d480 --- /dev/null +++ b/tez-ui/src/main/webapp/app/components/em-table.js @@ -0,0 +1,281 @@ +/** + * 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. + */ + +import Ember from 'ember'; +import Definition from '../utils/table-definition'; +import ColumnDefinition from '../utils/column-definition'; +import DataProcessor from '../utils/data-processor'; + +import layout from '../templates/components/em-table'; + +const DEFAULT_ROW_HIGHLIGHT_COLOR = "#EEE"; + +function createAssigner(targetPath, targetKey, sourcePath) { + return Ember.on("init", Ember.observer(targetPath, sourcePath, function () { + var target = this.get(targetPath), + source = this.get(sourcePath); + if(target && source !== undefined) { + target.set(targetKey, source); + } + })); +} + +const HANDLERS = { + // Mouse handlers + mouseOver: function(event) { + var index = Ember.$(this).index() + 1; + event.data.highlightRow(index); + }, + mouseLeave: function(event) { + event.data.highlightRow(-1); + }, + + // Scroll handler + onScroll: function(event) { + var tableBody = event.currentTarget, + scrollValues = event.data.get("scrollValues"); + + scrollValues.set("left", tableBody.scrollLeft); + scrollValues.set("width", tableBody.scrollWidth); + } +}; + +export default Ember.Component.extend({ + layout: layout, + + classNames: ["em-table"], + classNameBindings: ["showScrollShadow", "showLeftScrollShadow", "showRightScrollShadow"], + + definition: null, + dataProcessor: null, + + highlightRowOnMouse: false, // Could be true or {color: "#XYZ"} + + headerComponentNames: ['em-table-search-ui', 'em-table-pagination-ui'], + footerComponentNames: ['em-table-pagination-ui'], + + leftPanelComponentName: "em-table-facet-panel", + rightPanelComponentName: "", + + columnWidthChangeAction: null, + + scrollChangeAction: null, + scrollValues: null, + _widthTrackerTimer: null, + + init: function() { + this._super(); + this.set("scrollValues", Ember.Object.create({ + left: 0, + width: 0, + viewPortWidth: 0 + })); + }, + + showScrollShadow: false, + showLeftScrollShadow: false, + showRightScrollShadow: false, + + assignDefinitionInProcessor: createAssigner('_dataProcessor', 'tableDefinition', '_definition'), + assignRowsInProcessor: createAssigner('_dataProcessor', 'rows', 'rows'), + assignColumnsInDefinition: createAssigner('_definition', 'columns', 'columns'), + + assignEnableSortInDefinition: createAssigner('_definition', 'enableSort', 'enableSort'), + assignEnableSearchInDefinition: createAssigner('_definition', 'enableSearch', 'enableSearch'), + assignEnablePaginationInDefinition: createAssigner('_definition', 'enablePagination', 'enablePagination'), + assignRowCountInDefinition: createAssigner('_definition', 'rowCount', 'rowCount'), + + _definition: Ember.computed('definition', 'definitionClass', function () { + return this.get('definition') || (this.get('definitionClass') || Definition).create(); + }), + _dataProcessor: Ember.computed('dataProcessor', 'dataProcessorClass', function () { + return this.get('dataProcessor') || (this.get('dataProcessorClass') || DataProcessor).create(); + }), + + displayFooter: Ember.computed("_definition.minRowsForFooter", "_dataProcessor.processedRows.length", function () { + return this.get("_definition.minRowsForFooter") <= this.get("_dataProcessor.processedRows.length"); + }), + + _processedRowsObserver: Ember.observer('_dataProcessor.processedRows', function () { + this.sendAction('rowsChanged', this.get('_dataProcessor.processedRows')); + }), + + _setColumnWidth: function (columns) { + var widthText = (100 / columns.length) + "%"; + columns.forEach(function (column) { + if(!column.width) { + column.width = widthText; + } + }); + }, + + _columns: Ember.computed('_definition.columns', function () { + var rawColumns = this.get('_definition.columns'), + normalisedColumns = { + left: [], + center: [], + right: [], + length: rawColumns.length + }; + + rawColumns.forEach(function (column) { + normalisedColumns[column.get("pin")].push({ + definition: column, + width: column.width + }); + }); + + if(normalisedColumns.center.length === 0) { + normalisedColumns.center = [{ + definition: ColumnDefinition.fillerColumn, + }]; + } + + this._setColumnWidth(normalisedColumns.center); + + return normalisedColumns; + }), + + message: Ember.computed('_dataProcessor.message', '_columns.length', '_dataProcessor.processedRows.length', function () { + var message = this.get("_dataProcessor.message"); + if(message) { + return message; + } + else if(!this.get('_columns.length')) { + return "No columns available!"; + } + else if(!this.get("_dataProcessor.processedRows.length")) { + let identifiers = Ember.String.pluralize(this.get('_definition.recordType') || "record"); + return `No ${identifiers} available!`; + } + }), + + highlightRow: function (index) { + var element = Ember.$(this.get("element")), + sheet = element.find("style")[0].sheet, + elementID = element.attr("id"), + color = this.get("highlightRowOnMouse.color") || DEFAULT_ROW_HIGHLIGHT_COLOR; + + try { + sheet.deleteRule(0); + }catch(e){} + + if(index >= 0) { + sheet.insertRule(`#${elementID} .table-cell:nth-child(${index}){ background-color: ${color}; }`, 0); + } + }, + + didInsertElement: function () { + Ember.run.scheduleOnce('afterRender', this, function() { + this.highlightRowOnMouseObserver(); + this.scrollChangeActionObserver(); + }); + }, + + highlightRowOnMouseObserver: Ember.observer("highlightRowOnMouse", function () { + var highlightRowOnMouse = this.get("highlightRowOnMouse"), + element = this.get("element"); + + if(element) { + element = Ember.$(element).find(".table-mid"); + + if(highlightRowOnMouse) { + element.on('mouseover', '.table-cell', this, HANDLERS.mouseOver); + element.on('mouseleave', this, HANDLERS.mouseLeave); + } + else { + element.off('mouseover', '.table-cell', HANDLERS.mouseOver); + element.off('mouseleave', HANDLERS.mouseLeave); + } + } + }), + + scrollValuesObserver: Ember.observer("scrollValues.left", "scrollValues.width", "scrollValues.viewPortWidth", function () { + var scrollValues = this.get("scrollValues"); + + this.sendAction("scrollChangeAction", scrollValues); + + + this.set("showLeftScrollShadow", scrollValues.left > 1); + this.set("showRightScrollShadow", scrollValues.left < (scrollValues.width - scrollValues.viewPortWidth)); + }), + + scrollChangeActionObserver: Ember.observer("scrollChangeAction", "message", "showScrollShadow", function () { + Ember.run.scheduleOnce('afterRender', this, function() { + var addScrollListener = this.get("scrollChangeAction") || this.get("showScrollShadow"), + element = this.$().find(".table-body"), + scrollValues = this.get("scrollValues"); + + if(addScrollListener && element) { + element = element.get(0); + + clearInterval(this.get("_widthTrackerTimer")); + + if(element) { + if(addScrollListener) { + Ember.$(element).on('scroll', this, HANDLERS.onScroll); + + this.set("_widthTrackerTimer", setInterval(function () { + scrollValues.setProperties({ + width: element.scrollWidth, + viewPortWidth: element.offsetWidth + }); + }, 1000)); + } + else { + element.off('scroll', HANDLERS.onScroll); + } + } + } + }); + }), + + willDestroyElement: function () { + this._super(); + clearInterval(this.get("_widthTrackerTimer")); + Ember.$(this.$().find(".table-body")).off(); + Ember.$(this.$().find(".table-mid")).off(); + Ember.$(this.$()).off(); + }, + + actions: { + search: function (searchText, actualSearchType) { + this.set('_definition.searchText', searchText); + this.set('_definition._actualSearchType', actualSearchType); + this.sendAction("searchAction", searchText); + }, + sort: function (sortColumnId, sortOrder) { + this.get("_definition").setProperties({ + sortColumnId, + sortOrder + }); + this.sendAction("sortAction", sortColumnId, sortOrder); + }, + rowChanged: function (rowCount) { + this.set('_definition.rowCount', rowCount); + this.sendAction("rowAction", rowCount); + }, + pageChanged: function (pageNum) { + this.set('_definition.pageNum', pageNum); + this.sendAction("pageAction", pageNum); + }, + columnWidthChanged: function (width, columnDefinition, index) { + this.sendAction("columnWidthChangeAction", width, columnDefinition, index); + } + } +}); diff --git a/tez-ui/src/main/webapp/app/controllers/app/configs.js b/tez-ui/src/main/webapp/app/controllers/app/configs.js index 838abc1512..e8f13fc0e0 100644 --- a/tez-ui/src/main/webapp/app/controllers/app/configs.js +++ b/tez-ui/src/main/webapp/app/controllers/app/configs.js @@ -20,7 +20,7 @@ import Ember from 'ember'; import TableController from '../table'; -import ColumnDefinition from 'em-table/utils/column-definition'; +import ColumnDefinition from '../../utils/column-definition'; var MoreObject = more.Object; diff --git a/tez-ui/src/main/webapp/app/controllers/app/dags.js b/tez-ui/src/main/webapp/app/controllers/app/dags.js index bb4502a9b1..1febc66f97 100644 --- a/tez-ui/src/main/webapp/app/controllers/app/dags.js +++ b/tez-ui/src/main/webapp/app/controllers/app/dags.js @@ -17,7 +17,7 @@ */ import MultiTableController from '../multi-table'; -import ColumnDefinition from 'em-table/utils/column-definition'; +import ColumnDefinition from '../../utils/column-definition'; export default MultiTableController.extend({ breadcrumbs: [{ diff --git a/tez-ui/src/main/webapp/app/controllers/counters-table.js b/tez-ui/src/main/webapp/app/controllers/counters-table.js index 42361b4bb5..37bae66d06 100644 --- a/tez-ui/src/main/webapp/app/controllers/counters-table.js +++ b/tez-ui/src/main/webapp/app/controllers/counters-table.js @@ -20,7 +20,7 @@ import Ember from 'ember'; import TableController from './table'; -import ColumnDefinition from 'em-table/utils/column-definition'; +import ColumnDefinition from '../utils/column-definition'; var MoreObject = more.Object; diff --git a/tez-ui/src/main/webapp/app/controllers/dag/attempts.js b/tez-ui/src/main/webapp/app/controllers/dag/attempts.js index 47e95d9aaa..4616638cba 100644 --- a/tez-ui/src/main/webapp/app/controllers/dag/attempts.js +++ b/tez-ui/src/main/webapp/app/controllers/dag/attempts.js @@ -17,7 +17,7 @@ */ import MultiTableController from '../multi-table'; -import ColumnDefinition from 'em-table/utils/column-definition'; +import ColumnDefinition from '../../utils/column-definition'; export default MultiTableController.extend({ breadcrumbs: [{ diff --git a/tez-ui/src/main/webapp/app/controllers/dag/graphical.js b/tez-ui/src/main/webapp/app/controllers/dag/graphical.js index c55ab8b3a4..cc4130fd80 100644 --- a/tez-ui/src/main/webapp/app/controllers/dag/graphical.js +++ b/tez-ui/src/main/webapp/app/controllers/dag/graphical.js @@ -19,7 +19,7 @@ import Ember from 'ember'; import MultiTableController from '../multi-table'; -import ColumnDefinition from 'em-table/utils/column-definition'; +import ColumnDefinition from '../../utils/column-definition'; export default MultiTableController.extend({ diff --git a/tez-ui/src/main/webapp/app/controllers/dag/index/index.js b/tez-ui/src/main/webapp/app/controllers/dag/index/index.js index c9adde4c6b..6196b9aa87 100644 --- a/tez-ui/src/main/webapp/app/controllers/dag/index/index.js +++ b/tez-ui/src/main/webapp/app/controllers/dag/index/index.js @@ -19,7 +19,7 @@ import Ember from 'ember'; import MultiTableController from '../../multi-table'; -import ColumnDefinition from 'em-table/utils/column-definition'; +import ColumnDefinition from '../../../utils/column-definition'; export default MultiTableController.extend({ columns: ColumnDefinition.make([{ diff --git a/tez-ui/src/main/webapp/app/controllers/dag/swimlane.js b/tez-ui/src/main/webapp/app/controllers/dag/swimlane.js index bbac40b26d..1fe2988858 100644 --- a/tez-ui/src/main/webapp/app/controllers/dag/swimlane.js +++ b/tez-ui/src/main/webapp/app/controllers/dag/swimlane.js @@ -19,7 +19,7 @@ import Ember from 'ember'; import MultiTableController from '../multi-table'; -import ColumnDefinition from 'em-table/utils/column-definition'; +import ColumnDefinition from '../../utils/column-definition'; import VertexProcess from '../../utils/vertex-process'; import fullscreen from 'em-tgraph/utils/fullscreen'; diff --git a/tez-ui/src/main/webapp/app/controllers/dag/tasks.js b/tez-ui/src/main/webapp/app/controllers/dag/tasks.js index 92f674ad8e..834abf9b64 100644 --- a/tez-ui/src/main/webapp/app/controllers/dag/tasks.js +++ b/tez-ui/src/main/webapp/app/controllers/dag/tasks.js @@ -17,7 +17,7 @@ */ import MultiTableController from '../multi-table'; -import ColumnDefinition from 'em-table/utils/column-definition'; +import ColumnDefinition from '../../utils/column-definition'; export default MultiTableController.extend({ breadcrumbs: [{ diff --git a/tez-ui/src/main/webapp/app/controllers/dag/vertices.js b/tez-ui/src/main/webapp/app/controllers/dag/vertices.js index 313a5a9be5..34295e5fde 100644 --- a/tez-ui/src/main/webapp/app/controllers/dag/vertices.js +++ b/tez-ui/src/main/webapp/app/controllers/dag/vertices.js @@ -17,7 +17,7 @@ */ import MultiTableController from '../multi-table'; -import ColumnDefinition from 'em-table/utils/column-definition'; +import ColumnDefinition from '../../utils/column-definition'; export default MultiTableController.extend({ breadcrumbs: [{ diff --git a/tez-ui/src/main/webapp/app/controllers/home/index.js b/tez-ui/src/main/webapp/app/controllers/home/index.js index 754e5e6b99..53c4e6d4bf 100644 --- a/tez-ui/src/main/webapp/app/controllers/home/index.js +++ b/tez-ui/src/main/webapp/app/controllers/home/index.js @@ -19,8 +19,8 @@ import Ember from 'ember'; import TableController from '../table'; -import ColumnDefinition from 'em-table/utils/column-definition'; -import TableDefinition from 'em-table/utils/table-definition'; +import ColumnDefinition from '../../utils/column-definition'; +import TableDefinition from '../../utils/table-definition'; export default TableController.extend({ diff --git a/tez-ui/src/main/webapp/app/controllers/home/queries.js b/tez-ui/src/main/webapp/app/controllers/home/queries.js index ba7e6e3776..b5e483de6e 100644 --- a/tez-ui/src/main/webapp/app/controllers/home/queries.js +++ b/tez-ui/src/main/webapp/app/controllers/home/queries.js @@ -19,8 +19,8 @@ import Ember from 'ember'; import TableController from '../table'; -import ColumnDefinition from 'em-table/utils/column-definition'; -import TableDefinition from 'em-table/utils/table-definition'; +import ColumnDefinition from '../../utils/column-definition'; +import TableDefinition from '../../utils/table-definition'; export default TableController.extend({ diff --git a/tez-ui/src/main/webapp/app/controllers/query/configs.js b/tez-ui/src/main/webapp/app/controllers/query/configs.js index 8dcc91cd0d..d828088f2e 100644 --- a/tez-ui/src/main/webapp/app/controllers/query/configs.js +++ b/tez-ui/src/main/webapp/app/controllers/query/configs.js @@ -20,7 +20,7 @@ import Ember from 'ember'; import TableController from '../table'; -import ColumnDefinition from 'em-table/utils/column-definition'; +import ColumnDefinition from '../../utils/column-definition'; var MoreObject = more.Object; diff --git a/tez-ui/src/main/webapp/app/controllers/query/timeline.js b/tez-ui/src/main/webapp/app/controllers/query/timeline.js index b52fc26011..a7cd85eca2 100644 --- a/tez-ui/src/main/webapp/app/controllers/query/timeline.js +++ b/tez-ui/src/main/webapp/app/controllers/query/timeline.js @@ -19,7 +19,7 @@ import Ember from 'ember'; import TableController from '../table'; -import ColumnDefinition from 'em-table/utils/column-definition'; +import ColumnDefinition from '../../utils/column-definition'; var MoreObject = more.Object; diff --git a/tez-ui/src/main/webapp/app/controllers/table.js b/tez-ui/src/main/webapp/app/controllers/table.js index 57adf00469..01aec40569 100644 --- a/tez-ui/src/main/webapp/app/controllers/table.js +++ b/tez-ui/src/main/webapp/app/controllers/table.js @@ -20,7 +20,7 @@ import Ember from 'ember'; import AbstractController from './abstract'; -import TableDefinition from 'em-table/utils/table-definition'; +import TableDefinition from '../utils/table-definition'; import isIOCounter from '../utils/misc'; import CounterColumnDefinition from '../utils/counter-column-definition'; diff --git a/tez-ui/src/main/webapp/app/controllers/task/attempts.js b/tez-ui/src/main/webapp/app/controllers/task/attempts.js index a6acaecc84..04eb22ab7c 100644 --- a/tez-ui/src/main/webapp/app/controllers/task/attempts.js +++ b/tez-ui/src/main/webapp/app/controllers/task/attempts.js @@ -17,7 +17,7 @@ */ import MultiTableController from '../multi-table'; -import ColumnDefinition from 'em-table/utils/column-definition'; +import ColumnDefinition from '../../utils/column-definition'; import AutoCounterColumn from '../../mixins/auto-counter-column'; diff --git a/tez-ui/src/main/webapp/app/controllers/vertex/attempts.js b/tez-ui/src/main/webapp/app/controllers/vertex/attempts.js index b07be92e06..107ecdb82b 100644 --- a/tez-ui/src/main/webapp/app/controllers/vertex/attempts.js +++ b/tez-ui/src/main/webapp/app/controllers/vertex/attempts.js @@ -17,7 +17,7 @@ */ import MultiTableController from '../multi-table'; -import ColumnDefinition from 'em-table/utils/column-definition'; +import ColumnDefinition from '../../utils/column-definition'; import AutoCounterColumn from '../../mixins/auto-counter-column'; diff --git a/tez-ui/src/main/webapp/app/controllers/vertex/configs.js b/tez-ui/src/main/webapp/app/controllers/vertex/configs.js index 1cf4a3d31d..2e1d94e647 100644 --- a/tez-ui/src/main/webapp/app/controllers/vertex/configs.js +++ b/tez-ui/src/main/webapp/app/controllers/vertex/configs.js @@ -20,7 +20,7 @@ import Ember from 'ember'; import TableController from '../table'; -import ColumnDefinition from 'em-table/utils/column-definition'; +import ColumnDefinition from '../../utils/column-definition'; var MoreObject = more.Object; diff --git a/tez-ui/src/main/webapp/app/controllers/vertex/tasks.js b/tez-ui/src/main/webapp/app/controllers/vertex/tasks.js index 560c8ba0f9..dac000e247 100644 --- a/tez-ui/src/main/webapp/app/controllers/vertex/tasks.js +++ b/tez-ui/src/main/webapp/app/controllers/vertex/tasks.js @@ -17,7 +17,7 @@ */ import MultiTableController from '../multi-table'; -import ColumnDefinition from 'em-table/utils/column-definition'; +import ColumnDefinition from '../../utils/column-definition'; import AutoCounterColumn from '../../mixins/auto-counter-column'; diff --git a/tez-ui/src/main/webapp/app/styles/app.less b/tez-ui/src/main/webapp/app/styles/app.less index f8a66e376d..44bfb836e0 100644 --- a/tez-ui/src/main/webapp/app/styles/app.less +++ b/tez-ui/src/main/webapp/app/styles/app.less @@ -41,6 +41,7 @@ @import "em-swimlane"; @import "em-tooltip"; @import "em-swimlane-vertex-name"; +@import "em-table.less"; @import "em-table-status-cell"; @import "query-timeline"; @import "home-table-controls"; diff --git a/tez-ui/src/main/webapp/app/styles/em-table-facet-panel.less b/tez-ui/src/main/webapp/app/styles/em-table-facet-panel.less new file mode 100644 index 0000000000..28be8f0ef1 --- /dev/null +++ b/tez-ui/src/main/webapp/app/styles/em-table-facet-panel.less @@ -0,0 +1,218 @@ +/** + * 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. + */ + +.em-table-facet-panel { + width: 160px; + + margin: 5px 5px 0 0; + + border: 1px solid @border-color; + border-radius: @border-radius; + + padding: 5px 10px 10px 10px; + + background-color: @table-bg; + + overflow: hidden; + + .field-filter-box { + width: 100%; + } + + &.hide-filter { + .field-filter-box { + display: none; + } + } + + .filter-message { + color: #999; + } + + h4 { + text-align: center; + margin-top: 5px; + margin-bottom: 0px; + } + + ul { + list-style-type: none; + } + + li { + margin: 2px 0; + } + + ul.field-list { + padding-top: 5px; + padding-left: 0px; + + .em-table-facet-panel-values { + + position: relative; + + .field-name { + .no-select; + + padding-right: 20px; + + cursor: pointer; + display: flex; + + &::before { + content: "\25bc"; + font-size: .7em; + color: @text-light; + margin-top: 5px; + } + + .field-title { + overflow-x: hidden; + text-overflow: ellipsis; + white-space: nowrap; + margin: 0 3px; + } + + .field-count { + color: @text-light; + white-space: nowrap; + } + + .all-button { + margin-left: 5px; + position: absolute; + right: 0px; + } + } + + &.hide-select-all { + .field-name { + padding-right: 0px; + + .all-button { + display: none; + } + } + } + + .value-list { + overflow: hidden; + + padding-left: 10px; + + .filter-box { + width: 100%; + } + + li { + display: flex; + + .checkbox-container { + order: 0; + flex: 0 1 auto; + align-self: auto; + + padding-right: 5px; + } + + .facet-value { + order: 0; + flex: 1 1 auto; + align-self: auto; + padding-right: 2px; + + overflow-x: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + + .only-button { + order: 0; + flex: 0 1 auto; + align-self: auto; + + cursor: pointer; + + display: none; + padding: 0 5px; + } + + .facet-count { + order: 0; + flex: 0 1 auto; + align-self: auto; + + &:hover{ + text-decoration: none; + } + } + + &:hover { + .only-button { + display: inline; + } + } + } + + .pagination-controls { + padding-top: 5px; + + position: relative; + + .arrows { + position: absolute; + top: 5px; + right: 0px; + } + + span { + user-select: none; + color: lightgrey; + + &.active { + cursor: pointer; + color: #3B99FC; + } + } + } + + } + + &.hide-values { + .value-list { + display: none; + } + + .field-name::before { + transform: rotate(-90deg) translate(2px, 2px); + } + + .field-name .all-button { + display: none; + } + } + + &.hide-filter { + .filter-box { + display: none; + } + } + + } + } +} diff --git a/tez-ui/src/main/webapp/app/styles/em-table.less b/tez-ui/src/main/webapp/app/styles/em-table.less new file mode 100644 index 0000000000..d859d78475 --- /dev/null +++ b/tez-ui/src/main/webapp/app/styles/em-table.less @@ -0,0 +1,344 @@ +/** + * 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. + */ + +// Imports +@import (once) "bower_components/bootstrap/less/bootstrap"; + +@import (once) "bower_components/snippet-ss/less/use"; +@import (once) "bower_components/snippet-ss/less/background"; +@import (once) "bower_components/snippet-ss/less/effects"; + +@import "./variables"; +@import "./shared"; + +@import "./search-ui"; +@import "./pagination-ui"; + +@import "./progress-cell"; + +@import "./em-table-facet-panel"; + +.em-table { + font-size: @font-size; + color: @text-color; + + margin: 10px 0px; + overflow: hidden; + + .table-header { + .clear-fix; + } + + .table-mid { + display: flex; + flex-direction: row; + flex-wrap: nowrap; + justify-content: flex-start; + align-content: stretch; + align-items: flex-start; + + .table-panel-left, table-panel-right, .table-body-left, .table-body-right { + order: 0; + flex: 0 1 auto; + align-self: auto; + } + + .table-body, .table-message { + order: 0; + flex: 1 1 auto; + align-self: auto; + border: 1px solid @border-color; + + margin-top: 5px; + } + + &>div:nth-child(2) { + border-top-left-radius: @border-radius; + border-bottom-left-radius: @border-radius; + border-left: 1px solid @border-color; + } + + &>div:nth-last-child(2) { + border-top-right-radius: @border-radius; + border-bottom-right-radius: @border-radius; + border-right: 1px solid @border-color; + } + } + + .table-footer { + .clear-fix; + } + + .table-body-left, .table-body-right, .table-body { + border-top: 1px solid @border-color; + border-bottom: 1px solid @border-color; + background-color: @table-bg; + } + + .table-message { + border-radius: @border-radius; + background-color: @table-bg; + + text-align: center; + padding: 10px; + } + + .table-body-left, .table-body-right, .table-body { + margin: 5px 0px; + } + + .table-body-left, .table-body-right { + white-space: nowrap; + font-size: 0; // If not set, each column will have a space in between + } + + .table-body{ + .force-scrollbar; + + .table-scroll-body { + //Adding this here will keep the column, and table background same white + //making the UI look better when scroll bar is shown + .dotted-bg; + + white-space: nowrap; + font-size: 0; // If not set, each column will have a space in between + } + } + + &.show-scroll-shadow { + .left-scroll-shadow, .right-scroll-shadow { + order: 0; + flex: 0 1 auto; + align-self: stretch; + position: relative; + + opacity: 0; + transition: opacity 0.3s; + + width: 0px; + z-index: 99; + + pointer-events: none; + + .shadow-container { + position: absolute; + overflow: hidden; + + top: 0px; + bottom: 0px; + width: 50px; + + &:before { + content: ""; + position: absolute; + + top: 10px; + bottom: 15px; + width: 50px; + } + } + } + .left-scroll-shadow { + .shadow-container { + &:before { + left: -50px; + box-shadow: 12px 0 40px -4px rgba(0, 0, 0, 0.2); + } + } + } + .right-scroll-shadow { + .shadow-container { + right: 0px; + &:before { + left: 50px; + box-shadow: -12px 0 40px -4px rgba(0, 0, 0, 0.2); + } + } + } + + &.show-left-scroll-shadow { + .left-scroll-shadow { + opacity: 1; + } + } + &.show-right-scroll-shadow { + .right-scroll-shadow { + opacity: 1; + } + } + } + + .table-column { + .use-border-padding-in-width-height; + + background-color: @table-bg; + + vertical-align: top; + overflow: hidden; + display: inline-block; + min-width: 150px; + + &.inner { + border-left: 1px solid @border-color; + } + + // Just the shaded header + .table-header-cell { + background-color: @bg-grey; + border-bottom: 1px solid @border-color; + + &.is-sorting { + .animated-stripes; + } + } + + .header-body, .table-cell { + font-size: @font-size; + white-space: nowrap; + + text-overflow: ellipsis; + overflow: hidden; + + height: 2.1em; + padding: 5px; + + .ember-view { + text-overflow: ellipsis; + overflow: hidden; + } + } + + .header-body { + font-weight: bold; + + padding-right: 1.1em; // To compensate space occupied by sort/resize buttons + position: relative; // So that buttons can be positioned + + .sort-bar { + cursor: pointer; + position: absolute; + + left: 0; + right: .5em; + top: 0; + bottom: 0; + } + + .sort-icon { + cursor: pointer; + position: absolute; + right: .5em; + top: .2em; + + &:before, &:after { + font-size: .7em; + opacity: .5; + position: absolute; + } + + &:before { + content: "\25B2"; + + top: 0em; + right: 0px; + } + + &:after { + content: "\25BC"; + + top: 1em; + right: 0px; + } + + &.asc{ + &:before { + opacity: 1; + } + &:after { + opacity: .5; + } + } + + &.desc { + &:before { + opacity: .5; + } + &:after { + opacity: 1; + } + } + } + + .resize-column:after { + content: "\22EE"; + cursor: col-resize; + opacity: .3; + + position: absolute; + right: 2px; + top: 6px; + } + } + + .table-cell { + position: relative; + + .comment-indicator { + position: absolute; + + color: white; + font-size: 10px; + padding-left: 4px; + + top: -4px; + right: -4px; + + width: 10px; + height: 10px; + background-color: orange; + border-radius: 10px; + + opacity: 0.6; + + &:hover { + top: -2px; + right: -2px; + } + } + + &.bg-transition { + -webkit-transition: box-shadow 500ms ease-out 500ms; + -moz-transition: box-shadow 500ms ease-out 500ms; + -o-transition: box-shadow 500ms ease-out 500ms; + transition: box-shadow 500ms ease-out 500ms; + } + + &.highlight { + box-shadow: 0 0 60px lighten(@brand-primary, 10%) inset; + } + &.is-waiting { + .animated-stripes; + } + &.inner { + border-top: 1px dotted @border-color; + margin-top: -1px; + } + } + } + +} diff --git a/tez-ui/src/main/webapp/app/styles/pagination-ui.less b/tez-ui/src/main/webapp/app/styles/pagination-ui.less new file mode 100644 index 0000000000..df3d7c070f --- /dev/null +++ b/tez-ui/src/main/webapp/app/styles/pagination-ui.less @@ -0,0 +1,88 @@ +/** + * 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. + */ + +.pagination-ui { + .inline-block; + .align-top; + + float: right; + + .page-list { + .inline-block; + .align-top; + + overflow: hidden; + + border: 1px solid @border-color; + border-radius: 5px; + background-color: @table-bg; + + padding: 0px; + + font-size: 0px; + + li { + .inline-block; + + padding: 6px 12px; + height: 32px; + + font-size: @font-size; + color: @text-light; + + border-left: 1px solid @border-color; + + pointer-events: none; + + &.clickable { + pointer-events: auto; + color: @text-color; + + &:hover { + background-color: @bg-grey; + cursor: pointer; + } + } + } + + .total-page-count { + font-size: .8em; + } + + :first-child { + border-left: none; + } + } + + .row-select { + margin-left: 5px; + + display: inline-block; + text-align: center; + + select { + cursor: pointer; + } + } +} + +.table-footer { + .pagination-ui { + position: static; + } +} diff --git a/tez-ui/src/main/webapp/app/styles/progress-cell.less b/tez-ui/src/main/webapp/app/styles/progress-cell.less new file mode 100644 index 0000000000..9030216a53 --- /dev/null +++ b/tez-ui/src/main/webapp/app/styles/progress-cell.less @@ -0,0 +1,26 @@ +/** + * 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. + */ + + +.table-cell { + .em-progress-container { + .progress { + margin: -1px 0 0 0; + } + } +} diff --git a/tez-ui/src/main/webapp/app/styles/search-ui.less b/tez-ui/src/main/webapp/app/styles/search-ui.less new file mode 100644 index 0000000000..b850031c19 --- /dev/null +++ b/tez-ui/src/main/webapp/app/styles/search-ui.less @@ -0,0 +1,37 @@ +/** + * 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. + */ + +.search-ui { + .inline-block; + .align-top; + + max-width: 500px; + + margin-bottom: 2px; + + .type-select { + width: 5em; + margin-right: -3px !important; + } + + .search-syntax { + .label; + .label-default; + .no-select; + } +} diff --git a/tez-ui/src/main/webapp/app/styles/shared.less b/tez-ui/src/main/webapp/app/styles/shared.less index b34cfa6045..b448c30685 100644 --- a/tez-ui/src/main/webapp/app/styles/shared.less +++ b/tez-ui/src/main/webapp/app/styles/shared.less @@ -33,6 +33,99 @@ b { padding-left: 10px; } +.no-select { + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + + cursor: default; +} + +.no-display { + display: none !important; +} + +.no-visible { + visibility: hidden !important; +} + +.no-margin { + margin: 0px !important; +} + +.no-pointer { + pointer-events: none; +} + +.inactive { + .no-pointer; + opacity: 0.4; +} + +.no-wrap { + white-space: nowrap; +} + +.no-border { + border: none !important; +} + +.align-top { + vertical-align: top; +} + +.align-super { + vertical-align: super; +} + +.inline-block { + display: inline-block; +} + +.dotted-background { + background: + radial-gradient(#EEE 15%, transparent 17%) 0 0, + radial-gradient(#EEE 15%, transparent 17%) 5px -5px, + radial-gradient(#EEE 15%, transparent 17%) 5px 5px; + background-color: #DDD; + background-size: 10px 10px; +} + +.absolute { + position: absolute; +} + +.use-gpu { + -webkit-transform: translateZ(0); + -moz-transform: translateZ(0); + -ms-transform: translateZ(0); + -o-transform: translateZ(0); + transform: translateZ(0); +} + +.force-scrollbar { + overflow: auto; + + &::-webkit-scrollbar { + -webkit-appearance: none; + } + &::-webkit-scrollbar:vertical { + width: 11px; + } + &::-webkit-scrollbar:horizontal { + height: 11px; + } + &::-webkit-scrollbar-thumb { + border-radius: 8px; + border: 2px solid #EEE; + background-color: #BBB; + } + &::-webkit-scrollbar-track { + background-color: #EEE; + border-radius: 8px; + } +} + .align-checknradio { input[type=checkbox], input[type=radio] { vertical-align: middle; @@ -41,6 +134,28 @@ b { } } +.left-divider{ + padding-left: 5px; + border-left: 1px solid lightgrey; + margin-left: 5px; +} + +.clear-fix { + &:after { + content: " "; + visibility: hidden; + display: block; + height: 0; + clear: both; + } +} + +.animated-stripes { + .diagonal-stripes-background(#FFF, #EEE); + .animate; + .white-inner-glow; +} + .diagnostics { padding: 10px; white-space: pre-line; diff --git a/tez-ui/src/main/webapp/app/styles/variables.less b/tez-ui/src/main/webapp/app/styles/variables.less new file mode 100644 index 0000000000..e6dbeca7b2 --- /dev/null +++ b/tez-ui/src/main/webapp/app/styles/variables.less @@ -0,0 +1,28 @@ +/** + * 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. + */ + +@text-light: #BBBBBB; +@text-color: #222222; + +@bg-grey: #f0f0f0; +@table-bg: white; + +@border-color: #dcdcdc; +@border-radius: 5px; + +@font-size: 14px; diff --git a/tez-ui/src/main/webapp/app/templates/components/em-table-cell.hbs b/tez-ui/src/main/webapp/app/templates/components/em-table-cell.hbs new file mode 100644 index 0000000000..777be936f4 --- /dev/null +++ b/tez-ui/src/main/webapp/app/templates/components/em-table-cell.hbs @@ -0,0 +1,41 @@ +{{! + * 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. +}} + +{{#if isWaiting}} + Waiting... +{{else}} + {{#if columnDefinition.cellComponentName}} + {{component columnDefinition.cellComponentName content=_cellContent definition=columnDefinition.cellDefinition}} + {{else}} + {{#unless columnDefinition.cellDefinition}} + {{txt _cellContent}} + {{else}} + {{txt _cellContent + type=columnDefinition.cellDefinition.type + format=columnDefinition.cellDefinition.format + timeZone=columnDefinition.cellDefinition.timeZone + valueFormat=columnDefinition.cellDefinition.valueFormat + valueTimeZone=columnDefinition.cellDefinition.valueTimeZone + valueUnit=columnDefinition.cellDefinition.valueUnit + }} + {{/unless}} + {{/if}} + {{#if _comment}} +
+ {{/if}} +{{/if}} diff --git a/tez-ui/src/main/webapp/app/templates/components/em-table-column.hbs b/tez-ui/src/main/webapp/app/templates/components/em-table-column.hbs new file mode 100644 index 0000000000..a3908db67a --- /dev/null +++ b/tez-ui/src/main/webapp/app/templates/components/em-table-column.hbs @@ -0,0 +1,22 @@ +{{! + * 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. +}} + +{{em-table-header-cell title=definition.headerTitle definition=definition tableDefinition=tableDefinition dataProcessor=dataProcessor}} +{{#each rows as |row rowIndex|}} + {{em-table-cell columnDefinition=definition row=row index=rowIndex}} +{{/each}} diff --git a/tez-ui/src/main/webapp/app/templates/components/em-table-facet-panel-values.hbs b/tez-ui/src/main/webapp/app/templates/components/em-table-facet-panel-values.hbs new file mode 100644 index 0000000000..579e9eb2e1 --- /dev/null +++ b/tez-ui/src/main/webapp/app/templates/components/em-table-facet-panel-values.hbs @@ -0,0 +1,50 @@ +{{! + * 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. +}} + +
+
{{data.column.headerTitle}}
+
({{data.facets.length}})
+ All +
+ + diff --git a/tez-ui/src/main/webapp/app/templates/components/em-table-facet-panel.hbs b/tez-ui/src/main/webapp/app/templates/components/em-table-facet-panel.hbs new file mode 100644 index 0000000000..d513786207 --- /dev/null +++ b/tez-ui/src/main/webapp/app/templates/components/em-table-facet-panel.hbs @@ -0,0 +1,33 @@ +{{! + * 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. +}} + +{{#if dataProcessor.facetedFields.length}} + + +
+ + +
+{{else}} +

Not Available!

+{{/if}} diff --git a/tez-ui/src/main/webapp/app/templates/components/em-table-header-cell.hbs b/tez-ui/src/main/webapp/app/templates/components/em-table-header-cell.hbs new file mode 100644 index 0000000000..c48eb7a12a --- /dev/null +++ b/tez-ui/src/main/webapp/app/templates/components/em-table-header-cell.hbs @@ -0,0 +1,30 @@ +{{! + * 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. +}} + +
+ {{title}} + {{#if tableDefinition.enableSort}}{{#if definition.enableSort}} + + {{#if tableDefinition.headerAsSortButton}} + + {{/if}} + {{/if}}{{/if}} + {{#if tableDefinition.enableColumnResize}}{{#if definition.enableColumnResize}} + + {{/if}}{{/if}} +
diff --git a/tez-ui/src/main/webapp/app/templates/components/em-table-linked-cell.hbs b/tez-ui/src/main/webapp/app/templates/components/em-table-linked-cell.hbs new file mode 100644 index 0000000000..8a82631983 --- /dev/null +++ b/tez-ui/src/main/webapp/app/templates/components/em-table-linked-cell.hbs @@ -0,0 +1,41 @@ +{{! + * 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. +}} + +{{#if normalizedLinks.length}} + {{#each normalizedLinks as |link|}} + {{#if link.routeName}} + {{#if link.withModel}} + {{#link-to link.routeName link.model target=definition.target}} + {{link.text}} + {{/link-to}} + {{else}} + {{#link-to link.routeName target=definition.target}} + {{link.text}} + {{/link-to}} + {{/if}} + {{else if link.href}} + + {{link.text}} + + {{else}} + {{link.text}} + {{/if}} + {{/each}} +{{else}} + Not Available! +{{/if}} diff --git a/tez-ui/src/main/webapp/app/templates/components/em-table-pagination-ui.hbs b/tez-ui/src/main/webapp/app/templates/components/em-table-pagination-ui.hbs new file mode 100644 index 0000000000..78edcaece4 --- /dev/null +++ b/tez-ui/src/main/webapp/app/templates/components/em-table-pagination-ui.hbs @@ -0,0 +1,45 @@ +{{! + * 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. +}} + + + +
+ +
diff --git a/tez-ui/src/main/webapp/app/templates/components/em-table-progress-cell.hbs b/tez-ui/src/main/webapp/app/templates/components/em-table-progress-cell.hbs new file mode 100644 index 0000000000..9df8be0360 --- /dev/null +++ b/tez-ui/src/main/webapp/app/templates/components/em-table-progress-cell.hbs @@ -0,0 +1,29 @@ +{{! + * 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. +}} + +{{#if message}} + {{message}} +{{else}} + {{em-progress + value=content + valueMin=_definition.valueMin + valueMax=_definition.valueMax + striped=_definition.striped + style=_definition.style + }} +{{/if}} diff --git a/tez-ui/src/main/webapp/app/templates/components/em-table-search-ui.hbs b/tez-ui/src/main/webapp/app/templates/components/em-table-search-ui.hbs new file mode 100644 index 0000000000..13cb1ddadc --- /dev/null +++ b/tez-ui/src/main/webapp/app/templates/components/em-table-search-ui.hbs @@ -0,0 +1,52 @@ +{{! + * 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. +}} + +
+ {{#if (eq tableDefinition.searchType "manual")}} + + + + {{/if}} + + {{input + type="text" + class="form-control" + placeholder="Search..." + enter="search" + value=text + }} + + + + +
diff --git a/tez-ui/src/main/webapp/app/templates/components/em-table.hbs b/tez-ui/src/main/webapp/app/templates/components/em-table.hbs new file mode 100644 index 0000000000..f7be9d41b3 --- /dev/null +++ b/tez-ui/src/main/webapp/app/templates/components/em-table.hbs @@ -0,0 +1,106 @@ +{{! + * 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. +}} + +{{!--To add CSS rules at runtime!--}} + + +{{!--Header--}} +
+ {{#each headerComponentNames as |componentName|}} + {{component componentName tableDefinition=_definition dataProcessor=_dataProcessor}} + {{/each}} +
+ +
+
+ {{#if leftPanelComponentName}} + {{component leftPanelComponentName tableDefinition=_definition dataProcessor=_dataProcessor}} + {{/if}} +
+ + {{#if message}} +

{{message}}

+ {{else}} + {{!--Body--}} + {{#if _columns.left.length}} +
+ {{#each _columns.left as |column colIndex|}} + {{em-table-column + rows=_dataProcessor.processedRows + definition=column.definition + defaultWidth=column.width + tableDefinition=_definition + dataProcessor=_dataProcessor + index=colIndex + }} + {{/each}} +
+ {{/if}} + + + + +
+
+ {{#each _columns.center as |column colIndex|}} + {{em-table-column + rows=_dataProcessor.processedRows + definition=column.definition + defaultWidth=column.width + tableDefinition=_definition + dataProcessor=_dataProcessor + index=colIndex + }} + {{/each}} +
+
+ + + + + {{#if _columns.right.length}} +
+ {{#each _columns.right as |column colIndex|}} + {{em-table-column + rows=_dataProcessor.processedRows + definition=column.definition + defaultWidth=column.width + tableDefinition=_definition + dataProcessor=_dataProcessor + index=colIndex + }} + {{/each}} +
+ {{/if}} + {{/if}} + +
+ {{#if rightPanelComponentName}} + {{component rightPanelComponentName tableDefinition=_definition dataProcessor=_dataProcessor}} + {{/if}} +
+
+ +{{!--Footer--}} +{{#if displayFooter}} + +{{/if}} diff --git a/tez-ui/src/main/webapp/app/utils/column-definition.js b/tez-ui/src/main/webapp/app/utils/column-definition.js new file mode 100644 index 0000000000..1316866ab1 --- /dev/null +++ b/tez-ui/src/main/webapp/app/utils/column-definition.js @@ -0,0 +1,125 @@ +/** + * 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. + */ + +import Ember from 'ember'; + +import facetTypes from './facet-types'; + +function getContentAtPath(row) { + var contentPath = this.get('contentPath'); + + if(contentPath) { + return Ember.get(row, contentPath); + } + else { + throw new Error("contentPath not set!"); + } +} + +function returnEmptyString() { + return ""; +} + +var ColumnDefinition = Ember.Object.extend({ + id: "", + headerTitle: "Not Available!", + + classNames: [], + + cellComponentName: null, + + enableSearch: true, + enableSort: true, + enableColumnResize: true, + + width: null, + minWidth: "150px", + + contentPath: null, + observePath: false, + + cellDefinition: null, + + pin: "center", + + facetType: facetTypes.VALUES, + + beforeSort: null, + getCellContent: getContentAtPath, + getSearchValue: getContentAtPath, + getSortValue: getContentAtPath, + + init: function () { + if(!this.get("id")) { + throw new Error("ID is not set."); + } + }, +}); + +ColumnDefinition.make = function (rawDefinition) { + if(Array.isArray(rawDefinition)) { + return rawDefinition.map(function (def) { + return ColumnDefinition.create(def); + }); + } + else if(typeof rawDefinition === 'object') { + return ColumnDefinition.create(rawDefinition); + } + else { + throw new Error("rawDefinition must be an Array or an Object."); + } +}; + +ColumnDefinition.makeFromModel = function (ModelClass, columnOptions) { + var attributes = Ember.get(ModelClass, 'attributes'), + columns = []; + if(attributes) { + attributes.forEach(function (meta, name) { + var column = Ember.Object.create({ + id: name, + headerTitle: name.capitalize(), + contentPath: name, + }); + + if(columnOptions) { + column.setProperties(columnOptions); + } + + columns.push(column); + }); + + return ColumnDefinition.make(columns); + } + else { + throw new Error("Value passed is not a model class"); + } +}; + +ColumnDefinition.fillerColumn = ColumnDefinition.create({ + id: "fillerColumn", + headerTitle: "", + getCellContent: returnEmptyString, + getSearchValue: returnEmptyString, + getSortValue: returnEmptyString, + + enableSearch: false, + enableSort: false, + enableColumnResize: false, +}); + +export default ColumnDefinition; diff --git a/tez-ui/src/main/webapp/app/utils/counter-column-definition.js b/tez-ui/src/main/webapp/app/utils/counter-column-definition.js index d66e551eed..5590e10244 100644 --- a/tez-ui/src/main/webapp/app/utils/counter-column-definition.js +++ b/tez-ui/src/main/webapp/app/utils/counter-column-definition.js @@ -19,7 +19,7 @@ import Ember from 'ember'; import isIOCounter from '../utils/misc'; -import ColumnDefinition from 'em-table/utils/column-definition'; +import ColumnDefinition from './column-definition'; /* * Returns a counter value from for a row diff --git a/tez-ui/src/main/webapp/app/utils/data-processor.js b/tez-ui/src/main/webapp/app/utils/data-processor.js new file mode 100644 index 0000000000..07d31c09bf --- /dev/null +++ b/tez-ui/src/main/webapp/app/utils/data-processor.js @@ -0,0 +1,275 @@ +/** + * 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. + */ + +import Ember from 'ember'; + +import SQL from './sql'; + +/** + * Handles Sorting, Searching & Pagination + */ +export default Ember.Object.extend({ + isSorting: false, + isSearching: false, + + tableDefinition: null, + + sql: SQL.create(), + + rows: [], + _sortedRows: [], + _searchedRows: [], + _facetFilteredRows: [], + + _searchObserver: Ember.on("init", Ember.observer('tableDefinition.searchText', 'tableDefinition._actualSearchType', '_sortedRows.[]', function () { + Ember.run.once(this, "startSearch"); + })), + + _sortObserver: Ember.on("init", Ember.observer( + 'tableDefinition.sortColumnId', + 'tableDefinition.sortOrder', + 'rows.[]', function () { + Ember.run.once(this, "startSort"); + })), + + _facetedFilterObserver: Ember.on("init", Ember.observer('tableDefinition.facetConditions', '_searchedRows.[]', function () { + Ember.run.once(this, "startFacetedFilter"); + })), + + regexSearch: function (clause, rows, columns) { + var regex; + + try { + regex = new RegExp(clause, "i"); + } + catch(e) { + regex = new RegExp("", "i"); + } + + function checkRow(column) { + var value; + if(!column.get('enableSearch')) { + return false; + } + value = column.getSearchValue(this); + + if(typeof value === 'string') { + value = value.toLowerCase(); + return value.match(regex); + } + + return false; + } + + return rows.filter(function (row) { + return columns.some(checkRow, row); + }); + }, + + startSearch: function () { + var searchText = String(this.get('tableDefinition.searchText')), + rows = this.get('_sortedRows') || [], + columns = this.get('tableDefinition.columns'), + actualSearchType = this.get('tableDefinition._actualSearchType'), + that = this; + + if(searchText) { + this.set("isSearching", true); + + Ember.run.later(function () { + var result; + + switch(actualSearchType) { + case "SQL": + result = that.get("sql").search(searchText, rows, columns); + break; + + //case "Regex": Commenting as default will be called anyways + default: + result = that.regexSearch(searchText, rows, columns); + break; + } + + that.setProperties({ + _searchedRows: result, + isSearching: false + }); + }); + } + else { + this.set("_searchedRows", rows); + } + }, + + compareFunction: function (a, b){ + // Checking for undefined and null to handle some special cases in JavaScript comparison + // Eg: 1 > undefined = false & 1 < undefined = false + // "a1" > null = false & "a1" < null = false + if(a === undefined || a === null) { + return -1; + } + else if(b === undefined || b === null) { + return 1; + } + else if(a < b) { + return -1; + } + else if(a > b) { + return 1; + } + else { + return 0; + } + }, + + startSort: function () { + var rows = this.get('rows'), + tableDefinition = this.get('tableDefinition'), + sortColumnId = this.get('tableDefinition.sortColumnId'), + descending = this.get('tableDefinition.sortOrder') === 'desc', + that = this, + column; + + if(tableDefinition) { + column = tableDefinition.get('columns').find(function (element) { + return element.get('id') === sortColumnId; + }); + } + + if(rows && Array.isArray(rows.content)) { + rows = rows.toArray(); + } + + if(rows && rows.get('length') > 0 && column) { + this.set('isSorting', true); + + Ember.run.later(function () { + /* + * Creating sortArray as calling getSortValue form inside the + * sort function every time would be more costly. + */ + var sortArray = rows.map(function (row) { + return { + value: column.getSortValue(row), + row: row + }; + }), + compareFunction = that.get("compareFunction"); + + sortArray.sort(function (a, b) { + var result = compareFunction(a.value, b.value); + if(descending && result) { + result = -result; + } + return result; + }); + + that.setProperties({ + _sortedRows: sortArray.map(function (record) { + return record.row; + }), + isSorting: false + }); + }); + } + else { + this.set('_sortedRows', rows); + } + }, + + startFacetedFilter: function () { + var clause = this.get("sql").createFacetClause(this.get('tableDefinition.facetConditions'), this.get("tableDefinition.columns")), + rows = this.get('_searchedRows') || [], + columns = this.get('tableDefinition.columns'), + that = this; + + if(clause && columns) { + this.set("isSearching", true); + + Ember.run.later(function () { + var result = that.get("sql").search(clause, rows, columns); + + that.setProperties({ + _facetFilteredRows: result, + isSearching: false + }); + }); + } + else { + this.set("_facetFilteredRows", rows); + } + }, + + facetedFields: Ember.computed('_searchedRows.[]', 'tableDefinition.columns', function () { + var searchedRows = this.get("_searchedRows"), + columns = this.get('tableDefinition.columns'), + fields = []; + + if(columns) { + columns.forEach(function (column) { + var facetedData; + if(column.facetType) { + facetedData = column.facetType.facetRows(column, searchedRows); + if(facetedData) { + fields.push({ + column: column, + facets: facetedData + }); + } + } + }); + } + + return fields; + }), + + pageDetails: Ember.computed("tableDefinition.rowCount", "tableDefinition.pageNum", "_facetFilteredRows.length", function () { + var tableDefinition = this.get("tableDefinition"), + + pageNum = tableDefinition.get('pageNum'), + rowCount = tableDefinition.get('rowCount'), + + startIndex = (pageNum - 1) * rowCount, + + totalRecords = this.get('_facetFilteredRows.length'); + + if(startIndex < 0) { + startIndex = 0; + } + + return { + pageNum: pageNum, + totalPages: Math.ceil(totalRecords / rowCount), + rowCount: rowCount, + + startIndex: startIndex, + + fromRecord: totalRecords ? startIndex + 1 : 0, + toRecord: Math.min(startIndex + rowCount, totalRecords), + totalRecords: totalRecords + }; + }), + totalPages: Ember.computed.alias("pageDetails.totalPages"), // Adding an alias for backward compatibility + + // Paginate + processedRows: Ember.computed('_facetFilteredRows.[]', 'tableDefinition.rowCount', 'tableDefinition.pageNum', function () { + var rowCount = this.get('tableDefinition.rowCount'), + startIndex = (this.get('tableDefinition.pageNum') - 1) * rowCount; + return this.get('_facetFilteredRows').slice(startIndex, startIndex + rowCount); + }), +}); diff --git a/tez-ui/src/main/webapp/app/utils/facet-types.js b/tez-ui/src/main/webapp/app/utils/facet-types.js new file mode 100644 index 0000000000..0a340bbf7d --- /dev/null +++ b/tez-ui/src/main/webapp/app/utils/facet-types.js @@ -0,0 +1,85 @@ +/** + * 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. + */ + +import Ember from 'ember'; + +var facetTypes = { + VALUES: { + componentName: "em-table-facet-panel-values", + + toClause: function (column, facetConditions) { + var values, clauses = []; + + if(facetConditions) { + if(Ember.get(facetConditions, "in.length")) { + values = facetConditions.in.map(function (value) { + value = value.replace(/'/g, "''"); + return `'${value}'`; + }); + clauses.push(`${column.id} IN (${values})`); + } + + if(Ember.get(facetConditions, "notIn.length")) { + values = facetConditions.notIn.map(function (value) { + value = value.replace(/'/g, "''"); + return `'${value}'`; + }); + clauses.push(`${column.id} NOT IN (${values})`); + } + + return clauses.join(" AND "); + } + }, + + facetRows: function (column, rows) { + var facetedDataHash = {}, + facetedDataArr = []; + + rows.forEach(function (row) { + var value = column.getSearchValue(row); + + if(typeof value === "string") { + if(!facetedDataHash[value]) { + facetedDataHash[value] = { + count: 0, + value: value + }; + facetedDataArr.push(facetedDataHash[value]); + } + facetedDataHash[value].count++; + } + + }); + + if(facetedDataArr.length) { + facetedDataArr = facetedDataArr.sort(function (a, b) { + return -(a.count - b.count); // Sort in reverse order + }); + return facetedDataArr; + } + }, + + normaliseConditions: function (conditions, data) { + if(Ember.get(conditions, "in.length") < data.length) { + return conditions; + } + } + }, +}; + +export default facetTypes; diff --git a/tez-ui/src/main/webapp/app/utils/sql.js b/tez-ui/src/main/webapp/app/utils/sql.js new file mode 100644 index 0000000000..81db3a07f5 --- /dev/null +++ b/tez-ui/src/main/webapp/app/utils/sql.js @@ -0,0 +1,94 @@ +/*global alasql*/ +/** + * 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. + */ + + +import Ember from 'ember'; + +/* + * A wrapper around AlaSQL + */ +export default Ember.Object.extend({ + + constructQuery: function(clause) { + return `SELECT * FROM ? WHERE ${clause}`; + }, + + validateClause: function (clause, columns) { + clause = clause.toString(); + + var query = this.constructQuery(this.normaliseClause(clause, columns || [])), + valid = false; + + if(clause.match(/\W/g)) { // If it contain special characters including space + try { + alasql(query, [[{}]]); + valid = true; + } + catch(e) {} + } + + return valid; + }, + + createFacetClause: function (conditions, columns) { + if(conditions && columns) { + return columns.map(function (column) { + if(column.get("facetType")) { + return column.get("facetType.toClause")(column, conditions[Ember.get(column, "id")]); + } + }).filter(clause => clause).join(" AND "); + } + }, + + normaliseClause: function (clause, columns) { + clause = clause.toString(); + columns.forEach(function (column) { + var headerTitle = column.get("headerTitle"); + clause = clause.replace(new RegExp(`"${headerTitle}"`, "gi"), column.get("id")); + }); + return clause; + }, + + search: function (clause, rows, columns) { + clause = this.normaliseClause(clause, columns); + + // Convert into a form that alasql can digest easily + var dataSet = rows.map(function (row, index) { + var rowObj = { + _index_: index + }; + + columns.forEach(function (column) { + if(column.get("enableSearch") && row) { + rowObj[column.get("id")] = column.getSearchValue(row); + } + }); + + return rowObj; + }); + + // Search + dataSet = alasql(this.constructQuery(clause), [dataSet]); + + return dataSet.map(function (data) { + return rows[data._index_]; + }); + } + +}); diff --git a/tez-ui/src/main/webapp/app/utils/table-definition.js b/tez-ui/src/main/webapp/app/utils/table-definition.js new file mode 100644 index 0000000000..c304ec4e80 --- /dev/null +++ b/tez-ui/src/main/webapp/app/utils/table-definition.js @@ -0,0 +1,61 @@ +/** + * 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. + */ + +import Ember from 'ember'; + +export default Ember.Object.extend({ + + recordType: "", + + // Search + enableSearch: true, + searchText: '', + searchType: 'auto', // Can be either of auto, manual, regex OR sql + _actualSearchType: "Regex", // Set from em-table-search-ui + + // Faceting + enableFaceting: false, + facetConditions: null, + minFieldsForFilter: 15, + minValuesToDisplay: 2, + facetValuesPageSize: 10, + + // Sort + enableSort: true, + sortColumnId: '', + sortOrder: '', + headerAsSortButton: false, + + // Pagination + enablePagination: true, + pageNum: 1, + rowCount: 10, + rowCountOptions: [5, 10, 25, 50, 100], + + enableColumnResize: true, + showScrollShadow: false, + + minRowsForFooter: 25, + + columns: [], + + _pageNumResetObserver: Ember.observer('searchText', 'facetConditions', 'rowCount', function () { + this.set('pageNum', 1); + }), + +}); diff --git a/tez-ui/src/main/webapp/bower.json b/tez-ui/src/main/webapp/bower.json index 56a69f323e..cca56d817a 100644 --- a/tez-ui/src/main/webapp/bower.json +++ b/tez-ui/src/main/webapp/bower.json @@ -1,6 +1,7 @@ { "name": "tez-ui", "dependencies": { + "alasql": "^0.4.0", "ember": "2.2.0", "ember-cli-shims": "0.0.6", "ember-cli-test-loader": "0.2.1", diff --git a/tez-ui/src/main/webapp/ember-cli-build.js b/tez-ui/src/main/webapp/ember-cli-build.js index 7bbc77d334..e4217e9591 100644 --- a/tez-ui/src/main/webapp/ember-cli-build.js +++ b/tez-ui/src/main/webapp/ember-cli-build.js @@ -71,6 +71,7 @@ module.exports = function(defaults) { app.import('bower_components/codemirror/mode/sql/sql.js'); app.import('bower_components/codemirror/mode/pig/pig.js'); app.import('bower_components/codemirror/lib/codemirror.css'); + app.import('bower_components/alasql/dist/alasql.js'); return app.toTree(new MergeTrees([configEnv, zipWorker, copyFonts])); }; diff --git a/tez-ui/src/main/webapp/package.json b/tez-ui/src/main/webapp/package.json index fa80389b94..ad3aa74c5d 100644 --- a/tez-ui/src/main/webapp/package.json +++ b/tez-ui/src/main/webapp/package.json @@ -61,7 +61,6 @@ "phantomjs-prebuilt": "2.1.13" }, "dependencies": { - "em-table": "0.11.3", "em-tgraph": "0.0.14" } } diff --git a/tez-ui/src/main/webapp/tests/integration/components/em-table-cell-test.js b/tez-ui/src/main/webapp/tests/integration/components/em-table-cell-test.js new file mode 100644 index 0000000000..ccf535884e --- /dev/null +++ b/tez-ui/src/main/webapp/tests/integration/components/em-table-cell-test.js @@ -0,0 +1,45 @@ +/** + * 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. + */ + +import Ember from 'ember'; + +import { moduleForComponent, test } from 'ember-qunit'; +import hbs from 'htmlbars-inline-precompile'; + +import ColumnDefinition from '../../../utils/column-definition'; + +moduleForComponent('em-table-cell', 'Integration | Component | em table cell', { + integration: true +}); + +test('Basic rendering test', function(assert) { + var columnDefinition = ColumnDefinition.create({ + id: 'id', + contentPath: 'keyA' + }), + row = Ember.Object.create({ + keyA: 'valueA', + keyB: 'valueB' + }); + + this.set('columnDefinition', columnDefinition); + this.set('row', row); + this.render(hbs`{{em-table-cell columnDefinition=columnDefinition row=row}}`); + + assert.equal(this.$().text().trim(), 'valueA'); +}); diff --git a/tez-ui/src/main/webapp/tests/integration/components/em-table-column-test.js b/tez-ui/src/main/webapp/tests/integration/components/em-table-column-test.js new file mode 100644 index 0000000000..96eff7af2e --- /dev/null +++ b/tez-ui/src/main/webapp/tests/integration/components/em-table-column-test.js @@ -0,0 +1,30 @@ +/** + * 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. + */ + +import { moduleForComponent, test } from 'ember-qunit'; +import hbs from 'htmlbars-inline-precompile'; + +moduleForComponent('em-table-column', 'Integration | Component | em table column', { + integration: true +}); + +test('Basic rendering test', function(assert) { + this.render(hbs`{{em-table-column}}`); + + assert.equal(this.$().text().trim(), ''); +}); diff --git a/tez-ui/src/main/webapp/tests/integration/components/em-table-facet-panel-test.js b/tez-ui/src/main/webapp/tests/integration/components/em-table-facet-panel-test.js new file mode 100644 index 0000000000..cc0f1f0741 --- /dev/null +++ b/tez-ui/src/main/webapp/tests/integration/components/em-table-facet-panel-test.js @@ -0,0 +1,43 @@ +/** + * 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. + */ + +import { moduleForComponent, test } from 'ember-qunit'; +import hbs from 'htmlbars-inline-precompile'; + +moduleForComponent('em-table-facet-panel', 'Integration | Component | em table facet panel', { + integration: true +}); + +test('Basic renders', function(assert) { + + // Set any properties with this.set('myProperty', 'value'); + // Handle any actions with this.on('myAction', function(val) { ... });" + EOL + EOL + + + this.render(hbs`{{em-table-facet-panel}}`); + + assert.equal(this.$().text().replace(/\n|\r\n|\r| /g, '').trim(), 'NotAvailable!'); + + // Template block usage:" + EOL + + this.render(hbs` + {{#em-table-facet-panel}} + template block text + {{/em-table-facet-panel}} + `); + + assert.equal(this.$().text().replace(/\n|\r\n|\r| /g, '').trim(), 'NotAvailable!'); +}); diff --git a/tez-ui/src/main/webapp/tests/integration/components/em-table-facet-panel-values-test.js b/tez-ui/src/main/webapp/tests/integration/components/em-table-facet-panel-values-test.js new file mode 100644 index 0000000000..f401a7da6e --- /dev/null +++ b/tez-ui/src/main/webapp/tests/integration/components/em-table-facet-panel-values-test.js @@ -0,0 +1,44 @@ +/** + * 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. + */ + +import { moduleForComponent, test } from 'ember-qunit'; +import hbs from 'htmlbars-inline-precompile'; + +moduleForComponent('em-table-facet-panel-values', 'Integration | Component | em table facet panel values', { + integration: true +}); + +test('Basic render test', function(assert) { + + // Set any properties with this.set('myProperty', 'value'); + // Handle any actions with this.on('myAction', function(val) { ... });" + EOL + EOL + + + this.set("tmpFacetConditions", {}); + this.render(hbs`{{em-table-facet-panel-values tmpFacetConditions=tmpFacetConditions}}`); + + assert.ok(this.$().text().trim()); + + // Template block usage:" + EOL + + this.render(hbs` + {{#em-table-facet-panel-values tmpFacetConditions=tmpFacetConditions}} + template block text + {{/em-table-facet-panel-values}} + `); + + assert.ok(this.$().text().trim()); +}); diff --git a/tez-ui/src/main/webapp/tests/integration/components/em-table-header-cell-test.js b/tez-ui/src/main/webapp/tests/integration/components/em-table-header-cell-test.js new file mode 100644 index 0000000000..0c502ce9df --- /dev/null +++ b/tez-ui/src/main/webapp/tests/integration/components/em-table-header-cell-test.js @@ -0,0 +1,30 @@ +/** + * 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. + */ + +import { moduleForComponent, test } from 'ember-qunit'; +import hbs from 'htmlbars-inline-precompile'; + +moduleForComponent('em-table-header-cell', 'Integration | Component | em table header cell', { + integration: true +}); + +test('Basic rendering test', function(assert) { + this.render(hbs`{{em-table-header-cell}}`); + + assert.equal(this.$().text().trim(), ''); +}); diff --git a/tez-ui/src/main/webapp/tests/integration/components/em-table-linked-cell-test.js b/tez-ui/src/main/webapp/tests/integration/components/em-table-linked-cell-test.js new file mode 100644 index 0000000000..7553c41014 --- /dev/null +++ b/tez-ui/src/main/webapp/tests/integration/components/em-table-linked-cell-test.js @@ -0,0 +1,30 @@ +/** + * 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. + */ + +import { moduleForComponent, test } from 'ember-qunit'; +import hbs from 'htmlbars-inline-precompile'; + +moduleForComponent('em-table-linked-cell', 'Integration | Component | em table linked cell', { + integration: true +}); + +test('Basic rendering test', function(assert) { + this.render(hbs`{{em-table-linked-cell}}`); + + assert.equal(this.$().text().trim(), 'Not Available!'); +}); diff --git a/tez-ui/src/main/webapp/tests/integration/components/em-table-pagination-ui-test.js b/tez-ui/src/main/webapp/tests/integration/components/em-table-pagination-ui-test.js new file mode 100644 index 0000000000..0333d0cf11 --- /dev/null +++ b/tez-ui/src/main/webapp/tests/integration/components/em-table-pagination-ui-test.js @@ -0,0 +1,204 @@ +/** + * 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. + */ + +import Ember from 'ember'; + +import DataProcessor from '../../../utils/data-processor'; +import TableDefinition from '../../../utils/table-definition'; + +import { moduleForComponent, test } from 'ember-qunit'; +import hbs from 'htmlbars-inline-precompile'; + +moduleForComponent('em-table-pagination-ui', 'Integration | Component | em table pagination ui', { + integration: true +}); + +test('Basic rendering test', function(assert) { + var customRowCount = 25, + definition = TableDefinition.create({ + rowCount: customRowCount + }), + processor; + + Ember.run(function () { + processor = DataProcessor.create({ + tableDefinition: definition, + rows: Ember.A([Ember.Object.create()]) + }); + }); + + this.set('definition', definition); + this.set('processor', processor); + this.render(hbs`{{em-table-pagination-ui tableDefinition=definition dataProcessor=processor}}`); + + var paginationItems = this.$('li'); + assert.equal(paginationItems.length, 1); + assert.equal($(paginationItems[0]).text().trim(), "1"); + + var rowSelection = this.$('select')[0]; + assert.ok(rowSelection); + assert.equal($(rowSelection).val(), customRowCount); +}); + +test('No data test', function(assert) { + var customRowCount = 2, + definition = TableDefinition.create({ + rowCount: customRowCount + }), + processor; + + Ember.run(function () { + processor = DataProcessor.create({ + tableDefinition: definition, + rows: Ember.A() + }); + }); + + this.set('definition', definition); + this.set('processor', processor); + this.render(hbs`{{em-table-pagination-ui tableDefinition=definition dataProcessor=processor}}`); + + var paginationItems = this.$('li'); + assert.equal(paginationItems.length, 0); +}); + +test('Multiple page test; without first & last', function(assert) { + var customRowCount = 2, + definition = TableDefinition.create({ + rowCount: customRowCount + }), + processor; + + Ember.run(function () { + processor = DataProcessor.create({ + tableDefinition: definition, + rows: Ember.A([Ember.Object.create(), Ember.Object.create(), Ember.Object.create()]) + }); + }); + + this.set('definition', definition); + this.set('processor', processor); + this.render(hbs`{{em-table-pagination-ui tableDefinition=definition dataProcessor=processor}}`); + + var paginationItems = this.$('li'); + assert.equal(paginationItems.length, 2); + assert.equal($(paginationItems[0]).text().trim(), "1"); + assert.equal($(paginationItems[1]).text().trim(), "2"); +}); + +test('Display last test', function(assert) { + var customRowCount = 5, + definition = TableDefinition.create({ + rowCount: customRowCount + }), + processor, + rows = []; + + for(var i = 0; i < 100; i++) { + rows.push(Ember.Object.create()); + } + + Ember.run(function () { + processor = DataProcessor.create({ + tableDefinition: definition, + rows: Ember.A(rows) + }); + }); + + this.set('definition', definition); + this.set('processor', processor); + this.render(hbs`{{em-table-pagination-ui tableDefinition=definition dataProcessor=processor}}`); + + var paginationItems = this.$('li'); + assert.equal(paginationItems.length, 6); + assert.equal($(paginationItems[0]).text().trim(), "1"); + assert.equal($(paginationItems[1]).text().trim(), "2"); + assert.equal($(paginationItems[2]).text().trim(), "3"); + assert.equal($(paginationItems[3]).text().trim(), "4"); + assert.equal($(paginationItems[4]).text().trim(), "5"); + assert.equal($(paginationItems[5]).text().trim(), "Last - 20"); +}); + +test('Display first test', function(assert) { + var customRowCount = 5, + definition = TableDefinition.create({ + pageNum: 20, + rowCount: customRowCount + }), + processor, + rows = []; + + for(var i = 0; i < 100; i++) { + rows.push(Ember.Object.create()); + } + + Ember.run(function () { + processor = DataProcessor.create({ + tableDefinition: definition, + rows: Ember.A(rows) + }); + }); + + this.set('definition', definition); + this.set('processor', processor); + this.render(hbs`{{em-table-pagination-ui tableDefinition=definition dataProcessor=processor}}`); + + var paginationItems = this.$('li'); + assert.equal(paginationItems.length, 6); + assert.equal($(paginationItems[0]).text().trim(), "First"); + assert.equal($(paginationItems[1]).text().trim(), "16"); + assert.equal($(paginationItems[2]).text().trim(), "17"); + assert.equal($(paginationItems[3]).text().trim(), "18"); + assert.equal($(paginationItems[4]).text().trim(), "19"); + assert.equal($(paginationItems[5]).text().trim(), "20"); +}); + +test('Display first & last test', function(assert) { + var customRowCount = 5, + definition = TableDefinition.create({ + pageNum: 10, + rowCount: customRowCount + }), + processor, + rows = []; + + for(var i = 0; i < 100; i++) { + rows.push(Ember.Object.create()); + } + + Ember.run(function () { + processor = DataProcessor.create({ + tableDefinition: definition, + rows: Ember.A(rows) + }); + }); + + this.set('definition', definition); + this.set('processor', processor); + this.render(hbs`{{em-table-pagination-ui tableDefinition=definition dataProcessor=processor}}`); + + var paginationItems = this.$('li'); + assert.equal(paginationItems.length, 7); + assert.equal($(paginationItems[0]).text().trim(), "First"); + assert.equal($(paginationItems[1]).text().trim(), "8"); + assert.equal($(paginationItems[2]).text().trim(), "9"); + assert.equal($(paginationItems[3]).text().trim(), "10"); + assert.equal($(paginationItems[4]).text().trim(), "11"); + assert.equal($(paginationItems[5]).text().trim(), "12"); + assert.equal($(paginationItems[6]).text().trim(), "Last - 20"); +}); diff --git a/tez-ui/src/main/webapp/tests/integration/components/em-table-progress-cell-test.js b/tez-ui/src/main/webapp/tests/integration/components/em-table-progress-cell-test.js new file mode 100644 index 0000000000..b7eced31d6 --- /dev/null +++ b/tez-ui/src/main/webapp/tests/integration/components/em-table-progress-cell-test.js @@ -0,0 +1,43 @@ +/** + * 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. + */ + +import { moduleForComponent, test } from 'ember-qunit'; +import hbs from 'htmlbars-inline-precompile'; + +moduleForComponent('em-table-progress-cell', 'Integration | Component | em table progress cell', { + integration: true +}); + +test('Basic creation test', function(assert) { + + // Set any properties with this.set('myProperty', 'value'); + // Handle any actions with this.on('myAction', function(val) { ... });" + EOL + EOL + + + this.render(hbs`{{em-table-progress-cell content=0.5}}`); + + assert.equal(this.$().text().trim(), '50%'); + + // Template block usage:" + EOL + + this.render(hbs` + {{#em-table-progress-cell content=0.5}} + template block text + {{/em-table-progress-cell}} + `); + + assert.equal(this.$().text().trim(), '50%'); +}); diff --git a/tez-ui/src/main/webapp/tests/integration/components/em-table-search-ui-test.js b/tez-ui/src/main/webapp/tests/integration/components/em-table-search-ui-test.js new file mode 100644 index 0000000000..0cd2bbca15 --- /dev/null +++ b/tez-ui/src/main/webapp/tests/integration/components/em-table-search-ui-test.js @@ -0,0 +1,30 @@ +/** + * 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. + */ + +import { moduleForComponent, test } from 'ember-qunit'; +import hbs from 'htmlbars-inline-precompile'; + +moduleForComponent('em-table-search-ui', 'Integration | Component | em table search ui', { + integration: true +}); + +test('Basic rendering test', function(assert) { + this.render(hbs`{{em-table-search-ui}}`); + + assert.equal(this.$().text().trim(), 'Search'); +}); diff --git a/tez-ui/src/main/webapp/tests/integration/components/em-table-test.js b/tez-ui/src/main/webapp/tests/integration/components/em-table-test.js new file mode 100644 index 0000000000..96baf79fa9 --- /dev/null +++ b/tez-ui/src/main/webapp/tests/integration/components/em-table-test.js @@ -0,0 +1,48 @@ +/** + * 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. + */ + +import { moduleForComponent, test } from 'ember-qunit'; +import hbs from 'htmlbars-inline-precompile'; + +import TableDefinition from '../../../utils/table-definition'; +import ColumnDefinition from '../../../utils/column-definition'; + +moduleForComponent('em-table', 'Integration | Component | em table', { + integration: true +}); + +test('Basic rendering test', function(assert) { + this.render(hbs`{{em-table}}`); + + assert.equal(this.$('.table-message').text().trim(), 'No columns available!'); +}); + +test('Records missing test', function(assert) { + var definition = TableDefinition.create({ + recordType: "vertex" + }); + + this.set("columns", [ColumnDefinition.fillerColumn]); + + this.render(hbs`{{em-table columns=columns}}`); + assert.equal(this.$('.table-message').text().trim(), 'No records available!'); + + this.set("definition", definition); + this.render(hbs`{{em-table columns=columns definition=definition}}`); + assert.equal(this.$('.table-message').text().trim(), 'No vertices available!'); +}); diff --git a/tez-ui/src/main/webapp/tests/integration/em-table-status-cell-test.js b/tez-ui/src/main/webapp/tests/integration/em-table-status-cell-test.js new file mode 100644 index 0000000000..31483395e9 --- /dev/null +++ b/tez-ui/src/main/webapp/tests/integration/em-table-status-cell-test.js @@ -0,0 +1,40 @@ +/** + * 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. + */ + +import { moduleForComponent, test } from 'ember-qunit'; +import hbs from 'htmlbars-inline-precompile'; + +moduleForComponent('em-table-status-cell', 'Integration | Component | em table status cell', { + integration: true +}); + +test('Basic creation test', function(assert) { + + this.render(hbs`{{em-table-status-cell}}`); + + assert.equal(this.$().text().trim(), 'Not Available!'); + + // Template block usage:" + EOL + + this.render(hbs` + {{#em-table-status-cell}} + template block text + {{/em-table-status-cell}} + `); + + assert.equal(this.$().text().trim(), 'Not Available!'); +}); diff --git a/tez-ui/src/main/webapp/tests/unit/utils/column-definition-test.js b/tez-ui/src/main/webapp/tests/unit/utils/column-definition-test.js new file mode 100644 index 0000000000..5ee9a49023 --- /dev/null +++ b/tez-ui/src/main/webapp/tests/unit/utils/column-definition-test.js @@ -0,0 +1,104 @@ +/** + * 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. + */ + +import Ember from 'ember'; +import ColumnDefinition from '../../../utils/column-definition'; +import { module, test } from 'qunit'; + +module('Unit | Utility | column definition'); + +test('Class creation test', function(assert) { + assert.ok(ColumnDefinition); + + assert.ok(ColumnDefinition.make); + assert.ok(ColumnDefinition.makeFromModel); +}); + +test('make - Instance creation test', function(assert) { + + var definition = ColumnDefinition.make({ + id: "testId" + }); + var definitions = ColumnDefinition.make([{ + id: "testId 1" + },{ + id: "testId 2" + }]); + + // Single + assert.ok(definition); + + // Multiple + assert.ok(definitions); + assert.ok(Array.isArray(definitions)); + assert.equal(definitions.length, 2); +}); + +test('make - Instance creation failure test', function(assert) { + assert.throws(function () { + ColumnDefinition.make({}); + }); +}); + +test('makeFromModel test', function(assert) { + var attributes = Ember.Map.create(), + DummyModel = Ember.Object.create({ + attributes: attributes + }), + getCellContent = function () {}, + columns; + + attributes.set("attr1", "path1"); + attributes.set("attr2", "path2"); + attributes.set("attr3", "path3"); + + columns = ColumnDefinition.makeFromModel(DummyModel, { + getCellContent: getCellContent + }); + + assert.equal(columns.length, 3); + assert.equal(columns[0].id, "attr1"); + assert.equal(columns[0].headerTitle, "Attr1"); + assert.equal(columns[0].contentPath, "attr1"); + assert.equal(columns[0].getCellContent, getCellContent); +}); + +test('Instance test', function(assert) { + var definition = ColumnDefinition.make({ + id: "testId", + contentPath: "a.b" + }); + var data = Ember.Object.create({ + a: { + b: 42 + } + }); + + assert.ok(definition.getCellContent); + assert.ok(definition.getSearchValue); + assert.ok(definition.getSortValue); + + assert.equal(definition.id, "testId"); + assert.equal(definition.headerTitle, "Not Available!"); + assert.equal(definition.minWidth, "150px"); + assert.equal(definition.contentPath, "a.b"); + + assert.equal(definition.getCellContent(data), 42); + assert.equal(definition.getSearchValue(data), 42); + assert.equal(definition.getSortValue(data), 42); +}); diff --git a/tez-ui/src/main/webapp/tests/unit/utils/data-processor-test.js b/tez-ui/src/main/webapp/tests/unit/utils/data-processor-test.js new file mode 100644 index 0000000000..58f52dd013 --- /dev/null +++ b/tez-ui/src/main/webapp/tests/unit/utils/data-processor-test.js @@ -0,0 +1,137 @@ +/** + * 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. + */ + +import Ember from 'ember'; + +import DataProcessor from '../../../utils/data-processor'; +import ColumnDefinition from '../../../utils/column-definition'; +import { module, test } from 'qunit'; + +module('Unit | Utility | data processor'); + +test('Class creation test', function(assert) { + assert.ok(DataProcessor); +}); + +test('Instance default test', function(assert) { + var processor; + + Ember.run(function () { + processor = DataProcessor.create({ + tableDefinition: Ember.Object.create(), + startSearch: function () { + // Test Search + }, + startSort: function () { + // Test Sort + } + }); + }); + + assert.ok(processor); + assert.equal(processor.get('isSorting'), false); + assert.equal(processor.get('isSearching'), false); + + assert.ok(processor._searchObserver); + assert.ok(processor._sortObserver); + assert.ok(processor.startSearch); + assert.ok(processor.startSort); + assert.ok(processor.compareFunction); + assert.ok(processor.totalPages); + assert.ok(processor.processedRows); +}); + +test('compareFunction test', function(assert) { + var processor; + + Ember.run(function () { + processor = DataProcessor.create({ + tableDefinition: Ember.Object.create(), + startSearch: function () {}, + startSort: function () {} + }); + }); + + assert.equal(processor.compareFunction(1, 1), 0); + assert.equal(processor.compareFunction(1, 2), -1); + assert.equal(processor.compareFunction(2, 1), 1); + + assert.equal(processor.compareFunction("a", "a"), 0); + assert.equal(processor.compareFunction("a", "b"), -1); + assert.equal(processor.compareFunction("b", "a"), 1); + + assert.equal(processor.compareFunction(null, null), -1); + assert.equal(processor.compareFunction(1, null), 1); + assert.equal(processor.compareFunction(null, 2), -1); + assert.equal(processor.compareFunction("a", null), 1); + assert.equal(processor.compareFunction(null, "b"), -1); + + assert.equal(processor.compareFunction(undefined, undefined), -1); + assert.equal(processor.compareFunction(1, undefined), 1); + assert.equal(processor.compareFunction(undefined, 2), -1); + assert.equal(processor.compareFunction("a", undefined), 1); + assert.equal(processor.compareFunction(undefined, "b"), -1); +}); + +test('startSearch test', function(assert) { + var processor, + runLater = Ember.run.later; + + assert.expect(3); + + Ember.run.later = function (callback) { + callback(); + assert.equal(processor.get("_searchedRows.length"), 2); + assert.equal(processor.get("_searchedRows.0.foo"), "Foo1"); + assert.equal(processor.get("_searchedRows.1.foo"), "Foo12"); + + Ember.run.later = runLater; // Reset + }; + + Ember.run(function () { + processor = DataProcessor.create({ + tableDefinition: Ember.Object.create({ + searchText: "foo1", + columns: [ColumnDefinition.create({ + id: "foo", + contentPath: 'foo' + }), ColumnDefinition.create({ + id: "bar", + contentPath: 'bar' + })] + }), + startSort: function () { + // Test Sort + }, + _sortedRows: [Ember.Object.create({ + foo: "Foo1", + bar: "Bar1" + }), Ember.Object.create({ + foo: "Foo12", + bar: "Bar2" + }), Ember.Object.create({ + foo: "Foo3", + bar: "Bar3" + }), Ember.Object.create({ + foo: "Foo4", + bar: "Bar4" + })], + }); + }); + +}); diff --git a/tez-ui/src/main/webapp/tests/unit/utils/facet-types-test.js b/tez-ui/src/main/webapp/tests/unit/utils/facet-types-test.js new file mode 100644 index 0000000000..f3af95249f --- /dev/null +++ b/tez-ui/src/main/webapp/tests/unit/utils/facet-types-test.js @@ -0,0 +1,28 @@ +/** + * 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. + */ + +import facetTypes from '../../../utils/facet-types'; +import { module, test } from 'qunit'; + +module('Unit | Utility | facet types'); + +test('Basic creation test', function(assert) { + assert.ok(facetTypes); + + assert.ok(facetTypes.VALUES); +}); diff --git a/tez-ui/src/main/webapp/tests/unit/utils/sql-test.js b/tez-ui/src/main/webapp/tests/unit/utils/sql-test.js new file mode 100644 index 0000000000..7aed218801 --- /dev/null +++ b/tez-ui/src/main/webapp/tests/unit/utils/sql-test.js @@ -0,0 +1,90 @@ +/** + * 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. + */ + +import SQL from '../../../utils/sql'; +import ColumnDefinition from '../../../utils/column-definition'; +import { module, test } from 'qunit'; + +module('Unit | Utility | sql'); + +test('Class creation test', function(assert) { + var sql = SQL.create(); + + assert.ok(sql.constructQuery); + assert.ok(sql.validateClause); + assert.ok(sql.normaliseClause); + assert.ok(sql.search); +}); + +test('constructQuery test', function(assert) { + var sql = SQL.create(); + + assert.equal(sql.constructQuery("x = y"), "SELECT * FROM ? WHERE x = y"); +}); + +test('validateClause test', function(assert) { + var sql = SQL.create(); + + assert.ok(sql.validateClause("x = y")); + assert.ok(sql.validateClause("x = y AND a = b")); + assert.ok(sql.validateClause("(x = y OR y = z) AND a = b")); + assert.ok(sql.validateClause("x BETWEEN 1 AND 2")); + + assert.notOk(sql.validateClause("foo")); + assert.notOk(sql.validateClause("foo bar")); + assert.notOk(sql.validateClause("^[a-z0-9_-]{3,16}$")); + assert.notOk(sql.validateClause("^[a-z0-9_-]{6,18}$")); + assert.notOk(sql.validateClause("^[a-z0-9-]+$")); +}); + +test('normaliseClause test', function(assert) { + var sql = SQL.create(), + column = ColumnDefinition.create({ + headerTitle: "Column Header", + id: "columnID", + contentPath: "col" + }); + + assert.equal(sql.normaliseClause('"Column Header" = value', [column]), "columnID = value"); + assert.equal(sql.normaliseClause('"Another Column Header" = value', [column]), '"Another Column Header" = value'); +}); + +test('search test', function(assert) { + var sql = SQL.create(), + data = [{ + colA: "x1", + colB: "y1" + }, { + colA: "x2", + colB: "y2" + }, { + colA: "x1", + colB: "y3" + }], + columns = [ColumnDefinition.create({ + headerTitle: "Column A", + id: "colA", + contentPath: "colA" + })]; + + var result = sql.search('"Column A" = "x1"', data, columns); + + assert.equal(result.length, 2); + assert.equal(result[0].colB, "y1"); + assert.equal(result[1].colB, "y3"); +}); diff --git a/tez-ui/src/main/webapp/tests/unit/utils/table-definition-test.js b/tez-ui/src/main/webapp/tests/unit/utils/table-definition-test.js new file mode 100644 index 0000000000..234994b192 --- /dev/null +++ b/tez-ui/src/main/webapp/tests/unit/utils/table-definition-test.js @@ -0,0 +1,52 @@ +/** + * 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. + */ + +import TableDefinition from '../../../utils/table-definition'; +import { module, test } from 'qunit'; + +module('Unit | Utility | table definition'); + +test('Class creation test', function(assert) { + assert.ok(TableDefinition); +}); + +test('Default instance test', function(assert) { + var definition = TableDefinition.create(); + + assert.ok(definition); + + assert.equal(definition.pageNum, 1); + assert.equal(definition.rowCount, 10); + assert.equal(definition.minRowsForFooter, 25); +}); + +test('Page-num reset test', function(assert) { + var definition = TableDefinition.create(); + + assert.equal(definition.pageNum, 1); + + definition.set("pageNum", 5); + assert.equal(definition.pageNum, 5); + + definition.set("searchText", "x"); + assert.equal(definition.pageNum, 1); + + definition.set("pageNum", 5); + definition.set("rowCount", 5); + assert.equal(definition.pageNum, 1); +}); diff --git a/tez-ui/src/main/webapp/yarn.lock b/tez-ui/src/main/webapp/yarn.lock index 00250e82cf..660ac80d87 100644 --- a/tez-ui/src/main/webapp/yarn.lock +++ b/tez-ui/src/main/webapp/yarn.lock @@ -1391,16 +1391,6 @@ ee-first@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" -em-table@0.11.3: - version "0.11.3" - resolved "https://registry.yarnpkg.com/em-table/-/em-table-0.11.3.tgz#20e605cc3814214e644199399a2383cee8d23eeb" - dependencies: - ember-cli-htmlbars "^1.0.1" - ember-cli-less "^1.4.0" - source-map "^0.5.6" - optionalDependencies: - phantomjs-prebuilt "2.1.13" - em-tgraph@0.0.14: version "0.0.14" resolved "https://registry.yarnpkg.com/em-tgraph/-/em-tgraph-0.0.14.tgz#4d48b911760f85dec41904e4056ec52542391cc1"