logo

18 нояб. 2012 г.

BIEE 11g: Copy-Paste

Наверняка, многие сталкивались с трудностями копирования значений из ячеек отчетов BI.
Иногда это бывает необходимо - например, для ручного тестирования результатов отчета.
Существуют различные окольные решения:
1) Использовать выгрузку результатов отчета в Excel - но это долго.
2) Использовать Chrome-браузер, который позволяет выделять и копировать значения ячеек в BI отчетах - но BIEE не всегда корректно отображается под Chrome (проблема с графиками/диаграммами).
3) Есть и другие ухищрения, требующие настройки каждого отчета (например, через ActionsFramework)...

Я хочу предложить вам более элегантный вариант - осуществление копирования с помощью контекстного меню.



Как видно из рисунка выше - по щелчку правой кнопкой мыши на ячейку, значение которой вы хотите скопировать, отображается контекстное меню с пользовательским элементом -
"Copy (Ctrl-C)"
Нажав на который отобразиться модальное окошко с необходимым значением. Теперь достаточно нажать комбинацию клавиш Сtrl-C, и искомое значение в буфере обмена.
Причем эта настройка сделана для всех элементов BI отчета - для ячеек данных, для ячеек заголовков столбцов, для ячеек значений иерархических столбцов.


Итак, что нужно сделать для описанного функционала.
/* Сразу скажу, что эта кастомизация затрагивает javascript-файлы, которые, УВЕРЕН, будут правиться Oracle в каждом серьезном патче/релизе. Поэтому взвесьте ЗА и ПРОТИВ. И если преимущества перевешивают недостатки, то обязательно делайте бекапы, и при накате каждого патча сравнивайте (например, SVN-diff'ом) подправленные файлы с новыми версиями этих файлов
*/

Далее все подправленные javascript-файлы приводятся в beauty-формате, тогда как оригинальные js-файлы OBIEE "ужаты" (удалены все лишние пробелы, символы табуляции, перевода каретки - короче говоря, все что делает код читабельным).
Я для "расжатия" использую онлайн-ресурс jsbeautifier.org/

