QScript Functions for Sort and Delete
Jump to navigation
Jump to search
This page is currently under construction, or it refers to features which are under development and not yet available for use.
This page is under construction. Its contents are only visible to developers!
includeWeb("JavaScript Array Functions");
includeWeb("JavaScript Utilities");
includeWeb("QScript Utility Functions");
function sortPagesBySignificance(pages_to_sort, log_results = false, min_p_values) {
includeWeb("QScript Selection Functions");
const fail_message = "Cannot sort the selection. Select two or more pages " +
"in the same folder or at the base level of Pages, or select a single folder of pages.";
let parent = null; // parent QScript ReportGroup of pages to sort
if (!pages_to_sort) {
const user_selections = getAllUserSelections();
const pages_in_same_folder = selectedPageItemsAreInSameFolder(user_selections);
const one_folder_selected = exactlyNSelected(user_selections, "Top Pages", 1);
// Check the user has selected items that can be sorted unambiguously
if (!pages_in_same_folder && !one_folder_selected) {
log(fail_message);
return false;
}
if (pages_in_same_folder && !one_folder_selected) {
parent = user_selections.selected_top_level_pages.length > 0 ? user_selections.selected_top_level_pages[0].parentGroup() : user_selections.selected_items[0].group;
pages_to_sort = user_selections.selected_top_level_pages.concat(user_selections.selected_items);
}
else if (one_folder_selected) { // Sort pages within folder
parent = user_selections.selected_top_level_pages[0];
pages_to_sort = parent.subItems.filter(item => item.type === "ReportGroup" || !item.IsOnPage);
if (pages_to_sort.length < 1) {
log(fail_message);
return false;
}
}
else {
log(fail_message);
return false;
}
}
else {
parent = isReportGroup(pages_to_sort[0]) ? pages_to_sort[0].parentGroup() : pages_to_sort[0].group;
if (!parent) {
return false;
}
}
sortItemsBySignificance(pages_to_sort, parent, min_p_values);
// Make a list of pages that don't have sortable objects (tables, plots)
const unsortable_pages = pages_to_sort.filter(function (page) {
if (page.type === "Table" || page.type === "Plot") {
return false;
}
return page.subItems.filter(function (item) {
return ["Table", "Plot"].indexOf(item.type) > -1;
}).length == 0;
});
if (log_results) {
log("Pages have been sorted so that those containing Tables or Charts " +
"with the most significant results (lowest p-values) are at the top.");
}
if (unsortable_pages.length > 0) {
log("Some pages did not contain any tables or charts with " +
"p-values and have been sorted to the bottom.");
}
return;
}
function sortItemsBySignificance(items, parent, min_p_values) {
let sort_objects;
if (min_p_values == null) {
sort_objects = items.map((item, idx) => ({
item: item,
smallest_p: (isReportGroup(item) ?
getSmallestPValueOnPage(item) :
minimumPValue(item)),
idx: idx
}));
}
else {
sort_objects = items.map((item, idx) => ({
item: item,
smallest_p: min_p_values[idx],
idx: idx
}));
}
sort_objects = sort_objects.sort(function (a, b) {
if (a.smallest_p < b.smallest_p) {
return -1;
}
if (a.smallest_p > b.smallest_p) {
return 1;
}
return 0;
});
let previous_item = sort_objects[0].item;
sort_objects.forEach(function (obj, index) {
if (index > 0) {
parent.moveAfter(obj.item, previous_item);
previous_item = obj.item;
}
});
return;
}
function selectedPageItemsAreInSameFolder(user_selections) {
let result = true;
const selected_page_items = user_selections.selected_items;
const parents = selected_page_items.map(item => {
if (item.group && !item.IsOnPage) {
return item.group.guid;
}
return null;
});
if (parents) {
result = result && parents.every(guid => parents[0] === guid && guid != null);
}
const selected_top_level_pages = user_selections.selected_top_level_pages;
const page_parents = selected_top_level_pages.map(page => page.parentGroup());
if (page_parents) {
result = result && page_parents.every(grp => page_parents[0] == grp);
if (parents && parents.length > 0 && page_parents.length > 0) {
result = result && parents[0] === page_parents[0];
}
}
return result;
}
function getSmallestPValueOnPage(page) {
let smallest_p = Infinity;
const tables_plots_on_page = page.subItems.filter(item => isPlot(item) || isTable(item));
tables_plots_on_page.forEach(item => {
const min_p = minimumPValue(item);
if (min_p !== undefined && min_p < smallest_p) {
smallest_p = min_p;
}
});
return smallest_p;
}
function minimumPValue(item) {
var _a;
let table;
// When item is R output it must be
// a visualization with a nested table
// otherwise should not be checked
// and given a value of -Infinity.
// If it has a nested table, then that
// table should be evaluated as any
// other table.
if (isROutput(item)) {
if (!rItemIsPlot(item)) {
return -Infinity;
}
if (rPlotHasNestedTable(item)) {
table = item.subItems.filter(x => isTable(x))[0];
}
else {
return -Infinity; // So as to never delete
}
}
// treat plots and tables separately
if (isPlot(item)) {
if (item.primary == null || item.tertiary != null) {
return Infinity;
}
table = project.report.appendTable();
table.primary = item.primary;
if (item.secondary != null) {
table.secondary = item.secondary;
}
if (item.weight != null) {
table.weight = item.weight;
}
if (item.filters != null) {
table.filters = item.filters;
}
}
else if (isTable(item)) {
table = item;
}
else {
return Infinity;
}
if (tableQuestionsHaveNoData(table)) {
return Infinity;
}
// Ensure crosstabs with the same question
// in rows and columns appear at top (so they
// can be easily identified and deleted.
if ((_a = table.primary) === null || _a === void 0 ? void 0 : _a.equals(table.secondary)) {
return -Infinity;
}
let pvalues;
try {
pvalues = PValuesExcludingNETs(table);
}
catch {
pvalues = Infinity;
}
if (isPlot(item)) {
table.deleteItem(); // Deleting temp table
}
if (!isArray(pvalues)) {
return pvalues;
}
return minWithReplacedNaN(pvalues, Infinity);
}
function PValuesExcludingNETs(table) {
includeWeb("QScript Table Functions");
const stat_name = "Corrected p";
const table_output = table.calculateOutput();
const row_indices_without_net = table_output.rowIndices(false);
const stats = getStatisticsFromTable(table, [stat_name]);
// If there are no p-values, the item is not significant, delete.
if (stats == null || stats[stat_name] == null) {
return Infinity;
}
// remove NET and SUM rows
const pvalues_without_net = [];
row_indices_without_net.forEach(index => {
pvalues_without_net.push(stats[stat_name][index]);
});
return pvalues_without_net;
}
function tableQuestionsHaveNoData(table) {
if (table.primary != null && table.primary.dataReduction.rowLabels.length == 0) {
return true;
}
return table.secondary != null && table.secondary.dataReduction != null &&
table.secondary.dataReduction.rowLabels.length == 0;
}
function sortQuestionsInDataFileOrder(questions) {
const current_data_file = questions[0].dataFile;
const all_question_names = current_data_file.questions.map(q => q.name);
questions.sort((a, b) => {
if (all_question_names.indexOf(a.name) < all_question_names.indexOf(b.name)) {
return -1;
}
if (all_question_names.indexOf(a.name) > all_question_names.indexOf(b.name)) {
return 1;
}
return 0;
});
return questions;
}
function sortVariablesInDataFileOrder(variables) {
const current_data_file = variables[0].question.dataFile;
const all_variable_names = current_data_file.variables.map(q => q.name);
variables.sort((a, b) => {
if (all_variable_names.indexOf(a.name) < all_variable_names.indexOf(b.name)) {
return -1;
}
if (all_variable_names.indexOf(a.name) > all_variable_names.indexOf(b.name)) {
return 1;
}
return 0;
});
return variables;
}
function deleteInsignificantTablesPlots(p_threshold, log_results = true, min_p_values) {
includeWeb("QScript Selection Functions");
function getThreshold() {
let sig_level = -1;
while (sig_level <= 0 || sig_level >= 100) {
sig_level = parseFloat(prompt('Please enter the desired significance level (e.g. 95 for significance at 95%):', '95'));
}
return Math.round((1 - sig_level / 100) * 1e10) / 1e10; // corrected for numerical precision errors
}
const web_mode = inDisplayr();
let selected_items = [];
let deleted_items = [];
let not_evaluated_r_plots = [];
let not_evaluated_r_items = [];
if (web_mode) {
const user_selections = getAllUserSelections();
const organized_selections = organizeSelectedItemsForStatisticBasedDeletion(user_selections);
if (!organized_selections) {
return false;
}
selected_items = organized_selections.selected_items;
not_evaluated_r_plots = organized_selections.not_evaluated_r_plots;
not_evaluated_r_items = organized_selections.not_evaluated_r_items;
}
else {
selected_items = selectedTablesAndPlots(project.report);
if (selected_items.length < 1) {
log("There are no tables or charts selected. Select one or more tables or charts.");
return false;
}
}
if (p_threshold == null) {
p_threshold = getThreshold();
if (isNaN(p_threshold)) {
return false;
}
}
if (min_p_values == null) {
min_p_values = selected_items.map(minimumPValue);
}
// filter selected_items with significant p-values, leaving only items for deletion
deleted_items = selected_items.filter((item, idx) => {
const p = min_p_values[idx];
return p > p_threshold;
});
const not_deleted = selected_items.filter((item, idx) => {
const p = min_p_values[idx];
return p <= p_threshold;
});
// In Q, just delete the selected items. On the web we need to clear out the pages
// which are made blank by the removal of tables/plots
// This is mainly to facilitate the Insert > Report feature being cleanable.
const all_selected_pages_with_nulls = deleted_items.map(item => (item.IsOnPage && web_mode) ? item.group : item);
deleted_items = filterOutDuplicateItems(deleted_items);
const all_selected_pages = filterOutDuplicateItems(all_selected_pages_with_nulls);
const previous_page = identifyPreviousPageToDeletions(all_selected_pages);
const deleted_table_names = [];
const deleted_plot_names = [];
deleted_items.forEach(item => {
try {
(item.type === "Table" ? deleted_table_names : deleted_plot_names).push(item.name);
item.deleteItem();
return;
}
catch { /* empty */ }
});
let deleted_page_names = [];
if (web_mode) {
deleted_page_names = deleteEmptyPages(all_selected_pages.filter(item => isReportGroup(item)));
}
if (log_results && !web_mode) {
logDeletionSummary(deleted_table_names, deleted_plot_names, deleted_page_names, min_p_values, p_threshold);
}
if (deleted_items.length > 0) {
if (not_deleted.length > 0) {
const target_item = web_mode ? not_deleted[0].group : not_deleted[0];
project.report.setSelectedRaw(target_item ? [target_item] : []);
}
else {
project.report.setSelectedRaw(previous_page ? [previous_page] : []);
}
}
if (not_evaluated_r_plots.length > 0)
log("Some visualizations could not be evaluated for significance and have not been deleted.");
if (not_evaluated_r_items.length > 0)
log(correctTerminology("Calculations were not evaluated for significance and have not been deleted."));
return;
}
function filterOutDuplicateItems(items_array) {
const item_guids = [];
return items_array.filter(item => {
if (item && !(item_guids.indexOf(item.guid) >= 0)) {
item_guids.push(item.guid);
return true;
}
return false;
});
}
function deleteEmptyPages(pages) {
includeWeb("QScript Selection Functions");
const deleted_pages = [];
const deleted_page_names = [];
pages.forEach(x => {
recursiveGetAllGroupsInGroup(x, pages);
});
pages.forEach(page => {
let current_sub_items = [];
recursiveGetAllItemsInGroup(page, current_sub_items);
current_sub_items = current_sub_items.filter(item => item.type != "Text");
if (current_sub_items.length === 0) {
deleted_pages.push(page);
deleted_page_names.push(page.name);
}
});
deleted_pages.forEach(page => {
try {
page.deleteItem();
}
catch {
/* empty */
}
});
return deleted_page_names;
}
function logDeletionSummary(deleted_table_names, deleted_plot_names, deleted_page_names, min_p_values, p_threshold) {
const n_deleted = deleted_table_names.length + deleted_plot_names.length;
if (n_deleted === 0) {
log("No tables or plots met the condition to be deleted.");
return;
}
if (deleted_table_names.length > 0) {
log('The following tables were not significant at the ' + p_threshold +
' level, and have been deleted:');
log(deleted_table_names.join("\r\n"));
log('\r\n');
}
if (deleted_plot_names.length > 0) {
log('The following plots were not significant at the ' + p_threshold + ' level, and have been deleted:');
log(deleted_plot_names.join("\r\n"));
log('\r\n');
}
if (deleted_page_names.length > 0) {
log('The following pages were made empty, and have been deleted:');
log(deleted_page_names.join("\r\n"));
}
return;
}
function sortPagesAlphabetically() {
includeWeb("QScript Selection Functions");
const fail_message = "Cannot sort the selection. Select two or more pages/outputs in the same folder or at the base level of Pages, or select a single folder.";
const user_selections = getAllUserSelections();
const pages_in_same_folder = selectedPageItemsAreInSameFolder(user_selections);
const one_folder_selected = exactlyNSelected(user_selections, "Top Pages", 1);
// Check the selected pages are not length 0
if (user_selections.selected_top_level_pages.length == 0 && user_selections.selected_items.length == 0) {
log(fail_message);
return false;
}
// Check the user has selected items that can be sorted unambiguously
if (!pages_in_same_folder && !one_folder_selected) {
log(fail_message);
return false;
}
// Sort selected pages
let parent = null;
let pages_to_sort;
if (pages_in_same_folder) {
parent = user_selections.selected_top_level_pages.length > 0 ? user_selections.selected_top_level_pages[0].parentGroup() : user_selections.selected_items[0].group;
pages_to_sort = [...user_selections.selected_top_level_pages, ...user_selections.selected_items];
}
else if (one_folder_selected) { // Sort pages within folder
parent = user_selections.selected_top_level_pages[0];
pages_to_sort = parent.subItems.filter(item => isReportGroup(item) || !item.IsOnPage);
if (pages_to_sort.length < 1) {
log(fail_message);
return false;
}
}
pages_to_sort.sort((a, b) => {
if (a.name.toLowerCase() < b.name.toLowerCase()) {
return -1;
}
if (a.name.toLowerCase() > b.name.toLowerCase()) {
return 1;
}
return 0;
});
let last_page = pages_to_sort[0];
pages_to_sort.forEach((page, index) => {
if (index > 0) {
parent.moveAfter(page, last_page);
last_page = page;
}
});
}
/** Sorts the selected variable sets or variables alphabetically. */
function sortVariablesAlphabetically(by_variable_name = false) {
includeWeb("QScript Selection Functions");
function getSortableName(x, by_variable_name) {
if (isQuestion(x)) {
return by_variable_name ? x.variables[0].name : x.name;
}
else {
return by_variable_name ? x.name : x.label;
}
}
const fail_message = "Cannot sort. Select two or more individual variables, or at least one variable set, under Data Sources.";
const user_selections = getAllUserSelections();
if (!NOrMoreSelected(user_selections, "Variable", 2)) {
log(fail_message);
return false;
}
if (!exactlyNSelected(user_selections, "Data Set", 1)) {
log("Cannot sort. Select variables from one data set only.");
return false;
}
const data_file = user_selections.selected_data_sets[0];
// Sort variable sets First
// Only if more than one is selected
if (NOrMoreSelected(user_selections, "Question", 2)) {
const selected_questions = user_selections.selected_questions;
// Move variables together.
sortQuestionsInDataFileOrder(selected_questions);
let last_variable = selected_questions[0].variables[selected_questions[0].variables.length - 1];
const remaining_questions = selected_questions.slice(1);
let remaining_variables = [];
remaining_questions.forEach(function (q) {
remaining_variables = remaining_variables.concat(q.variables);
});
data_file.moveAfter(remaining_variables, last_variable);
// Sort questions by name
selected_questions.sort(function (a, b) {
const a_name = getSortableName(a, by_variable_name).toLowerCase();
const b_name = getSortableName(b, by_variable_name).toLowerCase();
if (a_name < b_name) {
return -1;
}
if (a_name > b_name) {
return 1;
}
return 0;
});
last_variable = selected_questions[0].variables[selected_questions[0].variables.length - 1];
selected_questions.forEach(function (q, index) {
if (index > 0) {
data_file.moveAfter(q.variables, last_variable);
last_variable = q.variables[q.variables.length - 1];
}
});
}
else if (exactlyNSelected(user_selections, "Question", 1)) {
// If a single variable set is selected, sort the variables within it
// or if a subset of variables is selected, sort them relative to
// one another.
const question = user_selections.selected_questions[0];
const selected_variables = user_selections.selected_variables.slice(0);
if (selected_variables.length < 2) {
return;
}
const question_index_in_file = question.dataFile.questions.findIndex(q => q.guid === question.guid);
let last_var = null;
if (question_index_in_file > 0) {
const previous_question = question.dataFile.questions[question_index_in_file - 1];
// RS-19773: guard against when the variable to be sort is the first question in the Data Source.
if (previous_question && Array.isArray(previous_question.variables) && previous_question.variables.length > 0) {
last_var = previous_question.variables[previous_question.variables.length - 1];
}
}
// Selected variables are in the order of selection, so we need to find the smallest variable index in the variable set
const selected_indices = selected_variables.map(selected_var => question.variables.findIndex(v => v.guid === selected_var.guid)).filter(i => i > -1);
if (selected_indices.length <= 0) {
log('Could not find the selected variables in the variable set.');
return;
}
const selected_first_variable_index = Math.min(...selected_indices);
// Sort selected variables alphabetically by label
selected_variables.sort(function (a, b) {
const a_name = getSortableName(a, by_variable_name).toLowerCase();
const b_name = getSortableName(b, by_variable_name).toLowerCase();
if (a_name < b_name) {
return -1;
}
if (a_name > b_name) {
return 1;
}
return 0;
});
const reordered_all_variables = question.variables.slice(0);
let index = selected_first_variable_index;
// move sorted selected variables to be together
for (let i = 0; i < selected_variables.length; i++, index++) {
reordered_all_variables[index] = selected_variables[i];
}
// move the rest variables
for (let i = selected_first_variable_index; i < question.variables.length; i++) {
const variable = question.variables[i];
if (selected_variables.findIndex(v => v.guid === variable.guid) < 0) {
reordered_all_variables[index] = variable;
index++;
}
}
question.dataFile.moveAfter(reordered_all_variables, last_var);
}
}
function organizeSelectedItemsForStatisticBasedDeletion(user_selections) {
includeWeb("QScript Selection Functions");
let selected_items = [];
let not_evaluated_r_plots = [];
let not_evaluated_r_items = [];
// Check selections
if (!(oneOrMoreSelected(user_selections, "Table", true) ||
oneOrMoreSelected(user_selections, "Plot", true) ||
oneOrMoreSelected(user_selections, "WordCloud", true) ||
oneOrMoreSelected(user_selections, "R Output", true))) {
log("There are no tables or charts selected. Select one or more " +
"pages which contain tables or charts.");
return false;
}
selected_items = [
...user_selections.selected_tables,
...user_selections.selected_plots,
...user_selections.selected_word_clouds,
...user_selections.implicitly_selected_tables,
...user_selections.implicitly_selected_plots,
...user_selections.implicitly_selected_word_clouds
];
// Special handling for R outputs.
// Can currently only check significance for R plots that have nested
// tables (i.e. have been converted from a table).
// Do not delete any other R items and later tell the user that those
// items could not be check and were not deleted.
const selected_r_items = user_selections.implicitly_selected_r_outputs;
// Separate plots from other R items
const r_plots_vs_other = splitArrayIntoApplicableAndNotApplicable(selected_r_items, rItemIsPlot);
not_evaluated_r_items = r_plots_vs_other.notApplicable;
const selected_r_plots = r_plots_vs_other.applicable;
// Separate plots which have nested tables from those that don't
const r_plots_can_evaluate = splitArrayIntoApplicableAndNotApplicable(selected_r_plots, rPlotHasNestedTable);
not_evaluated_r_plots = r_plots_can_evaluate.notApplicable;
// Include R plots with nested tables among things to check
selected_items = selected_items.concat(selected_r_plots);
return {
selected_items: selected_items,
not_evaluated_r_plots: not_evaluated_r_plots,
not_evaluated_r_items: not_evaluated_r_items
};
}
function deleteWithSmallSample() {
includeWeb("QScript Selection Functions");
const min_size = parseInt(prompt("Please enter the minimum sample size that you wish to view. Tables and visualizations with a smaller sample size will be removed.", "10"));
if (isNaN(min_size)) {
return false;
}
const web_mode = inDisplayr();
let selected_items = [];
let not_evaluated_r_plots = [];
let not_evaluated_r_items = [];
if (web_mode) {
const user_selections = getAllUserSelections();
const organized_selections = organizeSelectedItemsForStatisticBasedDeletion(user_selections);
if (!organized_selections) {
return false;
}
selected_items = organized_selections.selected_items;
not_evaluated_r_plots = organized_selections.not_evaluated_r_plots;
not_evaluated_r_items = organized_selections.not_evaluated_r_items;
selected_items = selected_items.filter(item => item.group != null);
}
else {
selected_items = selectManyTablesWithGroupNames("Please choose which tables you would like to check. Tables with a sample size less than " + min_size + " will be removed.", project.report).tables;
if (selected_items.length < 1) {
log("There are no tables or charts selected. Select one or more tables or charts.");
return false;
}
}
const deleted_items = [];
const not_deleted_items = [];
selected_items.forEach(item => {
if (maxBaseBelowThreshhold(item, min_size)) {
deleted_items.push(item);
}
else {
not_deleted_items.push(item);
}
});
// In Q, just delete the selected items. On the web we need to clear out the pages
// which are made blank by the removal of tables/plots
// This is mainly to facilitate the Insert > Report feature being cleanable.
const all_selected_pages = deleted_items.map(item => item.group).filter(o => !!o);
const previous_page = identifyPreviousPageToDeletions(all_selected_pages);
const deleted_table_names = [];
const deleted_plot_names = [];
deleted_items.forEach(function (item) {
try {
(item.type === "Table" ? deleted_table_names : deleted_plot_names).push(item.name);
item.deleteItem();
return;
}
catch { /* empty */ }
});
let deleted_page_names = [];
if (web_mode) {
deleted_page_names = deleteEmptyPages(all_selected_pages);
}
// If we have deleted anything, choose where to change the focus
if (deleted_items.length > 0) {
if (not_deleted_items.length > 0) {
const target_item = web_mode ? not_deleted_items[0].group : not_deleted_items[0];
project.report.setSelectedRaw(target_item ? [target_item] : []);
}
else {
project.report.setSelectedRaw(previous_page ? [previous_page] : []);
}
}
if (!web_mode) {
if (deleted_table_names.length > 0) {
log("The following tables have been removed: ");
log(deleted_table_names.join('\r\n'));
}
else {
log("No tables were found to have a sample size less than " + min_size + ".");
}
}
if (not_evaluated_r_plots.length > 0) {
log("Sample size could not be identified for some visualizations and these have not been deleted.");
}
if (not_evaluated_r_items.length > 0) {
log(correctTerminology("Sample size was not checked for Calculations and these have not been deleted."));
}
return;
}
// Identify whether the largest sample size for an item
// is below the given min size.
// For tables, check the Base n.
// For plots, convert to table and check the Base n
// For visualizations with nested tables, check the base n
// of the nested table.
// For other R items, do nothing.
function maxBaseBelowThreshhold(item, min_size) {
let output;
let table;
if (isTable(item)) {
try {
output = item.calculateOutput();
}
catch (e) {
return true; // Table is empty and should be removed
}
if (output.availableStatistics.indexOf('Text') > -1)
return false; // Table is text and does not have concept of missing data
return maxWithReplacedNaN(output.get('Base n'), 0) < min_size;
}
if (isPlot(item)) {
if (item.primary == null)
return true; // empty, delete
if (item.tertiary != null)
return false; //can't check sample size on this, don't delete
table = project.report.appendTable();
table.primary = item.primary;
if (item.secondary != null)
table.secondary = item.secondary;
if (item.weight != null)
table.weight = item.weight;
if (item.filters != null)
table.filters = item.filters;
try {
output = table.calculateOutput();
const delete_this = maxWithReplacedNaN(output.get('Base n'), 0) < min_size;
table.deleteItem();
return delete_this;
}
catch (e) {
table.deleteItem();
return true; // plot is empty and should be removed
}
}
if (isROutput(item)) {
if (!rItemIsPlot(item))
return false; // not a plot, don't delete
if (rPlotHasNestedTable(item)) {
table = item.subItems.filter(x => isTable(x))[0];
try {
output = table.calculateOutput();
return maxWithReplacedNaN(output.get('Base n'), 0) < min_size;
}
catch {
return true; // Table is empty and should be removed
}
}
else
return false; // not nested table, don't delete
}
}
// Given a collection of pages to be deleted, work out what page
// is previous to those pages to which the focus can be set at the
// completion of the QScript
function identifyPreviousPageToDeletions(deleted_pages) {
const web_mode = inDisplayr();
let previous_page = null;
if (deleted_pages.length > 0) {
const parent_of_first = deleted_pages[0].group;
if (parent_of_first) {
const guids_in_parent = parent_of_first.subItems.filter(x => !web_mode || isReportGroup(x) || !x.IsOnPage)
.map(y => y.guid);
const index_of_first_within_parent = guids_in_parent.indexOf(deleted_pages[0].guid);
if (index_of_first_within_parent < 1) {
previous_page = parent_of_first;
}
else {
previous_page = parent_of_first.subItems[index_of_first_within_parent - 1];
}
}
}
return previous_page;
}