b_mozilla\views\obips.viewmodel.js
1) добавим константу функции копирования
...
    obips.ViewModel.EventType = new Object();
    obips.ViewModel.EventType.NO_EVENT = 0;
    obips.ViewModel.EventType.PIVOT_SWAP_EVENT = 1;
    obips.ViewModel.EventType.SELECTION_EVENT = 2;
    obips.ViewModel.EventType.DROP_COLUMN_MEMBER_EVENT = 3;
    obips.ViewModel.EventType.PRIMARY_SORT_ASC_MENU_EVENT = 4;
    obips.ViewModel.EventType.PRIMARY_SORT_DESC_MENU_EVENT = 5;
    obips.ViewModel.EventType.ADD_SORT_ASC_MENU_EVENT = 6;
    obips.ViewModel.EventType.ADD_SORT_DESC_MENU_EVENT = 7;
    obips.ViewModel.EventType.CLEAR_ALL_SORTS_MENU_EVENT = 8;
    obips.ViewModel.EventType.REGULAR_DRILL_MENU_EVENT = 9;
    obips.ViewModel.EventType.HIER_EXPAND_MENU_EVENT = 10;
    obips.ViewModel.EventType.HIER_COLLAPSE_MENU_EVENT = 11;
    obips.ViewModel.EventType.MASTER_DETAIL_MENU_EVENT = 12;
    obips.ViewModel.EventType.REMOVE_COLUMN_MENU_EVENT = 13;
    obips.ViewModel.EventType.MOVE_BEFORE_MENU_EVENT = 14;
    obips.ViewModel.EventType.MOVE_AFTER_MENU_EVENT = 15;
    obips.ViewModel.EventType.MOVE_TO_PROMPTS_MENU_EVENT = 16;
    obips.ViewModel.EventType.MOVE_TO_SECTIONS_MENU_EVENT = 17;
    obips.ViewModel.EventType.MOVE_TO_COLUMNS_MENU_EVENT = 18;
    obips.ViewModel.EventType.MOVE_TO_ROWS_MENU_EVENT = 19;
    obips.ViewModel.EventType.MOVE_TO_MEASURES_MENU_EVENT = 20;
    obips.ViewModel.EventType.HIER_COLLAPSE_ALL_MENU_EVENT = 21;
    obips.ViewModel.EventType.HIER_COLLAPSE_LAYER_MENU_EVENT = 22;
    obips.ViewModel.EventType.EXCLUDE_COLUMN_MENU_EVENT = 23;
    obips.ViewModel.EventType.VIEW_CUSTOM_GROUP_EVENT = 24;
    obips.ViewModel.EventType.VIEW_CALCITEM_EVENT = 25;
    obips.ViewModel.EventType.INCLUDE_COLUMN_MENU_EVENT = 26;
    obips.ViewModel.EventType.SELN_SELECT_MEMBERS_MENU_EVENT = 27;
    obips.ViewModel.EventType.SELN_HIER_RELATED_MENU_EVENT = 28;
    obips.ViewModel.EventType.CREATE_CUSTOM_GROUP_MENU_EVENT = 29;
    obips.ViewModel.EventType.CREATE_CALC_ITEM_MENU_EVENT = 30;
    obips.ViewModel.EventType.SELN_COL_INTERACTION_MENU_EVENT = 31;
    obips.ViewModel.EventType.DRILL_REG_COL_MENU_EVENT = 32;
    obips.ViewModel.EventType.INCL_HIER_FAMILY_REL_MENU_EVENT = 33;
    obips.ViewModel.EventType.SHOW_HIER_LEVELS_MENU_EVENT = 34;
    obips.ViewModel.EventType.SELN_TOPX_EVENT = 35;
    obips.ViewModel.EventType.SELN_BOTTOMX_EVENT = 36;
    obips.ViewModel.EventType.SELN_ADD_MEMBERS = 37;
    obips.ViewModel.EventType.SELN_ADD_CUSTOM_CALC_ITEM = 38;
    obips.ViewModel.EventType.SELN_XGTVALUE_MENU_EVENT = 39;
    obips.ViewModel.EventType.SELN_XGTY_MENU_EVENT = 40;
    obips.ViewModel.EventType.SELN_SPECIFIC_MEMBERS = 41;
    obips.ViewModel.EventType.SELN_CUSTOM_CONDITION_MENU_EVENT = 42;
    obips.ViewModel.EventType.RUNNING_SUM_COLUMN_MENU_EVENT = 43;
    obips.ViewModel.EventType.SHOW_SUBTOTAL_MENU_EVENT = 44;
    obips.ViewModel.EventType.EDIT_CUSTOM_GROUP_MENU_EVENT = 45;
    obips.ViewModel.EventType.DELETE_CUSTOM_GROUP_MENU_EVENT = 46;
    obips.ViewModel.EventType.EDIT_CALCITEM_MENU_EVENT = 47;
    obips.ViewModel.EventType.DELETE_CALCITEM_MENU_EVENT = 48;
    obips.ViewModel.EventType.SHOW_GRANDTOTAL_MENU_EVENT = 49;
    obips.ViewModel.EventType.SELN_REMOVE_ALL_STEPS = 50;
    obips.ViewModel.EventType.HIDE_COLUMN = 51;
//jack carver start
    obips.ViewModel.EventType.XX_COPY_CELL_VALUE = 100;
//jack carver end
...

2) добавим обработку нажатия на создаваемый элемент контекстного меню
...
    obips.ViewModel.onContextMenuItemSelected = function (d) {
        var e = d.context;
        var b = e.menuOption;
        var a = b.eventType;
        var c = b.eventData;
        if (!a || !c) {
            return
        }
        obips.ViewModel.ptCurrentEvent.eventType = a;
        obips.ViewModel.ptCurrentEvent.eventData = c;
        switch (a) {
//jack carver start
            case obips.ViewModel.EventType.XX_COPY_CELL_VALUE:
                var b = obips.EdgeCoords.findCoords(c.getElement());
                var a = b.getId();
                var f = b.getEdge();
                var d = b.getLayer();
                var g = b.getSlice();
                if (g == -1) 
                    var eId = "hl_" + a + "_" + f + "_" + d;
                else
                    var eId = "e_" + a + "_" + f + "_" + d + "_" + g;

                var cell = document.getElementById(eId);

                var cellValue = cell.innerHTML;
                if (cellValue) {
                    cellValue = cellValue.toString().replace(/\s*\<.*?\>\s*/g, '');

                    cellValue = cellValue.toString().replace(/&lt;/g, '<');
                    cellValue = cellValue.toString().replace(/&gt;/g, '>');
                    cellValue = cellValue.toString().replace(/&quot;/g, '"');
                    cellValue = cellValue.toString().replace(/&nbsp;/g, ' ');
                }

                window.prompt('To copy -> Ctrl+C', cellValue );
                break;
//jack carver end
            case obips.ViewModel.EventType.PRIMARY_SORT_ASC_MENU_EVENT:
                obips.ViewModel.sortMenuHandler(c, "ascending", true, b.pos, b.slice);
                break;
...


b_mozilla\views\viewrightclickmenu.js
1) добавим новый пункт меню для простых Таблиц
...
    obips.ViewRightClickMenu.prototype.onEdgeContextMenu = function (b, r) {
        var i = new Array();
        var l = obips.ViewModel.EventType.NO_EVENT;
        var w = "views/obips.viewrightclickmenu.xml";
        var n = obips.ResourceManager.getSingleton();
        if (b) {
            var f = b;
            b.bDatabody = false;
            var h = b.getEdge();
            var G = b.getLayer();
            var t = b.getSlice();
            var H = b.getId();
            var z = this.viewModel;
            G = this.viewModel.getPhysicalLayer(h, G);
            var g = this.viewModel.getEdgeDefinition(b.getId());
            var d = g.isMeasureLabelsLayer(h, G);
            var s = g.isMeasureLayer(h, G);
            var x = this.isHeaderLabelCell(t);
            var j = this.viewModel.getLayerCount(h);
            if (j == 0) {
                return
            }
            var E = "";
            var k = g.isLayerHierarchical(h, G);
            var u = obips.ViewRightClickMenu.isRuntimeMode();
//jack carver start
            E = "Copy (Ctrl-C)";
            l = obips.ViewModel.EventType.XX_COPY_CELL_VALUE
            var p = obips.ViewRightClickMenu.CreateContextMenuOption(E, E, null, null, l, f);
            i.push(p)
//jack carver end
            var D = this.supportsFeature(obips.ViewRightClickMenu.FEATURE_DRILL);
...


b_mozilla\views\pivot\pivotview.js
1) добавим новый пункт меню для Таблиц Среза
...
    obips.PivotTable.onDatabodyContextMenu = function (h, n, s) {
        var g = new Array();
        var i = obips.ViewModel.EventType.NO_EVENT;
        var t = "views/pivot/obips.gridview.xml";
        var k = obips.ResourceManager.getSingleton();
        var f = obips.PivotTable.isRuntimeMode();
        if (h) {
            h.bDatabody = true;
            var e = h;
            var C = h.getRow();
            var z = h.getCol();
            var B = h.getId();
            var w = obips.ViewModel.getViewModelById(B);
            var x = w.getBodyDefinition(B);
            var q = x.getValue(C, z);
            var r = x.getID(C, z);
            var c = w.getPivotViewXML();
            var p = obips.PivotTable.containsNestedViews(c);
            if (p && !obips.PivotTable.isShowingNumbers(c, r)) {
                return
            }
            var o = h.getElement();
            var A = obips.PivotTable.getActionLinkIdsFromElement(o);
            var m = new ActionLinksModel(w, B, obips.EdgeDefinition.DATA_EDGE, C, z, false, w.actionLinksXmlId);
            m.setDataValue(r, q);
            var a = new ActionLinksRenderer(m, A);
//jack carver start
            linkText = "Copy (Ctrl-C)";
            i = obips.ViewModel.EventType.XX_COPY_CELL_VALUE;
            var l = obips.PivotTable.CreateContextMenuOption(linkText, linkText, null, null, i, e);
            g.push(l)
//jack carver end
...

2) добавим обработку нажатия нового пункта контекстного меню для ячеек Таблицы Среза
...
    obips.PivotTable.onContextMenuItemSelected = function (d) {
        var e = d.context;
        var b = e.menuOption;
        var a = b.eventType;
        var c = b.eventData;
        if (!a || !c) {
            return
        }
        obips.PivotTable.ptCurrentEvent.eventType = a;
        obips.PivotTable.ptCurrentEvent.eventData = c;
        switch (a) {
//jack carver start
            case obips.ViewModel.EventType.XX_COPY_CELL_VALUE:
                var a = c.getId();
                var e = c.getRow();
                var h = c.getCol();

                var eId = "db_" + a + "_" + e + "_" + h;

                var cell = document.getElementById(eId);

                var cellValue = cell.innerHTML;
                if (cellValue) {
                    cellValue = cellValue.toString().replace(/\s*\<.*?\>\s*/g, '');

                    cellValue = cellValue.toString().replace(/&lt;/g, '<');
                    cellValue = cellValue.toString().replace(/&gt;/g, '>');
                    cellValue = cellValue.toString().replace(/&quot;/g, '"');
                    cellValue = cellValue.toString().replace(/&nbsp;/g, ' ');
                }

                window.prompt('To copy -> Ctrl+C', cellValue );
                break;
//jack carver end
...

Вот и все! Надеюсь, этот текст будет кому-нибудь полезен!

P.S. Решение приведено для версии OBIEE 11.1.1.6.2 (Build 120604.0813 BP1 64-bit)

13 комментариев:

  1. В BI 11.1.1.7 в меню пункт появился, но при нажатии ничего не происходит.
    Есть подозрение, что не отрабатывает window.prompt('To copy -> Ctrl+C', cellValue );
    Может сталкивались?

    ОтветитьУдалить
  2. Проблема была связана с ошибкой в переменных(в моих конфигах название переменных отличны от приведенных).
    Советую для FF использовать FireBug - очень помогает)

    Спасибо огромное автору!

    ОтветитьУдалить
  3. Сергей, спасибо за Ваш блог и консультации на sql.ru :)
    Я попробовал сделать немного проще и у меня работает, но, возможно, это неправильно и может привести где-то к ошибке.
    Посмотрите, пожалуйста, возможно ли использовать данный вариант в продакшене?

    Все изменения в pivotview.js

    в методе obips.PivotTable.onKeyDown = function (f) {
    .........
    var h = obips.PivotTable.isCtrlKeyPressed(f);
    var i = obips.PivotTable.isShiftKeyPressed(f);
    // Начало моего кода
    if (h && (f.keyCode == 67 || f.keyCode == 99)) {
    var l_viewmodel = obips.ViewModel.getViewModelById(e);
    var l_edge = l_viewmodel.focusedCellCoords.edge;
    var l_layer = l_viewmodel.focusedCellCoords.layer;
    var l_slice = l_viewmodel.focusedCellCoords.slice;
    var col_Id = obips.PivotTable.getCellId(e, l_edge, l_layer, l_slice);
    var l_value = document.getElementById(col_Id).innerHTML;
    window.prompt("Copy to clipboard: Ctrl+C", l_value);
    }
    // Окончание моего кода
    switch (f.keyCode) {
    .......

    ОтветитьУдалить
    Ответы
    1. Как по мне - решение супер! Проще и очевиднее для пользователей!
      Вот только у вас обработчик "слушает" нажатия Ctrl-C только с PivotTable. Нужно аналогичные действия выполнить и для обычных таблиц (или obips.PivotTable.onKeyDown слушает и нажатия на простых TableView?)

      Удалить
    2. Здравствуйте :)
      В версии 11.1.1.7.140114 "слушатель"(obips.PivotTable.onKeyDown) у них общий и это работает для сводных и для обычных таблиц. Как в других версиях не знаю, пока нету возможности проверить. К сожалению, на сводных работает только для строк, для показателей значение не копируется, пока разбираюсь. Для обычных таблиц работает всё корректно, но нужно добавить регэксп l_value = l_value .toString().replace(/\s*\<.*?\>\s*/g, ''); для корректной обработки объеденных ячеек и ячеек содержащих ссылки на действие.

      Удалить
  4. Забыл добавить, я делал не через контекстное меню, а через вызов по CTRL+C

    ОтветитьУдалить
  5. Добрый день

    сделал как написано. меню с копированием нет
    посмотрел исходный код отчета BI
    подгружается почему-то не измененный файл. в Могиле трейс показывает, что послан запрос на сервер, ответа нет, взят из кеша

    что надо сделать, чтобы появился пункт меню?

    Спасибо

    ОтветитьУдалить
    Ответы
    1. Скажите, в какой именно папке правили javascript файлы?

      Удалить
    2. Их оказывается 2 папки
      у меня правильные расположены тут:
      bifoundation_domain\servers\bi_server1\tmp\_WL_user\analytics_11.1.1\7dezjl\war\res\b_mozilla\views
      все сделал, изменил переменные, все работает

      а каким образом можно копировать значения нескольких ячеек из отчета?

      Спасибо

      Удалить
    3. а каким образом можно копировать значения нескольких ячеек из отчета?

      Удалить
    4. К сожалению, решения на эту проблему у меня нет.
      Нужно ковыряться в javascript...

      Удалить
  6. в Мозиле
    прошу прощения

    ОтветитьУдалить