From 1c874e3cbe1fb02de91932c3c80d25fa515a2685 Mon Sep 17 00:00:00 2001 From: Roberto Soto <robsoto@mit.edu> Date: Mon, 22 May 2017 13:48:17 -0400 Subject: [PATCH 01/10] Copy Query Builder to be modified into "Policy Builder" --- .../js/dq-security-policy-builder.js | 110 ++++++++++++++++++ 1 file changed, 110 insertions(+) create mode 100644 src/apps/dataq/client_src/js/dq-security-policy-builder.js diff --git a/src/apps/dataq/client_src/js/dq-security-policy-builder.js b/src/apps/dataq/client_src/js/dq-security-policy-builder.js new file mode 100644 index 00000000..95b80b95 --- /dev/null +++ b/src/apps/dataq/client_src/js/dq-security-policy-builder.js @@ -0,0 +1,110 @@ +/** + * Logic for constructing a SQL query string from a DataQ.Query object. + */ +(function() { + // If the global DataQ object does not exist, create it. + window.DataQ = window.DataQ || {}; + + /** + * Take a DataQ.Query object and generate a SQL query string from it. + * + * @param query - The DataQ.Query object. + * @return A String representing the SQL query. + */ + window.DataQ.build_query = function(query) { + + // The list of columns to select. + var select_list = []; + + // The list of tables to select from. + var from_list = []; + + // The filters to apply. + var where_list = []; + + // The grouping to perform. + var group_list = []; + + // The sorting to apply. + var order_list = []; + + // Get the current repo name - we'll need to prepend this to some of the table/column names. + var repo = query.repo(); + + // Create the FROM clause. It is simply the list of tables that the user has selected. + // Each item in the list is a String of the form: "repo.table". + query.get_selected_tables().forEach(function(table) { + from_list.push(repo + "." + table); + }); + + // Create the SELECT clause. + // Iterate through every selected column of every selected table and add the column to the + // select list (and write the aggregate if possible). + query.get_selected_tables().forEach(function(table) { + query.selected_columns(table).forEach(function(column) { + if (column.agg === undefined || column.agg === null || column.agg === "none") { + select_list.push(repo + "." + table + "." + column.name); + } else { + // When an aggregate "agg" on column "col" in table "table" and repo "repo" appears, mark + // "agg(repo.table.col) as agg_table_col". + select_list.push(column.agg + "(" + repo + "." + table + "." + column.name + ")" + + " as " + column.agg + "_" + table + "_" + + column.name); + } + }); + }); + + // Create the WHERE clause. + // Simply iterate through each filter and add it to the list. + query.get_filters().forEach(function(filter) { + where_list.push(filter.filter1 + " " + filter.op + " " + filter.filter2); + }); + + // Create the GROUP BY clause. + query.grouping().forEach(function(group) { + var agg = group.column.agg; + + // We can only add a group by if it's not the aggregate column. + if (agg === null || agg === undefined || agg === "none") { + group_list.push(repo + "." + group.string); + } + }); + + // Create the ORDER BY clause. + query.sorts().forEach(function(sort) { + var agg = sort.column.agg; + if (agg === null || agg === undefined || agg === "none") { + order_list.push(repo + "." + sort.string); + } else { + order_list.push(agg + "_" + sort.table + "_" + sort.column.name); + } + }); + + // Set the query string. + if (select_list.length === 0) { + return ""; + } + var query_string = "SELECT " + select_list.join(", ") + + " FROM " + from_list.join(", "); + + // Set the where list. + if (where_list.length > 0) { + query_string += " WHERE " + where_list.join(" AND "); + } + + // Set the group list. + if (group_list.length > 0) { + query_string += " GROUP BY " + group_list.join(", ") + } + + // Set the order list. + if (order_list.length > 0) { + query_string += " ORDER BY " + order_list.join(", ") + } + + // Remove leading and trailing spaces and then append semicolon. + query_string.trim(); + query_string += ";"; + return query_string; + }; +})(); From 2659213b8a1b865f5c6f7330c8dad8c114d00b97 Mon Sep 17 00:00:00 2001 From: Roberto Soto <robsoto@mit.edu> Date: Tue, 23 May 2017 16:36:14 -0400 Subject: [PATCH 02/10] DataQ modal accessible from Security Policies page --- src/browser/templates/security-policies.html | 22 ++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/browser/templates/security-policies.html b/src/browser/templates/security-policies.html index 050d0d3a..5486b298 100644 --- a/src/browser/templates/security-policies.html +++ b/src/browser/templates/security-policies.html @@ -23,6 +23,7 @@ <h4 class="inline-block"> <div class="col-sm-2"> <button class="btn btn-primary btn-xsm" id="btn-run" type="submit">Execute</button> <button class="btn btn-primary btn-xsm modal-upload-dialog" type="button" target-modal="#security-policy-modal" title="New Policy"> New </button> + <button class="btn btn-primary btn-xsm" id="btn-dataq" type="button">Query Builder</button> </div> </div> @@ -228,4 +229,25 @@ <h4 class="modal-title" id="confirm-modal-title">Update Security Policy</h4> }); </script> + +<script src="//cdnjs.cloudflare.com/ajax/libs/blueimp-md5/1.0.1/js/md5.js"></script> +<!-- dataq --> +<script src="//cdnjs.cloudflare.com/ajax/libs/handlebars.js/2.0.0/handlebars.runtime.js"></script> +<script src="/static/dataq/templates.js"></script> +<script src="/static/dataq/js/dataq.min.js"></script> +<!-- /dataq --> + +<script> + $(document).on("click", "#btn-dataq", function(e) { + e.preventDefault(); + DataQ.DQ("{{repo}}", function(query) { + $("#txt-sql").val(query); + }); + console.log('click'); + }); +</script> {% endblock js %} + +{% block stylesheets %} +<link rel="stylesheet" href="/static/dataq/css/style.css"> +{% endblock stylesheets %} From 4ea3e182bed04205b93901728edfbbffaf386772 Mon Sep 17 00:00:00 2001 From: Roberto Soto <robsoto@mit.edu> Date: Wed, 24 May 2017 23:45:29 -0400 Subject: [PATCH 03/10] Build CREATE POLICY string from DataQ.Policy object. --- .../js/dq-security-policy-builder.js | 158 +++++++----------- 1 file changed, 64 insertions(+), 94 deletions(-) diff --git a/src/apps/dataq/client_src/js/dq-security-policy-builder.js b/src/apps/dataq/client_src/js/dq-security-policy-builder.js index 95b80b95..4b136853 100644 --- a/src/apps/dataq/client_src/js/dq-security-policy-builder.js +++ b/src/apps/dataq/client_src/js/dq-security-policy-builder.js @@ -1,110 +1,80 @@ /** - * Logic for constructing a SQL query string from a DataQ.Query object. - */ +* Logic for constructing a PostgreSQL CREATE POLICY command from a +* DataQ.policy object. +*/ (function() { // If the global DataQ object does not exist, create it. window.DataQ = window.DataQ || {}; /** - * Take a DataQ.Query object and generate a SQL query string from it. + * Take a DataQ.Policy object and generate a CREATE POLICY string from it. + * A CREATE POLICY command looks like: * - * @param query - The DataQ.Query object. - * @return A String representing the SQL query. + * CREATE POLICY name ON table_name + * [ FOR { ALL | SELECT | INSERT | UPDATE | DELETE } ] + * [ TO { role_name | PUBLIC | CURRENT_USER | SESSION_USER } [, ...] ] + * [ USING ( using_expression ) ] + * [ WITH CHECK ( check_expression ) ] + * + * see https://www.postgresql.org/docs/9.5/static/sql-createpolicy.html + * + * @param policy - The DataQ.policy object. + * @return A String representing the CREATE POLICY command. */ - window.DataQ.build_query = function(query) { - - // The list of columns to select. - var select_list = []; - - // The list of tables to select from. - var from_list = []; - - // The filters to apply. - var where_list = []; - - // The grouping to perform. - var group_list = []; - - // The sorting to apply. - var order_list = []; - - // Get the current repo name - we'll need to prepend this to some of the table/column names. - var repo = query.repo(); - - // Create the FROM clause. It is simply the list of tables that the user has selected. - // Each item in the list is a String of the form: "repo.table". - query.get_selected_tables().forEach(function(table) { - from_list.push(repo + "." + table); - }); - - // Create the SELECT clause. - // Iterate through every selected column of every selected table and add the column to the - // select list (and write the aggregate if possible). - query.get_selected_tables().forEach(function(table) { - query.selected_columns(table).forEach(function(column) { - if (column.agg === undefined || column.agg === null || column.agg === "none") { - select_list.push(repo + "." + table + "." + column.name); - } else { - // When an aggregate "agg" on column "col" in table "table" and repo "repo" appears, mark - // "agg(repo.table.col) as agg_table_col". - select_list.push(column.agg + "(" + repo + "." + table + "." + column.name + ")" + - " as " + column.agg + "_" + table + "_" - + column.name); - } - }); - }); - - // Create the WHERE clause. - // Simply iterate through each filter and add it to the list. - query.get_filters().forEach(function(filter) { - where_list.push(filter.filter1 + " " + filter.op + " " + filter.filter2); - }); - - // Create the GROUP BY clause. - query.grouping().forEach(function(group) { - var agg = group.column.agg; - - // We can only add a group by if it's not the aggregate column. - if (agg === null || agg === undefined || agg === "none") { - group_list.push(repo + "." + group.string); - } - }); - - // Create the ORDER BY clause. - query.sorts().forEach(function(sort) { - var agg = sort.column.agg; - if (agg === null || agg === undefined || agg === "none") { - order_list.push(repo + "." + sort.string); - } else { - order_list.push(agg + "_" + sort.table + "_" + sort.column.name); - } - }); - - // Set the query string. - if (select_list.length === 0) { - return ""; - } - var query_string = "SELECT " + select_list.join(", ") - + " FROM " + from_list.join(", "); + window.DataQ.build_policy = function(policy) { - // Set the where list. - if (where_list.length > 0) { - query_string += " WHERE " + where_list.join(" AND "); - } + // Name of policy to be created. + var policy_name = policy.name(); - // Set the group list. - if (group_list.length > 0) { - query_string += " GROUP BY " + group_list.join(", ") - } + // Name of table to which the policy applies. + var table_name = policy.repo() + "." + policy.table_name(); + + // Command to which the policy applies. + var command = policy.command(); + + // List of roles to which the policy applies. + var role_list = policy.roles(); + + // List of users to which the policy applies. + // Each element must be one of { PUBLIC | CURRENT_USER | SESSION_USER } + var user_list = policy.users(); + + // SQL conditional expression to control row visibility. + // Rows for which the expression returns true will be visible. + var using_expr = policy.using_expression(); - // Set the order list. - if (order_list.length > 0) { - query_string += " ORDER BY " + order_list.join(", ") + // SQL conditional expression to control INSERT and UPDATE privileges. + // Only rows for which the expression evaluates to true will be allowed. + var check_expr = policy.check_expression(); + + /* Build policy string */ + var policy_string = "CREATE POLICY " + policy_name; + + // ON clause + policy_string += " ON " + table_name; + + // FOR clause + policy_string += " FOR " + command; + + // TO clause + policy_string += " TO " + role_list.join(", "); + policy_string += ", " + user_list.join(", "); + + // USING clause + policy_string += " USING " + using_expr; + + // WITH CHECK clause + if (command !== "SELECT") { + // A SELECT policy cannot have a WITH CHECK expression, as it only applies + // in cases where records are being retrieved from the relation. + policy_string += " WITH CHECK " + check_expr; } // Remove leading and trailing spaces and then append semicolon. - query_string.trim(); - query_string += ";"; - return query_string; + policy_string.trim(); + policy_string += ";"; + + return policy_string; + }; })(); From a85aea54462a3ed39078d9882b3fe78be29a6f0b Mon Sep 17 00:00:00 2001 From: Roberto Soto <robsoto@mit.edu> Date: Wed, 24 May 2017 23:47:46 -0400 Subject: [PATCH 04/10] Renamed dq-security-policy-builder.js -> dq-rls-policy-builder.js --- .../{dq-security-policy-builder.js => dq-rls-policy-builder.js} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/apps/dataq/client_src/js/{dq-security-policy-builder.js => dq-rls-policy-builder.js} (100%) diff --git a/src/apps/dataq/client_src/js/dq-security-policy-builder.js b/src/apps/dataq/client_src/js/dq-rls-policy-builder.js similarity index 100% rename from src/apps/dataq/client_src/js/dq-security-policy-builder.js rename to src/apps/dataq/client_src/js/dq-rls-policy-builder.js From 9e7796cb178a2dfe1898c375200618cee0c84bea Mon Sep 17 00:00:00 2001 From: Roberto Soto <robsoto@mit.edu> Date: Thu, 25 May 2017 01:43:30 -0400 Subject: [PATCH 05/10] Remove "user_list" --- src/apps/dataq/client_src/js/dq-rls-policy-builder.js | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/apps/dataq/client_src/js/dq-rls-policy-builder.js b/src/apps/dataq/client_src/js/dq-rls-policy-builder.js index 4b136853..61010c1c 100644 --- a/src/apps/dataq/client_src/js/dq-rls-policy-builder.js +++ b/src/apps/dataq/client_src/js/dq-rls-policy-builder.js @@ -27,7 +27,7 @@ var policy_name = policy.name(); // Name of table to which the policy applies. - var table_name = policy.repo() + "." + policy.table_name(); + var table_name = policy.repo() + "." + policy.name(); // Command to which the policy applies. var command = policy.command(); @@ -35,10 +35,6 @@ // List of roles to which the policy applies. var role_list = policy.roles(); - // List of users to which the policy applies. - // Each element must be one of { PUBLIC | CURRENT_USER | SESSION_USER } - var user_list = policy.users(); - // SQL conditional expression to control row visibility. // Rows for which the expression returns true will be visible. var using_expr = policy.using_expression(); @@ -58,7 +54,6 @@ // TO clause policy_string += " TO " + role_list.join(", "); - policy_string += ", " + user_list.join(", "); // USING clause policy_string += " USING " + using_expr; From 0ef4d7dbf12f2a126babf1977f17727638a89978 Mon Sep 17 00:00:00 2001 From: Roberto Soto <robsoto@mit.edu> Date: Thu, 25 May 2017 01:44:01 -0400 Subject: [PATCH 06/10] Define DataQ.Policy object. --- src/apps/dataq/client_src/js/dq-rls-policy.js | 132 ++++++++++++++++++ 1 file changed, 132 insertions(+) create mode 100644 src/apps/dataq/client_src/js/dq-rls-policy.js diff --git a/src/apps/dataq/client_src/js/dq-rls-policy.js b/src/apps/dataq/client_src/js/dq-rls-policy.js new file mode 100644 index 00000000..249c5615 --- /dev/null +++ b/src/apps/dataq/client_src/js/dq-rls-policy.js @@ -0,0 +1,132 @@ +/** +* The object for building a row-level security policy. +*/ +(function() { + // If the DataQ object doesn't exist, create it. + window.DataQ = window.DataQ || {}; + + DataQ.Policy = function() { + + // Create the object and initialize its contents. + var that = {}; + that._repo_name = null; + that._name = null; + that._table = null; + that._command = null; + that._roles = []; + that._using_expr = null; + that._check_expr = null; + + /** + * Get or set the repo name. + * + * @param repo_name - If this argument is omitted (or undefined), this function acts as a + * getter. Otherwise, it acts as a setter, setting the repo name. + * + * @return The name of the repo. + */ + that.repo = function(repo_name) { + if (repo_name !== undefined) { + that._repo_name = repo_name; + } + return that._repo_name; + }; + + /** + * Get or set the name of the policy. + * + * @param policy_name - If this argument is omitted (or undefined), this function acts as a + * getter. Otherwise, it acts as a setter, setting the repo name. + * + * @return The name of the policy. + */ + that.name = function(policy_name) { + if (policy_name !== undefined) { + that._name = policy_name; + } + return that._name; + }; + + /** + * Get or set the name of the table to which this policy will apply. + * + * @param table_name - If this argument is omitted (or undefined), this function acts as a + * getter. Otherwise, it acts as a setter, setting the repo name. + * + * @return The name of the table. + */ + that.table = function(table_name) { + if (table_name !== undefined) { + that._table = table_name; + } + return that._table; + }; + + /** + * Get or set the command { ALL | SELECT | INSERT| UPDATE | DELETE } to which + * this policy will apply. + * + * @param cmd - If this argument is omitted (or undefined), this function acts as a + * getter. Otherwise, it acts as a setter, setting the repo name. + * + * @return The name of the command. + */ + that.command = function(cmd) { + if (cmd !== undefined) { + that._command = cmd; + } + return that._command; + }; + + /** + * Get or set the Roles to which this policy will apply. + * + * @param role_list - If this argument is omitted (or undefined), this function acts as a + * getter. Otherwise, it acts as a setter, setting the repo name. + * + * @return The list of Roles. + */ + that.roles = function(role_list) { + if (role_list !== undefined) { + if (!(role_list instanceof Array)) { + role_list = [role_list] + } + that._roles = role_list; + } + return that._roles; + }; + + /** + * Get or set the policy's using_expression. + * + * @param expr - If this argument is omitted (or undefined), this function acts as a + * getter. Otherwise, it acts as a setter, setting the repo name. + * + * @return The full using_expression. + */ + that.using_expression = function(expr) { + if (expr !== undefined) { + that._using_expr = expr; + } + return that._using_expr; + }; + + /** + * Get or set the policy's check_expression. + * + * @param expr - If this argument is omitted (or undefined), this function acts as a + * getter. Otherwise, it acts as a setter, setting the repo name. + * + * @return The full check_expression. + */ + that.check_expression = function(expr) { + if (expr !== undefined) { + that._check_expr = expr; + } + return that._check_expr; + }; + + return that; + + }; +})(); From 2dbaf59b0551cdedffcf50d2ea01009478ff2a3d Mon Sep 17 00:00:00 2001 From: Roberto Soto <robsoto@mit.edu> Date: Thu, 25 May 2017 20:22:41 -0400 Subject: [PATCH 07/10] Basic CREATE POLICY builder modal accessible on security policies page. --- src/apps/dataq/client_src/js/dataq.js | 28 +++++++++- .../templates/dataq-container-rls-policy.hbs | 56 +++++++++++++++++++ src/browser/templates/security-policies.html | 11 ++-- 3 files changed, 88 insertions(+), 7 deletions(-) create mode 100644 src/apps/dataq/client_src/templates/dataq-container-rls-policy.hbs diff --git a/src/apps/dataq/client_src/js/dataq.js b/src/apps/dataq/client_src/js/dataq.js index 1a8f640f..65d36d96 100644 --- a/src/apps/dataq/client_src/js/dataq.js +++ b/src/apps/dataq/client_src/js/dataq.js @@ -2,7 +2,7 @@ * Defines the DataQ.DQ object, which is what the user of the library will interact with. * * Simply call DataQ.DQ(repo_name, callback) and DataQ will launch. After the user builds a query, - * the callback is executed as callback(query), where query is a String representing the SQL query + * the callback is executed as callback(query), where query is a String representing the SQL query * or null if the query was not built successfully. */ (function() { @@ -40,6 +40,30 @@ }) }; + /** + * @param repo_name - The name of the repo that DataQ should work on. + * @param cb - The callback to trigger when the query is built. + */ + DataQ.DQ_rls_policy = function(repo_name, cb) { + // Set the callback. + callback = cb; + + // Add the container to the page. + var container = $(DataQ.templates["dataq-container-rls-policy"]()); + $('body').append(container); + + // Create the policy object and set the repo name. + policy = DataQ.Policy(); + policy.repo(repo_name); + + // Handle DataQ close when clicking backdrop. + $(".dq-black-background").click(function() { + $(".dq-black-background").remove(); + $(".dataq").remove(); + callback(null); + }); + }; + /** * Update the UI to reflect the latest query. */ @@ -107,7 +131,7 @@ query.sorts().forEach(function(sort) { sort_strings.push(sort.string); }); - + // Display the sorts. if (sort_strings.length > 0) { $(".dq-sorting-text").html(sort_strings.join(", ")); diff --git a/src/apps/dataq/client_src/templates/dataq-container-rls-policy.hbs b/src/apps/dataq/client_src/templates/dataq-container-rls-policy.hbs new file mode 100644 index 00000000..51883934 --- /dev/null +++ b/src/apps/dataq/client_src/templates/dataq-container-rls-policy.hbs @@ -0,0 +1,56 @@ +<!-- HTML file containing an extension of the overall DataQ interface. + This interface is for building DataQ.Policy objects. --> + +<!-- Create a translucent black background --> +<div class="dq-black-background"></div> + +<!-- The DataQ container --> +<div class="dataq container-fluid"> + + <h4> + DataQ-Policy-Builder allows you to write PostgreSQL CREATE POLICY commands using a simple checklist-like user interface. + </h4> + +<div class="panel-group"> + <div class="panel panel-default"> + <div class="panel-heading dq-panel" data-toggle="collapse" href="#collapseOne"> + <h4 class="panel-title"> + Policy Name + </h4> + </div> + <div id="collapseOne" class="panel-collapse collapse in"> + <div class="panel-body"> + + <input id="policyName" type="text" class="form-control" placeholder="Policy Name" /> + + </div> + </div> + + <div class="panel panel-default"> + <div class="panel-heading dq-panel" data-toggle="collapse" href="#collapseTwo"> + <h4 class="panel-title"> + Allowed commands + </h4> + </div> + <div id="collapseTwo" class="panel-collapse collapse in"> + <div class="panel-body"> + <div class="dropdown"> + <button class="btn btn-primary dropdown-toggle" type="button" data-toggle="dropdown">Choose command + <span class="caret"></span></button> + <ul class="dropdown-menu"> + <li><a href="#">SELECT</a></li> + <li><a href="#">INSERT</a></li> + <li><a href="#">UPDATE</a></li> + <li><a href="#">DELETE</a></li> + <li><a href="#">ALL</a></li> + </ul> + </div> + </div> + </div> + + <!-- Button to run query --> + <div class="row"> + <button class="btn btn-primary col-xs-3 dq-btn-run-query col-xs-offset-3">Query</button> + <button class="btn btn-default col-xs-3 dq-btn-cancel-query">Cancel</button> + </div> +</div> diff --git a/src/browser/templates/security-policies.html b/src/browser/templates/security-policies.html index 5486b298..732131ee 100644 --- a/src/browser/templates/security-policies.html +++ b/src/browser/templates/security-policies.html @@ -22,8 +22,8 @@ <h4 class="inline-block"> <div class="col-sm-2"> <button class="btn btn-primary btn-xsm" id="btn-run" type="submit">Execute</button> - <button class="btn btn-primary btn-xsm modal-upload-dialog" type="button" target-modal="#security-policy-modal" title="New Policy"> New </button> - <button class="btn btn-primary btn-xsm" id="btn-dataq" type="button">Query Builder</button> + <button class="btn btn-primary btn-xsm modal-upload-dialog" type="button" target-modal="#security-policy-modal" title="New Policy"> New Table-Level Policy </button> + <button class="btn btn-primary btn-xsm" id="btn-dataq-rls-policy" type="button">Row-Level Policy Builder</button> </div> </div> @@ -238,12 +238,13 @@ <h4 class="modal-title" id="confirm-modal-title">Update Security Policy</h4> <!-- /dataq --> <script> - $(document).on("click", "#btn-dataq", function(e) { + $(document).on("click", "#btn-dataq-rls-policy", function(e) { e.preventDefault(); - DataQ.DQ("{{repo}}", function(query) { + console.log(DataQ); + // DataQ.DQ("{{repo}}", function(query) { + DataQ.DQ_rls_policy("{{repo}}", function(query) { $("#txt-sql").val(query); }); - console.log('click'); }); </script> {% endblock js %} From 69214b42f35abc4e2c2ca797e397f32190e85572 Mon Sep 17 00:00:00 2001 From: Roberto Soto <robsoto@mit.edu> Date: Sat, 27 May 2017 13:22:42 -0400 Subject: [PATCH 08/10] Basic front end done --- src/apps/dataq/client_src/js/dataq.js | 23 ++- .../templates/dataq-container-rls-policy.hbs | 154 +++++++++++++++++- src/browser/static/dataq/css/style.css | 2 +- src/browser/static/dataq/js/dataq.min.js | 2 +- src/browser/static/dataq/js/dataq.min.js.map | 2 +- src/browser/static/dataq/templates.js | 59 +++++++ .../static/docs/html/_static/custom.css | 1 + src/browser/templates/security-policies.html | 2 - 8 files changed, 229 insertions(+), 16 deletions(-) create mode 100644 src/browser/static/docs/html/_static/custom.css diff --git a/src/apps/dataq/client_src/js/dataq.js b/src/apps/dataq/client_src/js/dataq.js index 65d36d96..1465b3f4 100644 --- a/src/apps/dataq/client_src/js/dataq.js +++ b/src/apps/dataq/client_src/js/dataq.js @@ -9,11 +9,15 @@ // Create the global DataQ object if it doesn't exist. window.DataQ = window.DataQ || {}; - // The DataQ.Query that is being built. + // The DataQ.Query that may be built. query = null; - // The callback to execute after the query is built. It is executed as cb(query) where query - // is a String representing the SQL query or null if the query was not built. + // The DataQ.Policy object that may be built. + policy = null; + + // The callback to execute after the { query | policy } is built. It is executed as + // { cb(query) | cb(policy) } where { query | policy } is a String representing the + // SQL { query | policy } or null if the { query | policy } was not built. var callback; /** @@ -197,4 +201,17 @@ $(".dataq").remove(); callback(DataQ.build_query(query)); }); + + // Handle DataQ run policy. + $(document).on("click", ".dq-btn-run-policy", function() { + // Build policy object + + + // Close DataQ + $(".dq-black-background").remove(); + $(".dataq").remove(); + + // Build policy string + callback(DataQ.build_policy(policy)); + }); })(); diff --git a/src/apps/dataq/client_src/templates/dataq-container-rls-policy.hbs b/src/apps/dataq/client_src/templates/dataq-container-rls-policy.hbs index 51883934..352ae66f 100644 --- a/src/apps/dataq/client_src/templates/dataq-container-rls-policy.hbs +++ b/src/apps/dataq/client_src/templates/dataq-container-rls-policy.hbs @@ -8,7 +8,7 @@ <div class="dataq container-fluid"> <h4> - DataQ-Policy-Builder allows you to write PostgreSQL CREATE POLICY commands using a simple checklist-like user interface. + DataQ Policy Builder allows you to write PostgreSQL CREATE POLICY commands using a simple checklist-like user interface. </h4> <div class="panel-group"> @@ -17,24 +17,24 @@ <h4 class="panel-title"> Policy Name </h4> + </div> </div> <div id="collapseOne" class="panel-collapse collapse in"> <div class="panel-body"> - - <input id="policyName" type="text" class="form-control" placeholder="Policy Name" /> - + <input id="dq-policy-name" type="text" class="form-control" placeholder="Policy Name" /> </div> </div> <div class="panel panel-default"> <div class="panel-heading dq-panel" data-toggle="collapse" href="#collapseTwo"> <h4 class="panel-title"> - Allowed commands + Allowed Commands </h4> + </div> </div> <div id="collapseTwo" class="panel-collapse collapse in"> <div class="panel-body"> - <div class="dropdown"> + <div class="dropdown" id="dq-policy-commands-dropdown"> <button class="btn btn-primary dropdown-toggle" type="button" data-toggle="dropdown">Choose command <span class="caret"></span></button> <ul class="dropdown-menu"> @@ -48,9 +48,147 @@ </div> </div> + <div class="panel panel-default"> + <div class="panel-heading dq-panel" data-toggle="collapse" href="#collapseThree"> + <h4 class="panel-title"> + Select role(s) this policy will apply to: + </h4> + </div> + </div> + <div id="collapseThree" class="panel-collapse collapse in"> + <div class="panel-body"> + <input id="dq-policy-role-list" type="text" class="form-control" placeholder="Comma separated role names. e.g. CURRENT_USER, admin, myGroupRole" /> + </div> + </div> + + <div class="panel panel-default"> + <div class="panel-heading dq-panel" data-toggle="collapse" href="#collapseFour"> + <h4 class="panel-title"> + USING expression: Rows for which this expression returns true will be visible. + </h4> + </div> + </div> + <div id="collapseFour" class="panel-collapse collapse in"> + <div class="panel-body"> + <!-- The first dropdown where the user can specify <val1> in the filter <val1> <operation> <val2> --> + <div class="input-group col-xs-4"> + <!-- Text box for entering the filter text --> + <input type="text" class="form-control dq-filter-1-text" placeholder="table1.col1 * 3"> + <!-- Dropdown for selecting a column to add to filter --> + <div class="input-group-btn"> + <button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown" aria-expanded="false"><span class="caret"></span></button> + <ul class="dropdown-menu dropdown-menu-right" role="menu"> + {{#each columns}} + <li><a href="#" class="dq-filter-1-link" data-columnname="{{this.column_name}}" data-reponame="{{../repo}}" data-tablename="{{this.table_name}}">{{this.full_name}}</a></li> + {{/each}} + </ul> + </div> + </div> <!-- end filter1 --> + + <!-- The first dropdown where the user can specify <operation> in the filter <val1> <operation> <val2> --> + <div class="input-group col-xs-3"> + <!-- Text box for entering the operation text --> + <input type="text" class="form-control dq-filter-op-text" value="="> + <!-- The possible operations are <, <=, >, >=, =, LIKE, IN, and NOT IN --> + <div class="input-group-btn"> + <button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown" aria-expanded="false"><span class="caret"></span></button> + <ul class="dropdown-menu dropdown-menu-right" role="menu"> + <li><a href="#" class="dq-filter-op-link"><</a></li> + <li><a href="#" class="dq-filter-op-link"><=</a></li> + <li><a href="#" class="dq-filter-op-link">=</a></li> + <li><a href="#" class="dq-filter-op-link">></a></li> + <li><a href="#" class="dq-filter-op-link">>=</a></li> + <li><a href="#" class="dq-filter-op-link">LIKE</a></li> + <li><a href="#" class="dq-filter-op-link">IN</a></li> + <li><a href="#" class="dq-filter-op-link">NOT IN</a></li> + </ul> + </div> + </div> <!-- end filterop --> + + <!-- The second dropdown where the user can specify <val2> in the filter <val1> <operation> <val2> --> + <div class="input-group col-xs-4"> + <!-- Text box for entering the filter text --> + <input type="text" class="form-control dq-filter-2-text" placeholder="table2.col2+3"> + <!-- Dropdown for selecting a column to add to filter --> + <div class="input-group-btn"> + <button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown" aria-expanded="false"><span class="caret"></span></button> + <ul class="dropdown-menu dropdown-menu-right" role="menu"> + {{#each columns}} + <li><a href="#" class="dq-filter-2-link" data-columnname="{{this.column_name}}" data-reponame="{{../repo}}" data-tablename="{{this.table_name}}">{{this.full_name}}</a></li> + {{/each}} + </ul> + </div> + </div> <!-- end filter2 --> +</div> +</div> + + <div class="panel panel-default"> + <div class="panel-heading dq-panel" data-toggle="collapse" href="#collapseFive"> + <h4 class="panel-title"> + WITH CHECK expression: New rows for which this expression returns true will be allowed. + </h4> + </div> + <div id="collapseFive" class="panel-collapse collapse in"> + <div class="panel-body"> + <div class="panel-body"> + <!-- The first dropdown where the user can specify <val1> in the filter <val1> <operation> <val2> --> + <div class="input-group col-xs-4"> + <!-- Text box for entering the filter text --> + <input type="text" class="form-control dq-filter-1-text" placeholder="table1.col1 * 3"> + <!-- Dropdown for selecting a column to add to filter --> + <div class="input-group-btn"> + <button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown" aria-expanded="false"><span class="caret"></span></button> + <ul class="dropdown-menu dropdown-menu-right" role="menu"> + {{#each columns}} + <li><a href="#" class="dq-filter-1-link" data-columnname="{{this.column_name}}" data-reponame="{{../repo}}" data-tablename="{{this.table_name}}">{{this.full_name}}</a></li> + {{/each}} + </ul> + </div> + </div> <!-- end filter1 --> + + <!-- The first dropdown where the user can specify <operation> in the filter <val1> <operation> <val2> --> + <div class="input-group col-xs-3"> + <!-- Text box for entering the operation text --> + <input type="text" class="form-control dq-filter-op-text" value="="> + <!-- The possible operations are <, <=, >, >=, =, LIKE, IN, and NOT IN --> + <div class="input-group-btn"> + <button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown" aria-expanded="false"><span class="caret"></span></button> + <ul class="dropdown-menu dropdown-menu-right" role="menu"> + <li><a href="#" class="dq-filter-op-link"><</a></li> + <li><a href="#" class="dq-filter-op-link"><=</a></li> + <li><a href="#" class="dq-filter-op-link">=</a></li> + <li><a href="#" class="dq-filter-op-link">></a></li> + <li><a href="#" class="dq-filter-op-link">>=</a></li> + <li><a href="#" class="dq-filter-op-link">LIKE</a></li> + <li><a href="#" class="dq-filter-op-link">IN</a></li> + <li><a href="#" class="dq-filter-op-link">NOT IN</a></li> + </ul> + </div> + </div> <!-- end filterop --> + + <!-- The first dropdown where the user can specify <val2> in the filter <val1> <operation> <val2> --> + <div class="input-group col-xs-4"> + <!-- Text box for entering the filter text --> + <input type="text" class="form-control dq-filter-2-text" placeholder="table2.col2+3"> + <!-- Dropdown for selecting a column to add to filter --> + <div class="input-group-btn"> + <button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown" aria-expanded="false"><span class="caret"></span></button> + <ul class="dropdown-menu dropdown-menu-right" role="menu"> + {{#each columns}} + <li><a href="#" class="dq-filter-2-link" data-columnname="{{this.column_name}}" data-reponame="{{../repo}}" data-tablename="{{this.table_name}}">{{this.full_name}}</a></li> + {{/each}} + </ul> + </div> + </div> <!-- end filter2 --> + </div> + + </div> + </div> +</div> + <!-- Button to run query --> <div class="row"> - <button class="btn btn-primary col-xs-3 dq-btn-run-query col-xs-offset-3">Query</button> - <button class="btn btn-default col-xs-3 dq-btn-cancel-query">Cancel</button> + <button class="btn btn-primary col-xs-3 dq-btn-create-policy col-xs-offset-3">Create Policy</button> + <button class="btn btn-default col-xs-3 dq-btn-cancel-create-policy">Cancel</button> </div> </div> diff --git a/src/browser/static/dataq/css/style.css b/src/browser/static/dataq/css/style.css index f22c496b..e0acd6d5 100644 --- a/src/browser/static/dataq/css/style.css +++ b/src/browser/static/dataq/css/style.css @@ -122,4 +122,4 @@ .dq-grouping-modal-list { margin-top: 20px; list-style: none; -} \ No newline at end of file +} diff --git a/src/browser/static/dataq/js/dataq.min.js b/src/browser/static/dataq/js/dataq.min.js index 01ce4c7e..65b33526 100644 --- a/src/browser/static/dataq/js/dataq.min.js +++ b/src/browser/static/dataq/js/dataq.min.js @@ -1,2 +1,2 @@ -!function(){window.DataQ=window.DataQ||{},query=null;var t;DataQ.DQ=function(o,e){t=e;var n=$(DataQ.templates["dataq-container"]());$("body").append(n),query=DataQ.Query(),query.repo(o),$(".dq-black-background").click(function(){$(".dq-black-background").remove(),$(".dataq").remove(),t(null)})};var o=function(){$(".dq-selected-tables").html(""),query.get_selected_tables().forEach(function(t){var o=query.selected_columns(t);if(null!==o){var e=[];o.forEach(function(t){"none"===t.agg?e.push(t.name):e.push(t.agg+"("+t.name+")")});var n=DataQ.templates["dq-selected-table"]({table_name:t,column_list:e.join(", ")});$(".dq-selected-tables").append(n)}}),$(".dq-filter-list").html(""),query.get_filters().forEach(function(t){$(".dq-filter-list").append(DataQ.templates["dq-filter-list-item"]({filter:t}))});var t=[];query.grouping().forEach(function(o){t.push(o.string)}),t.length>0?$(".dq-grouping-text").html(t.join(", ")):$(".dq-grouping-text").html("No Grouping...");var o=[];query.sorts().forEach(function(t){o.push(t.string)}),o.length>0?$(".dq-sorting-text").html(o.join(", ")):$(".dq-sorting-text").html("No Sorting...")};$(document).on("click",".dq-btn-add-table",function(){DataQ.TableModal(query,null,o)}),$(document).on("click",".dq-btn-delete-table",function(){var t=$(this).data("tablename");query.operated_column()&&query.operated_column().split(".")[0]===t&&query.operated_column(null),query.selected_columns(t,null),query.update_grouping(),o()}),$(document).on("click",".dq-btn-edit-table",function(){DataQ.TableModal(query,$(this).data("tablename"),o)}),$(document).on("click",".dq-btn-add-filter",function(){DataQ.FilterModal(query,o)}),$(document).on("click",".dq-btn-delete-filter",function(){var t=$(this).parent().data("code");query.delete_filter(t),o()}),$(document).on("click",".dq-btn-edit-grouping",function(){DataQ.GroupingModal(query,o)}),$(document).on("click",".dq-btn-edit-sorting",function(){DataQ.SortModal(query,o)}),$(document).on("click",".dq-btn-cancel-query",function(){$(".dq-black-background").remove(),$(".dataq").remove(),t(null)}),$(document).on("click",".dq-btn-run-query",function(){$(".dq-black-background").remove(),$(".dataq").remove(),t(DataQ.build_query(query))})}(),function(){window.DataQ=window.DataQ||{},DataQ.API={},DataQ.API.get_repos=function(t){$.get("/apps/dataq/api/",t)},DataQ.API.get_tables=function(t,o){$.get("/apps/dataq/api/"+t+"/",o)},DataQ.API.get_schema=function(t,o,e){$.get("/apps/dataq/api/"+t+"/"+o+"/",e)}}(),function(){window.DataQ=window.DataQ||{};var t,o;DataQ.FilterModal=function(e,n){t=n,o=e;var a=$("#dq-filter-modal");if(0===a.length){var r=[];o.get_selected_tables().forEach(function(t){o.schema(t).forEach(function(o){r.push({column_name:o[0],table_name:t,full_name:t+"."+o[0]})})});var l=DataQ.templates["dq-filter-modal"]({columns:r,repo:o.repo()});$("body").append(l)}$("#dq-filter-modal").modal({keyboard:!1}),$(".modal-backdrop").click(function(){t(),$("#dq-filter-modal").remove()})},$(document).on("click",".dq-filter-quit",function(){t(),$("#dq-filter-modal").remove(),$(".modal-backdrop").remove()}),$(document).on("click",".dq-filter-done",function(){var e=$(".dq-filter-1-text").val(),n=$(".dq-filter-2-text").val(),a=$(".dq-filter-op-text").val();e.length>0&&n.length>0&&a.length>0?(o.add_filter(e,a,n),t(),$("#dq-filter-modal").modal("hide"),$("#dq-filter-modal").remove(),$(".modal-backdrop").remove()):alert("You need to fill out the three text boxes")}),$(document).on("click",".dq-filter-1-link",function(){$(".dq-filter-1-text").val($(this).html())}),$(document).on("click",".dq-filter-2-link",function(){$(".dq-filter-2-text").val($(this).html())}),$(document).on("click",".dq-filter-op-link",function(){var t=$(this).html().replace(">",">").replace("<","<");$(".dq-filter-op-text").val(t)})}(),function(){window.DataQ=window.DataQ||{};var t,o;DataQ.GroupingModal=function(e,n){o=e,t=n;var a=$("#dq-grouping-modal");if(0===a.length){var r=DataQ.templates["dq-grouping-modal"]({columns:o.grouping()});$("body").append(r)}$("#dq-grouping-modal").modal({keyboard:!1}),$("#dq-grouping-modal").on("shown.bs.modal",function(){$(".dq-grouping-modal-list").sortable({forcePlaceholderSize:!0})}),$(".modal-backdrop").click(function(){t(),$("#dq-grouping-modal").remove(),$(".modal-backdrop").remove()})},$(document).on("click","#dq-grouping-modal-quit-btn",function(){t(),$("#dq-grouping-modal").remove(),$(".modal-backdrop").remove()}),$(document).on("click","#dq-grouping-modal-done-btn",function(){var e=[];$(".dq-grouping-list-item").each(function(){var t,n=$(this),a=n.data("string");o.grouping();o.grouping().forEach(function(o){o.string===a&&(t=o)}),e.push(t)}),o.grouping(e),t(),$("#dq-grouping-modal").remove(),$(".modal-backdrop").remove()})}(),function(){window.DataQ=window.DataQ||{};for(var t=["bigint","int8","bigserial","serial8","double precision","float8","integer","int","int4","real","float4","smallint","int2","serial","serial4"],o={},e=0;e<t;e++)o[t[e]]=!0;var n={none:"max",max:"min",min:"sum",sum:"count",count:"avg",avg:"none"},a={none:"count",count:"none"};DataQ.next_aggregate=function(t,e){return null===e&&(e="none"),o[t]?n[e]:a[e]}}(),function(){window.DataQ=window.DataQ||{},window.DataQ.build_query=function(t){var o=[],e=[],n=[],a=[],r=[],l=t.repo();if(t.get_selected_tables().forEach(function(t){e.push(l+"."+t)}),t.get_selected_tables().forEach(function(e){t.selected_columns(e).forEach(function(t){void 0===t.agg||null===t.agg||"none"===t.agg?o.push(l+"."+e+"."+t.name):o.push(t.agg+"("+l+"."+e+"."+t.name+") as "+t.agg+"_"+e+"_"+t.name)})}),t.get_filters().forEach(function(t){n.push(t.filter1+" "+t.op+" "+t.filter2)}),t.grouping().forEach(function(t){var o=t.column.agg;null!==o&&void 0!==o&&"none"!==o||a.push(l+"."+t.string)}),t.sorts().forEach(function(t){var o=t.column.agg;null===o||void 0===o||"none"===o?r.push(l+"."+t.string):r.push(o+"_"+t.table+"_"+t.column.name)}),0===o.length)return"";var d="SELECT "+o.join(", ")+" FROM "+e.join(", ");return n.length>0&&(d+=" WHERE "+n.join(" AND ")),a.length>0&&(d+=" GROUP BY "+a.join(", ")),r.length>0&&(d+=" ORDER BY "+r.join(", ")),d.trim(),d+=";"}}(),function(){window.DataQ=window.DataQ||{},DataQ.Query=function(){var t={};return t._schema_for_table_name={},t._repo_name=null,t._operated_column=null,t._selected_columns_for_table={},t._filter_for_code={},t._grouping=[],t._sorts={},t.schema=function(o,e){return void 0!==e&&(t._schema_for_table_name[o]=e),t._schema_for_table_name[o]},t.repo=function(o){return void 0!==o&&(t._repo_name=o),t._repo_name},t.operated_column=function(o){return void 0!==o&&(t._operated_column=o),t._operated_column},t.update_grouping=function(){t._grouping=[];var o=!1;for(var e in t._selected_columns_for_table)t._selected_columns_for_table[e]&&t._selected_columns_for_table[e].forEach(function(n){"none"===n.agg?t._grouping.push({string:e+"."+n.name,table:e,column:n}):o=!0});o||(t._grouping=[])},t.selected_columns=function(o,e){return void 0!==e&&(t._selected_columns_for_table[o]=e),t._selected_columns_for_table[o]},t.get_selected_tables=function(){var o=[];for(var e in t._selected_columns_for_table)o.push(e);return o},t.add_filter=function(o,e,n){var a=o+" "+e+" "+n,r=md5((new Date).getTime()+a);return t._filter_for_code[r]={filter1:o,op:e,filter2:n,filter_string:a},t._filter_for_code[r]},t.delete_filter=function(o){t._filter_for_code[o]=void 0},t.get_filters=function(){var o=[];for(var e in t._filter_for_code){var n=t._filter_for_code[e];n&&o.push({code:e,filter1:n.filter1,op:n.op,filter2:n.filter2,filter_string:n.filter_string})}return o},t.grouping=function(o){return void 0!==o&&(t._grouping=o),t._grouping},t.add_sort=function(o){t._sorts[o.string]=o},t.delete_sort=function(o){t._sorts[o]=void 0},t.sorts=function(){var o=[];for(var e in t._sorts)void 0!==t._sorts[e]&&o.push(t._sorts[e]);return o},t}}(),function(){window.DataQ=window.DataQ||{};var t,o;DataQ.SortModal=function(e,a){o=e,t=a;var r=$("#dq-sort-modal");if(0===r.length){var l=DataQ.templates["dq-sort-modal"]();$("body").append(l)}$("#dq-sort-modal").modal({keyboard:!1}),$("#dq-sort-modal").on("shown.bs.modal",function(){n()}),$(".modal-backdrop").click(function(){$("#dq-sort-modal").remove(),$(".modal-backdrop").remove(),t()})},$(document).on("click",".dq-sort-modal-dropdown-btn",function(){var t=$(".dq-sort-modal-dropdown");t.html(""),e().forEach(function(o){t.append(DataQ.templates["dq-sort-dropdown-li"]({item:o}))})});var e=function(){var t={};o.sorts().forEach(function(o){t[o.string]=!0});var e=[];return o.get_selected_tables().forEach(function(n){o.selected_columns(n).forEach(function(o){var a=n+"."+o.name;"none"!==o.agg&&(a=o.agg+"("+n+"."+o.name+")"),t[a]||e.push({string:a,table:n,column:o})})}),e},n=function(){var t=$(".dq-sort-item-list");t.html(""),o.sorts().forEach(function(o){var e=DataQ.templates["dq-sort-list-item"]({item:o});t.append(e)})};$(document).on("click",".dq-sort-modal-quit",function(){$("#dq-sort-modal").remove(),$(".modal-backdrop").remove(),t()}),$(document).on("click",".dq-sort-link",function(){var t=$(this),e=t.data("columnname"),a=t.data("columntype"),r=t.data("aggregate"),l=t.data("table"),d=t.data("string");void 0!==r&&null!==r||(r="none");var i={column:{name:e,type:a,agg:r},string:d,table:l};o.add_sort(i),n()}),$(document).on("click",".dq-sort-modal-done-btn",function(){$("#dq-sort-modal").remove(),$(".modal-backdrop").remove(),t()}),$(document).on("click",".dq-sort-delete-btn",function(){var t=$(this).parent().parent(),e=t.data("string");o.delete_sort(e),n()})}(),function(){window.DataQ=window.DataQ||{};var t,o,e;DataQ.TableModal=function(a,r,l){o=r,t=l,e=a;var d=$("#dq-table-modal");if(0===d.length){var i=DataQ.templates["dq-table-modal"]({table_name:o});$("body").append(i)}$("#dq-table-modal").modal({keyboard:!1}),$(".dq-modal-done-btn").hide(),$("#dq-table-modal").on("shown.bs.modal",function(){o&&n(o)}),$(".modal-backdrop").click(function(){$("#dq-table-modal").remove(),$(".modal-backdrop").remove(),t()})},$(document).on("click",".dq-modal-quit",function(){$("#dq-table-modal").remove(),$(".modal-backdrop").remove(),t()}),$(document).on("click",".dq-modal-dropdown-btn",function(){var t=$(".dq-modal-dropdown");t.html(""),DataQ.API.get_tables(e.repo(),function(o){o.tables.forEach(function(o){var e=DataQ.templates["dq-modal-dropdown-item"]({item_name:o});t.append(e)})})}),$(document).on("click",".dq-modal-dropdown-link",function(){var t=$(this).data("item_name");o=t,$(".dq-modal-table-dropdown-text").text(o),n()});var n=function(){DataQ.API.get_schema(e.repo(),o,function(t){$(".dq-modal-done-btn").show(),e.schema(o,t.schema).sort(function(t,o){return t[0]>o[0]});var n=DataQ.templates["dq-modal-columns"]({columns:e.schema(o)});$(".dq-column-list").html(n),e.selected_columns(o)&&e.selected_columns(o).forEach(function(t){var o=$('.dq-modal-column[data-columnname="'+t.name+'"]');o.data("columnname",t.name),o.data("columntype",t.type),o.data("currentaggregate",t.agg||"none"),o.find("input[type=checkbox]").prop("checked",!0),"none"!==t.agg&&o.find("button").text(t.agg+"("+t.name+")")})})};$(document).on("click",".dq-modal-column button",function(){var t=$(this).parent(),n=t.data("columnname"),a=t.data("columntype"),r=t.data("currentaggregate"),l=DataQ.next_aggregate(a,r);e.operated_column()!==o+"."+n&&null!==e.operated_column()&&(l="none"),"none"===l?(e.operated_column()===o+"."+n&&e.operated_column(null),$(this).text(n)):($(this).text(l+"("+n+")"),e.operated_column(o+"."+n)),t.data("currentaggregate",l)}),$(document).on("click",".dq-modal-done-btn",function(){var n=[],a=!1;$(".dq-modal-column").each(function(){var t=$(this);if(t.find("input").is(":checked")){var r=t.data("currentaggregate"),l=t.data("columntype"),d=t.data("columnname");o+"."+d===e.operated_column()&&(a=!0),null!==r&&void 0!==r||(r="none"),n.push({name:d,type:l,agg:r})}}),e.operated_column()&&e.operated_column().split(".")[0]===o&&!a&&e.operated_column(null),e.selected_columns(o,n),e.update_grouping(),$("#dq-table-modal").remove(),$(".modal-backdrop").remove(),console.log("help me"),console.log("now"),t()})}(); +!function(){window.DataQ=window.DataQ||{},query=null,policy=null;var n;DataQ.DQ=function(o,t){n=t;var e=$(DataQ.templates["dataq-container"]());$("body").append(e),query=DataQ.Query(),query.repo(o),$(".dq-black-background").click(function(){$(".dq-black-background").remove(),$(".dataq").remove(),n(null)})},DataQ.DQ_rls_policy=function(o,t){n=t;var e=$(DataQ.templates["dataq-container-rls-policy"]());$("body").append(e),policy=DataQ.Policy(),policy.repo(o),$(".dq-black-background").click(function(){$(".dq-black-background").remove(),$(".dataq").remove(),n(null)})};var o=function(){$(".dq-selected-tables").html(""),query.get_selected_tables().forEach(function(n){var o=query.selected_columns(n);if(null!==o){var t=[];o.forEach(function(n){"none"===n.agg?t.push(n.name):t.push(n.agg+"("+n.name+")")});var e=DataQ.templates["dq-selected-table"]({table_name:n,column_list:t.join(", ")});$(".dq-selected-tables").append(e)}}),$(".dq-filter-list").html(""),query.get_filters().forEach(function(n){$(".dq-filter-list").append(DataQ.templates["dq-filter-list-item"]({filter:n}))});var n=[];query.grouping().forEach(function(o){n.push(o.string)}),n.length>0?$(".dq-grouping-text").html(n.join(", ")):$(".dq-grouping-text").html("No Grouping...");var o=[];query.sorts().forEach(function(n){o.push(n.string)}),o.length>0?$(".dq-sorting-text").html(o.join(", ")):$(".dq-sorting-text").html("No Sorting...")};$(document).on("click",".dq-btn-add-table",function(){DataQ.TableModal(query,null,o)}),$(document).on("click",".dq-btn-delete-table",function(){var n=$(this).data("tablename");query.operated_column()&&query.operated_column().split(".")[0]===n&&query.operated_column(null),query.selected_columns(n,null),query.update_grouping(),o()}),$(document).on("click",".dq-btn-edit-table",function(){DataQ.TableModal(query,$(this).data("tablename"),o)}),$(document).on("click",".dq-btn-add-filter",function(){DataQ.FilterModal(query,o)}),$(document).on("click",".dq-btn-delete-filter",function(){var n=$(this).parent().data("code");query.delete_filter(n),o()}),$(document).on("click",".dq-btn-edit-grouping",function(){DataQ.GroupingModal(query,o)}),$(document).on("click",".dq-btn-edit-sorting",function(){DataQ.SortModal(query,o)}),$(document).on("click",".dq-btn-cancel-query",function(){$(".dq-black-background").remove(),$(".dataq").remove(),n(null)}),$(document).on("click",".dq-btn-run-query",function(){$(".dq-black-background").remove(),$(".dataq").remove(),n(DataQ.build_query(query))}),$(document).on("click",".dq-btn-run-policy",function(){$(".dq-black-background").remove(),$(".dataq").remove(),n(DataQ.build_policy(policy))})}(),function(){window.DataQ=window.DataQ||{},DataQ.API={},DataQ.API.get_repos=function(n){$.get("/apps/dataq/api/",n)},DataQ.API.get_tables=function(n,o){$.get("/apps/dataq/api/"+n+"/",o)},DataQ.API.get_schema=function(n,o,t){$.get("/apps/dataq/api/"+n+"/"+o+"/",t)}}(),function(){window.DataQ=window.DataQ||{};var n,o;DataQ.FilterModal=function(t,e){n=e,o=t;var a=$("#dq-filter-modal");if(0===a.length){var r=[];o.get_selected_tables().forEach(function(n){o.schema(n).forEach(function(o){r.push({column_name:o[0],table_name:n,full_name:n+"."+o[0]})})});var l=DataQ.templates["dq-filter-modal"]({columns:r,repo:o.repo()});$("body").append(l)}$("#dq-filter-modal").modal({keyboard:!1}),$(".modal-backdrop").click(function(){n(),$("#dq-filter-modal").remove()})},$(document).on("click",".dq-filter-quit",function(){n(),$("#dq-filter-modal").remove(),$(".modal-backdrop").remove()}),$(document).on("click",".dq-filter-done",function(){var t=$(".dq-filter-1-text").val(),e=$(".dq-filter-2-text").val(),a=$(".dq-filter-op-text").val();t.length>0&&e.length>0&&a.length>0?(o.add_filter(t,a,e),n(),$("#dq-filter-modal").modal("hide"),$("#dq-filter-modal").remove(),$(".modal-backdrop").remove()):alert("You need to fill out the three text boxes")}),$(document).on("click",".dq-filter-1-link",function(){$(".dq-filter-1-text").val($(this).html())}),$(document).on("click",".dq-filter-2-link",function(){$(".dq-filter-2-text").val($(this).html())}),$(document).on("click",".dq-filter-op-link",function(){var n=$(this).html().replace(">",">").replace("<","<");$(".dq-filter-op-text").val(n)})}(),function(){window.DataQ=window.DataQ||{};var n,o;DataQ.GroupingModal=function(t,e){o=t,n=e;var a=$("#dq-grouping-modal");if(0===a.length){var r=DataQ.templates["dq-grouping-modal"]({columns:o.grouping()});$("body").append(r)}$("#dq-grouping-modal").modal({keyboard:!1}),$("#dq-grouping-modal").on("shown.bs.modal",function(){$(".dq-grouping-modal-list").sortable({forcePlaceholderSize:!0})}),$(".modal-backdrop").click(function(){n(),$("#dq-grouping-modal").remove(),$(".modal-backdrop").remove()})},$(document).on("click","#dq-grouping-modal-quit-btn",function(){n(),$("#dq-grouping-modal").remove(),$(".modal-backdrop").remove()}),$(document).on("click","#dq-grouping-modal-done-btn",function(){var t=[];$(".dq-grouping-list-item").each(function(){var n,e=$(this),a=e.data("string");o.grouping();o.grouping().forEach(function(o){o.string===a&&(n=o)}),t.push(n)}),o.grouping(t),n(),$("#dq-grouping-modal").remove(),$(".modal-backdrop").remove()})}(),function(){window.DataQ=window.DataQ||{};for(var n=["bigint","int8","bigserial","serial8","double precision","float8","integer","int","int4","real","float4","smallint","int2","serial","serial4"],o={},t=0;t<n;t++)o[n[t]]=!0;var e={none:"max",max:"min",min:"sum",sum:"count",count:"avg",avg:"none"},a={none:"count",count:"none"};DataQ.next_aggregate=function(n,t){return null===t&&(t="none"),o[n]?e[t]:a[t]}}(),function(){window.DataQ=window.DataQ||{},window.DataQ.build_query=function(n){var o=[],t=[],e=[],a=[],r=[],l=n.repo();if(n.get_selected_tables().forEach(function(n){t.push(l+"."+n)}),n.get_selected_tables().forEach(function(t){n.selected_columns(t).forEach(function(n){void 0===n.agg||null===n.agg||"none"===n.agg?o.push(l+"."+t+"."+n.name):o.push(n.agg+"("+l+"."+t+"."+n.name+") as "+n.agg+"_"+t+"_"+n.name)})}),n.get_filters().forEach(function(n){e.push(n.filter1+" "+n.op+" "+n.filter2)}),n.grouping().forEach(function(n){var o=n.column.agg;null!==o&&void 0!==o&&"none"!==o||a.push(l+"."+n.string)}),n.sorts().forEach(function(n){var o=n.column.agg;null===o||void 0===o||"none"===o?r.push(l+"."+n.string):r.push(o+"_"+n.table+"_"+n.column.name)}),0===o.length)return"";var d="SELECT "+o.join(", ")+" FROM "+t.join(", ");return e.length>0&&(d+=" WHERE "+e.join(" AND ")),a.length>0&&(d+=" GROUP BY "+a.join(", ")),r.length>0&&(d+=" ORDER BY "+r.join(", ")),d.trim(),d+=";"}}(),function(){window.DataQ=window.DataQ||{},DataQ.Query=function(){var n={};return n._schema_for_table_name={},n._repo_name=null,n._operated_column=null,n._selected_columns_for_table={},n._filter_for_code={},n._grouping=[],n._sorts={},n.schema=function(o,t){return void 0!==t&&(n._schema_for_table_name[o]=t),n._schema_for_table_name[o]},n.repo=function(o){return void 0!==o&&(n._repo_name=o),n._repo_name},n.operated_column=function(o){return void 0!==o&&(n._operated_column=o),n._operated_column},n.update_grouping=function(){n._grouping=[];var o=!1;for(var t in n._selected_columns_for_table)n._selected_columns_for_table[t]&&n._selected_columns_for_table[t].forEach(function(e){"none"===e.agg?n._grouping.push({string:t+"."+e.name,table:t,column:e}):o=!0});o||(n._grouping=[])},n.selected_columns=function(o,t){return void 0!==t&&(n._selected_columns_for_table[o]=t),n._selected_columns_for_table[o]},n.get_selected_tables=function(){var o=[];for(var t in n._selected_columns_for_table)o.push(t);return o},n.add_filter=function(o,t,e){var a=o+" "+t+" "+e,r=md5((new Date).getTime()+a);return n._filter_for_code[r]={filter1:o,op:t,filter2:e,filter_string:a},n._filter_for_code[r]},n.delete_filter=function(o){n._filter_for_code[o]=void 0},n.get_filters=function(){var o=[];for(var t in n._filter_for_code){var e=n._filter_for_code[t];e&&o.push({code:t,filter1:e.filter1,op:e.op,filter2:e.filter2,filter_string:e.filter_string})}return o},n.grouping=function(o){return void 0!==o&&(n._grouping=o),n._grouping},n.add_sort=function(o){n._sorts[o.string]=o},n.delete_sort=function(o){n._sorts[o]=void 0},n.sorts=function(){var o=[];for(var t in n._sorts)void 0!==n._sorts[t]&&o.push(n._sorts[t]);return o},n}}(),function(){window.DataQ=window.DataQ||{},window.DataQ.build_policy=function(n){var o=n.name(),t=n.repo()+"."+n.name(),e=n.command(),a=n.roles(),r=n.using_expression(),l=n.check_expression(),d="CREATE POLICY "+o;return d+=" ON "+t,d+=" FOR "+e,d+=" TO "+a.join(", "),d+=" USING "+r,"SELECT"!==e&&(d+=" WITH CHECK "+l),d.trim(),d+=";"}}(),function(){window.DataQ=window.DataQ||{},DataQ.Policy=function(){var n={};return n._repo_name=null,n._name=null,n._table=null,n._command=null,n._roles=[],n._using_expr=null,n._check_expr=null,n.repo=function(o){return void 0!==o&&(n._repo_name=o),n._repo_name},n.name=function(o){return void 0!==o&&(n._name=o),n._name},n.table=function(o){return void 0!==o&&(n._table=o),n._table},n.command=function(o){return void 0!==o&&(n._command=o),n._command},n.roles=function(o){return void 0!==o&&(o instanceof Array||(o=[o]),n._roles=o),n._roles},n.using_expression=function(o){return void 0!==o&&(n._using_expr=o),n._using_expr},n.check_expression=function(o){return void 0!==o&&(n._check_expr=o),n._check_expr},n}}(),function(){window.DataQ=window.DataQ||{};var n,o;DataQ.SortModal=function(t,a){o=t,n=a;var r=$("#dq-sort-modal");if(0===r.length){var l=DataQ.templates["dq-sort-modal"]();$("body").append(l)}$("#dq-sort-modal").modal({keyboard:!1}),$("#dq-sort-modal").on("shown.bs.modal",function(){e()}),$(".modal-backdrop").click(function(){$("#dq-sort-modal").remove(),$(".modal-backdrop").remove(),n()})},$(document).on("click",".dq-sort-modal-dropdown-btn",function(){var n=$(".dq-sort-modal-dropdown");n.html(""),t().forEach(function(o){n.append(DataQ.templates["dq-sort-dropdown-li"]({item:o}))})});var t=function(){var n={};o.sorts().forEach(function(o){n[o.string]=!0});var t=[];return o.get_selected_tables().forEach(function(e){o.selected_columns(e).forEach(function(o){var a=e+"."+o.name;"none"!==o.agg&&(a=o.agg+"("+e+"."+o.name+")"),n[a]||t.push({string:a,table:e,column:o})})}),t},e=function(){var n=$(".dq-sort-item-list");n.html(""),o.sorts().forEach(function(o){var t=DataQ.templates["dq-sort-list-item"]({item:o});n.append(t)})};$(document).on("click",".dq-sort-modal-quit",function(){$("#dq-sort-modal").remove(),$(".modal-backdrop").remove(),n()}),$(document).on("click",".dq-sort-link",function(){var n=$(this),t=n.data("columnname"),a=n.data("columntype"),r=n.data("aggregate"),l=n.data("table"),d=n.data("string");void 0!==r&&null!==r||(r="none");var i={column:{name:t,type:a,agg:r},string:d,table:l};o.add_sort(i),e()}),$(document).on("click",".dq-sort-modal-done-btn",function(){$("#dq-sort-modal").remove(),$(".modal-backdrop").remove(),n()}),$(document).on("click",".dq-sort-delete-btn",function(){var n=$(this).parent().parent(),t=n.data("string");o.delete_sort(t),e()})}(),function(){window.DataQ=window.DataQ||{};var n,o,t;DataQ.TableModal=function(a,r,l){o=r,n=l,t=a;var d=$("#dq-table-modal");if(0===d.length){var i=DataQ.templates["dq-table-modal"]({table_name:o});$("body").append(i)}$("#dq-table-modal").modal({keyboard:!1}),$(".dq-modal-done-btn").hide(),$("#dq-table-modal").on("shown.bs.modal",function(){o&&e(o)}),$(".modal-backdrop").click(function(){$("#dq-table-modal").remove(),$(".modal-backdrop").remove(),n()})},$(document).on("click",".dq-modal-quit",function(){$("#dq-table-modal").remove(),$(".modal-backdrop").remove(),n()}),$(document).on("click",".dq-modal-dropdown-btn",function(){var n=$(".dq-modal-dropdown");n.html(""),DataQ.API.get_tables(t.repo(),function(o){o.tables.forEach(function(o){var t=DataQ.templates["dq-modal-dropdown-item"]({item_name:o});n.append(t)})})}),$(document).on("click",".dq-modal-dropdown-link",function(){var n=$(this).data("item_name");o=n,$(".dq-modal-table-dropdown-text").text(o),e()});var e=function(){DataQ.API.get_schema(t.repo(),o,function(n){$(".dq-modal-done-btn").show(),t.schema(o,n.schema).sort(function(n,o){return n[0]>o[0]});var e=DataQ.templates["dq-modal-columns"]({columns:t.schema(o)});$(".dq-column-list").html(e),t.selected_columns(o)&&t.selected_columns(o).forEach(function(n){var o=$('.dq-modal-column[data-columnname="'+n.name+'"]');o.data("columnname",n.name),o.data("columntype",n.type),o.data("currentaggregate",n.agg||"none"),o.find("input[type=checkbox]").prop("checked",!0),"none"!==n.agg&&o.find("button").text(n.agg+"("+n.name+")")})})};$(document).on("click",".dq-modal-column button",function(){var n=$(this).parent(),e=n.data("columnname"),a=n.data("columntype"),r=n.data("currentaggregate"),l=DataQ.next_aggregate(a,r);t.operated_column()!==o+"."+e&&null!==t.operated_column()&&(l="none"),"none"===l?(t.operated_column()===o+"."+e&&t.operated_column(null),$(this).text(e)):($(this).text(l+"("+e+")"),t.operated_column(o+"."+e)),n.data("currentaggregate",l)}),$(document).on("click",".dq-modal-done-btn",function(){var e=[],a=!1;$(".dq-modal-column").each(function(){var n=$(this);if(n.find("input").is(":checked")){var r=n.data("currentaggregate"),l=n.data("columntype"),d=n.data("columnname");o+"."+d===t.operated_column()&&(a=!0),null!==r&&void 0!==r||(r="none"),e.push({name:d,type:l,agg:r})}}),t.operated_column()&&t.operated_column().split(".")[0]===o&&!a&&t.operated_column(null),t.selected_columns(o,e),t.update_grouping(),$("#dq-table-modal").remove(),$(".modal-backdrop").remove(),n()})}(); //# sourceMappingURL=dataq.min.js.map diff --git a/src/browser/static/dataq/js/dataq.min.js.map b/src/browser/static/dataq/js/dataq.min.js.map index 9b22dc13..f4c0f536 100644 --- a/src/browser/static/dataq/js/dataq.min.js.map +++ b/src/browser/static/dataq/js/dataq.min.js.map @@ -1 +1 @@ -{"version":3,"sources":["dataq.js","dq-api.js","dq-filter-modal.js","dq-grouping-modal.js","dq-next-aggregate.js","dq-query-builder.js","dq-query.js","dq-sort-modal.js","dq-table-modal.js"],"names":["window","DataQ","query","callback","DQ","repo_name","cb","container","$","templates","append","Query","repo","click","remove","display_query","html","get_selected_tables","forEach","selected_table","selected_columns","column_list","selected_column","agg","push","name","table_name","join","get_filters","filter","group_strings","grouping","group","string","length","sort_strings","sorts","sort","document","on","TableModal","this","data","operated_column","split","update_grouping","FilterModal","code","parent","delete_filter","GroupingModal","SortModal","build_query","API","get_repos","get","get_tables","get_schema","table","q","modal","columns","schema","column","column_name","full_name","keyboard","filter1","val","filter2","op","add_filter","alert","replace","sortable","forcePlaceholderSize","new_grouping","each","current_group","li","number_types","is_number","i","next_numeric_aggregate","none","max","min","sum","count","avg","next_nonnumeric_aggregate","next_aggregate","columntype","current_aggregate","select_list","from_list","where_list","group_list","order_list","undefined","query_string","trim","that","_schema_for_table_name","_repo_name","_operated_column","_selected_columns_for_table","_filter_for_code","_grouping","_sorts","has_operated_column","tbls","k","filter_string","md5","Date","getTime","result","add_sort","delete_sort","update_list","list","create_items_in_dropdown","item","used_dict","items","type","hide","populate_column_list","dropdown","tables","item_name","text","show","a","b","element","find","prop","parent_li","columnname","currentaggregate","nextaggregate","is_op_col_checked","is","console","log"],"mappings":"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","file":"dataq.min.js","sourcesContent":["/**\n * Defines the DataQ.DQ object, which is what the user of the library will interact with.\n *\n * Simply call DataQ.DQ(repo_name, callback) and DataQ will launch. After the user builds a query,\n * the callback is executed as callback(query), where query is a String representing the SQL query \n * or null if the query was not built successfully.\n */\n(function() {\n // Create the global DataQ object if it doesn't exist.\n window.DataQ = window.DataQ || {};\n\n // The DataQ.Query that is being built.\n query = null;\n\n // The callback to execute after the query is built. It is executed as cb(query) where query\n // is a String representing the SQL query or null if the query was not built.\n var callback;\n\n /**\n * @param repo_name - The name of the repo that DataQ should work on.\n * @param cb - The callback to trigger when the query is built.\n */\n DataQ.DQ = function(repo_name, cb) {\n // Set the callback.\n callback = cb;\n\n // Add the container to the page.\n var container = $(DataQ.templates[\"dataq-container\"]());\n $('body').append(container);\n\n // Create the query object and set the repo name.\n query = DataQ.Query();\n query.repo(repo_name);\n\n // Handle DataQ close when clicking backdrop.\n $(\".dq-black-background\").click(function() {\n $(\".dq-black-background\").remove();\n $(\".dataq\").remove();\n callback(null);\n })\n };\n\n /**\n * Update the UI to reflect the latest query.\n */\n var display_query = function() {\n\n /**********************************/\n /*** 1: Selected Tables/Columns ***/\n /**********************************/\n $('.dq-selected-tables').html(\"\");\n query.get_selected_tables().forEach(function(selected_table) {\n var selected_columns = query.selected_columns(selected_table);\n if (selected_columns === null) {\n return;\n }\n\n // Go through each column for the table and add it to the column list.\n var column_list = [];\n selected_columns.forEach(function(selected_column) {\n if (selected_column.agg === \"none\") {\n column_list.push(selected_column.name);\n } else {\n // If col has an aggregate, write it as \"aggregate(col)\".\n column_list.push(selected_column.agg + \"(\" + selected_column.name + \")\");\n }\n });\n\n // Add the table and column list to the UI.\n var html = DataQ.templates[\"dq-selected-table\"]({\n \"table_name\": selected_table,\n \"column_list\": column_list.join(\", \")\n });\n $('.dq-selected-tables').append(html);\n });\n\n /***************************/\n /*** 2: Selected Filters ***/\n /***************************/\n $('.dq-filter-list').html(\"\");\n query.get_filters().forEach(function(filter) {\n $('.dq-filter-list').append(DataQ.templates['dq-filter-list-item']({\n \"filter\": filter\n }));\n });\n\n /**************************/\n /*** 3: Selected Groups ***/\n /**************************/\n // Identify which groups are checked.\n var group_strings = [];\n query.grouping().forEach(function(group) {\n group_strings.push(group.string);\n });\n\n // Display the groups.\n if (group_strings.length > 0) {\n $(\".dq-grouping-text\").html(group_strings.join(\", \"));\n } else {\n $(\".dq-grouping-text\").html(\"No Grouping...\");\n }\n\n /************************************/\n /*** 4: Identify the Sort Columns ***/\n /************************************/\n var sort_strings = [];\n query.sorts().forEach(function(sort) {\n sort_strings.push(sort.string);\n });\n \n // Display the sorts.\n if (sort_strings.length > 0) {\n $(\".dq-sorting-text\").html(sort_strings.join(\", \"));\n } else {\n $(\".dq-sorting-text\").html(\"No Sorting...\");\n }\n\n }; // end display_query\n\n // Handle table additions.\n $(document).on(\"click\", \".dq-btn-add-table\", function() {\n DataQ.TableModal(query, null, display_query);\n });\n\n // Handle table deletes.\n $(document).on(\"click\", \".dq-btn-delete-table\", function() {\n var table_name = $(this).data(\"tablename\");\n if (query.operated_column() && query.operated_column().split(\".\")[0] === table_name) {\n query.operated_column(null);\n }\n query.selected_columns(table_name, null);\n query.update_grouping();\n display_query();\n });\n\n // Handle table edits.\n $(document).on(\"click\", \".dq-btn-edit-table\", function() {\n DataQ.TableModal(query, $(this).data(\"tablename\"), display_query);\n });\n\n // Handle filter additions.\n $(document).on(\"click\", \".dq-btn-add-filter\", function() {\n DataQ.FilterModal(query, display_query);\n });\n\n // Handle filter deletion.\n $(document).on(\"click\", \".dq-btn-delete-filter\", function() {\n var code = $(this).parent().data(\"code\");\n query.delete_filter(code);\n display_query();\n });\n\n // Handle grouping edit.\n $(document).on(\"click\", \".dq-btn-edit-grouping\", function() {\n DataQ.GroupingModal(query, display_query);\n });\n\n // Handle sorting edit.\n $(document).on(\"click\", \".dq-btn-edit-sorting\", function() {\n DataQ.SortModal(query, display_query);\n });\n\n // Handle DataQ cancel.\n $(document).on(\"click\", \".dq-btn-cancel-query\", function() {\n $(\".dq-black-background\").remove();\n $(\".dataq\").remove();\n callback(null);\n });\n\n // Handle DataQ run query.\n $(document).on(\"click\", \".dq-btn-run-query\", function() {\n $(\".dq-black-background\").remove();\n $(\".dataq\").remove();\n callback(DataQ.build_query(query));\n });\n})();\n","/**\n * Helper for accessing DataQ API.\n */\n(function() {\n // Create the global DataQ object if it doesn't exist.\n window.DataQ = window.DataQ || {};\n\n DataQ.API = {};\n\n // See dataq/views.py for description of result.\n DataQ.API.get_repos = function(callback) {\n $.get(\"/apps/dataq/api/\", callback);\n };\n\n // See dataq/views.py for description of result.\n DataQ.API.get_tables = function(repo, callback) {\n $.get(\"/apps/dataq/api/\" + repo + \"/\", callback);\n };\n\n // See dataq/views.py for description of result.\n DataQ.API.get_schema = function(repo, table, callback) {\n $.get(\"/apps/dataq/api/\" + repo + \"/\" + table + \"/\", callback);\n };\n})();\n","/**\n * The modal window that allows the user to create filters (of the form <val1> <operation> <val2>).\n *\n * The modal window is a bootstrap modal.\n */\n(function() {\n // If the global DataQ object does not exist, create it.\n window.DataQ = window.DataQ || {};\n\n // The callback to trigger when the modal is closed. This is executed as callback().\n var callback;\n\n // The DataQ.Query object being built.\n var query;\n\n /**\n * Launch the modal.\n *\n * @param q - The DataQ.Query object being built.\n * @param cb - The callback to execute when the filter has been added. It will be executed\n * as cb().\n */\n DataQ.FilterModal = function(q, cb) {\n // Set the instance variables.\n callback = cb;\n query = q;\n\n // If the modal does not exist, create it.\n var modal = $(\"#dq-filter-modal\");\n if (modal.length === 0) {\n var columns = [];\n\n // Iterate through each column of each selected table and add to the list of columns.\n query.get_selected_tables().forEach(function(selected_table) {\n query.schema(selected_table).forEach(function(column) {\n columns.push({\n \"column_name\": column[0],\n \"table_name\": selected_table,\n \"full_name\": selected_table + \".\" + column[0]\n });\n });\n });\n\n // Create the HTML for the filter modal.\n var html = DataQ.templates['dq-filter-modal']({\n \"columns\": columns,\n \"repo\": query.repo()\n });\n\n // Add the modal to the page.\n $('body').append(html);\n }\n\n // Display the modal (disable Esc)\n $('#dq-filter-modal').modal({\n keyboard: false\n });\n\n // Handle modal close when clicking backdrop.\n $(\".modal-backdrop\").click(function() {\n callback();\n $(\"#dq-filter-modal\").remove();\n })\n } // End FilterModal\n\n // Handle modal close.\n $(document).on(\"click\", \".dq-filter-quit\", function() {\n callback();\n $(\"#dq-filter-modal\").remove();\n $(\".modal-backdrop\").remove();\n });\n\n // Handle modal done.\n $(document).on(\"click\", \".dq-filter-done\", function() {\n var filter1 = $('.dq-filter-1-text').val();\n var filter2 = $('.dq-filter-2-text').val();\n var op = $('.dq-filter-op-text').val();\n if (filter1.length > 0 && filter2.length > 0 && op.length > 0) {\n query.add_filter(filter1, op, filter2);\n callback();\n $('#dq-filter-modal').modal('hide');\n $(\"#dq-filter-modal\").remove();\n $(\".modal-backdrop\").remove();\n } else {\n alert(\"You need to fill out the three text boxes\");\n }\n });\n\n // Handle filter1 dropdown link click (in <filter1> <operation> <filter2>)\n $(document).on(\"click\", \".dq-filter-1-link\", function() {\n $('.dq-filter-1-text').val($(this).html());\n });\n\n // Handle filter2 dropdown link click (in <filter1> <operation> <filter2>)\n $(document).on(\"click\", \".dq-filter-2-link\", function() {\n $('.dq-filter-2-text').val($(this).html());\n });\n\n // Handle operation dropdown link click (in <filter1> <operation> <filter2>)\n $(document).on(\"click\", \".dq-filter-op-link\", function() {\n // Unescape html for > and <\n var op = $(this).html().replace(\">\", \">\").replace(\"<\", \"<\");\n $('.dq-filter-op-text').val(op);\n });\n\n})();\n","/***\n * The modal window for editing the groupings.\n *\n * The modal window is a bootstrap modal.\n */\n(function() {\n // If the global DataQ object does not exist, create it.\n window.DataQ = window.DataQ || {};\n\n // The callback to trigger when the modal is closed. This is executed as callback().\n var callback;\n\n // The DataQ.Query object being built.\n var query;\n\n /**\n * Launch the modal.\n *\n * @param q - The DataQ.Query object being built.\n * @param cb - The callback to execute when the grouping has been modified. It will be executed\n * as cb().\n */\n DataQ.GroupingModal = function(q, cb) {\n // Set the instance variables.\n query = q;\n callback = cb;\n\n // Create the modal HTML if it doesn't exist.\n var modal = $(\"#dq-grouping-modal\");\n if (modal.length === 0) {\n var html = DataQ.templates[\"dq-grouping-modal\"]({\n columns: query.grouping()\n });\n $(\"body\").append(html);\n }\n\n // Display the modal. (disable Esc)\n $(\"#dq-grouping-modal\").modal({\n keyboard: false\n });\n\n // When the modal is displayed, enable HTML5Sortable.\n $(\"#dq-grouping-modal\").on(\"shown.bs.modal\", function() {\n $(\".dq-grouping-modal-list\").sortable({\n forcePlaceholderSize: true\n });\n });\n\n // Handle modal close when clicking backdrop.\n $(\".modal-backdrop\").click(function() {\n callback();\n $(\"#dq-grouping-modal\").remove();\n $(\".modal-backdrop\").remove();\n })\n }; // End GroupingModal\n\n // Handler for close modal.\n $(document).on(\"click\", \"#dq-grouping-modal-quit-btn\", function() {\n callback();\n $(\"#dq-grouping-modal\").remove();\n $(\".modal-backdrop\").remove();\n });\n\n // Handle for finishing edits.\n $(document).on(\"click\", \"#dq-grouping-modal-done-btn\", function() {\n var new_grouping = [];\n\n // Iterate through each list item (in order).\n $(\".dq-grouping-list-item\").each(function() {\n var li = $(this);\n var string = li.data(\"string\");\n var grouping = query.grouping();\n var current_group;\n\n // Find the group associated with this list item.\n query.grouping().forEach(function(group) {\n if (group.string === string) {\n current_group = group;\n }\n });\n\n new_grouping.push(current_group);\n });\n\n query.grouping(new_grouping);\n callback();\n $(\"#dq-grouping-modal\").remove();\n $(\".modal-backdrop\").remove();\n });\n\n})();\n","/**\n * Helper function to return the next aggregate for a given column type.\n * ex. max, min, avg, sum, count.\n *\n * This is useful to cycle through the possible aggregates as user clicks a column.\n * col1 --click--> max(col1) --click--> min(col1), etc.\n */\n(function() {\n window.DataQ = window.DataQ || {};\n\n // Numeric types supported in PostgreSQL.\n var number_types = [\"bigint\", \"int8\", \"bigserial\", \"serial8\", \"double precision\", \"float8\", \n \"integer\", \"int\", \"int4\", \"real\", \"float4\", \"smallint\", \"int2\", \"serial\", \"serial4\"];\n\n // Turn number_types into a Javascript object for more efficient lookup.\n // key = PostgreSQL type, val = true iff key is a numeric type.\n var is_number = {};\n for (var i = 0; i < number_types; i++) {\n is_number[number_types[i]] = true;\n }\n\n // Helper for cycling through numeric aggregates.\n // key = aggregate, value = next aggregate\n // For example, a cycle (beginning at none) would be:\n // none -> max -> min -> sum -> count -> avg -> none\n var next_numeric_aggregate = {\n \"none\": \"max\",\n \"max\": \"min\",\n \"min\": \"sum\",\n \"sum\": \"count\",\n \"count\": \"avg\",\n \"avg\": \"none\"\n };\n\n // Helper for cycling through non-numeric aggregates.\n var next_nonnumeric_aggregate = {\n \"none\": \"count\",\n \"count\": \"none\"\n };\n\n\n /**\n * Determine the next aggregate in the cycle for the given column type and the current aggregate.\n *\n * @param columntype - The PostgreSQL type of the column.\n * @param current_aggregate - The current aggregate operator applied to the column (ex. max, min)\n * If no aggregate has been applied, this value should be either null\n * or \"none\".\n * @return The next aggregate in the cycle.\n */\n DataQ.next_aggregate = function(columntype, current_aggregate) {\n if (current_aggregate === null) {\n current_aggregate = \"none\";\n }\n if (is_number[columntype]) {\n return next_numeric_aggregate[current_aggregate];\n } else {\n return next_nonnumeric_aggregate[current_aggregate];\n }\n };\n})();\n","/**\n * Logic for constructing a SQL query string from a DataQ.Query object.\n */\n(function() {\n // If the global DataQ object does not exist, create it.\n window.DataQ = window.DataQ || {};\n\n /**\n * Take a DataQ.Query object and generate a SQL query string from it.\n *\n * @param query - The DataQ.Query object.\n * @return A String representing the SQL query.\n */\n window.DataQ.build_query = function(query) {\n\n // The list of columns to select.\n var select_list = [];\n\n // The list of tables to select from.\n var from_list = [];\n\n // The filters to apply.\n var where_list = [];\n\n // The grouping to perform.\n var group_list = [];\n\n // The sorting to apply.\n var order_list = [];\n\n // Get the current repo name - we'll need to prepend this to some of the table/column names.\n var repo = query.repo();\n\n // Create the FROM clause. It is simply the list of tables that the user has selected.\n // Each item in the list is a String of the form: \"repo.table\".\n query.get_selected_tables().forEach(function(table) {\n from_list.push(repo + \".\" + table);\n });\n\n // Create the SELECT clause.\n // Iterate through every selected column of every selected table and add the column to the \n // select list (and write the aggregate if possible).\n query.get_selected_tables().forEach(function(table) {\n query.selected_columns(table).forEach(function(column) {\n if (column.agg === undefined || column.agg === null || column.agg === \"none\") {\n select_list.push(repo + \".\" + table + \".\" + column.name);\n } else {\n // When an aggregate \"agg\" on column \"col\" in table \"table\" and repo \"repo\" appears, mark\n // \"agg(repo.table.col) as agg_table_col\".\n select_list.push(column.agg + \"(\" + repo + \".\" + table + \".\" + column.name + \")\" + \n \" as \" + column.agg + \"_\" + table + \"_\"\n + column.name);\n }\n });\n });\n\n // Create the WHERE clause.\n // Simply iterate through each filter and add it to the list.\n query.get_filters().forEach(function(filter) {\n where_list.push(filter.filter1 + \" \" + filter.op + \" \" + filter.filter2);\n });\n\n // Create the GROUP BY clause.\n query.grouping().forEach(function(group) {\n var agg = group.column.agg;\n\n // We can only add a group by if it's not the aggregate column.\n if (agg === null || agg === undefined || agg === \"none\") {\n group_list.push(repo + \".\" + group.string);\n } \n });\n\n // Create the ORDER BY clause.\n query.sorts().forEach(function(sort) {\n var agg = sort.column.agg;\n if (agg === null || agg === undefined || agg === \"none\") {\n order_list.push(repo + \".\" + sort.string);\n } else {\n order_list.push(agg + \"_\" + sort.table + \"_\" + sort.column.name);\n }\n });\n\n // Set the query string.\n if (select_list.length === 0) {\n return \"\";\n }\n var query_string = \"SELECT \" + select_list.join(\", \")\n + \" FROM \" + from_list.join(\", \");\n\n // Set the where list.\n if (where_list.length > 0) {\n query_string += \" WHERE \" + where_list.join(\" AND \");\n }\n\n // Set the group list.\n if (group_list.length > 0) {\n query_string += \" GROUP BY \" + group_list.join(\", \")\n }\n\n // Set the order list.\n if (order_list.length > 0) {\n query_string += \" ORDER BY \" + order_list.join(\", \")\n }\n\n // Remove leading and trailing spaces and then append semicolon.\n query_string.trim();\n query_string += \";\";\n return query_string;\n };\n})();\n","/**\n * The object for building a query.\n */\n(function() {\n // If the DataQ object doesn't exist, create it.\n window.DataQ = window.DataQ || {};\n\n DataQ.Query = function() {\n \n // Create the object and initialize the objects.\n var that = {};\n that._schema_for_table_name = {};\n that._repo_name = null;\n that._operated_column = null;\n that._selected_columns_for_table = {};\n that._filter_for_code = {};\n that._grouping = [];\n that._sorts = {};\n\n /**\n * Get or set the schema for a given table.\n *\n * @param table_name - The name of the table.\n * @param schema - If this argument is omitted (or undefined), this function acts as a getter\n * returning the schema for the given table. Otherwise, the function acts as a \n * setter, setting the schema for table_name.\n *\n * @return The schema for the table name.\n */\n that.schema = function(table_name, schema) {\n if (schema !== undefined) {\n that._schema_for_table_name[table_name] = schema;\n } \n return that._schema_for_table_name[table_name];\n };\n\n /**\n * Get or set the repo name.\n *\n * @param repo_name - If this argument is omitted (or undefined), this function acts as a \n * getter. Otherwise, it acts as a setter, setting the repo name.\n *\n * @return The name of the repo.\n */\n that.repo = function(repo_name) {\n if (repo_name !== undefined) {\n that._repo_name = repo_name;\n }\n return that._repo_name;\n };\n\n /**\n * Get or set the operated column.\n *\n * @param operated_column - if this argument is omitted (or undefined), this function acts as\n * a getter. Otherwise, it acts a setter, setting the operated column.\n *\n * @return The name of the operated column (\"table.col\"). This may be null.\n */\n that.operated_column = function(operated_column) {\n if (operated_column !== undefined) {\n that._operated_column = operated_column;\n }\n return that._operated_column;\n };\n\n that.update_grouping = function() {\n that._grouping = [];\n var has_operated_column = false;\n for (var table in that._selected_columns_for_table) {\n if (!that._selected_columns_for_table[table]) {\n continue;\n }\n that._selected_columns_for_table[table].forEach(function(column) {\n if (column.agg === \"none\") {\n that._grouping.push({\n \"string\": table + \".\" + column.name,\n \"table\": table,\n \"column\": column\n });\n } else {\n has_operated_column = true;\n }\n });\n } // end for each table\n\n if (!has_operated_column) {\n that._grouping = [];\n }\n };\n\n that.selected_columns = function(table_name, selected_columns) {\n if (selected_columns !== undefined) {\n that._selected_columns_for_table[table_name] = selected_columns;\n }\n return that._selected_columns_for_table[table_name];\n };\n\n that.get_selected_tables = function() {\n var tbls = [];\n for (var k in that._selected_columns_for_table) {\n tbls.push(k);\n }\n return tbls;\n };\n\n that.add_filter = function(filter1, op, filter2) {\n var filter_string = filter1 + \" \" + op + \" \" + filter2;\n var code = md5((new Date()).getTime() + filter_string);\n that._filter_for_code[code] = {\n \"filter1\": filter1,\n \"op\": op,\n \"filter2\": filter2,\n \"filter_string\": filter_string\n };\n return that._filter_for_code[code];\n };\n\n that.delete_filter = function(code) {\n that._filter_for_code[code] = undefined;\n };\n\n that.get_filters = function() {\n var result = [];\n for (var k in that._filter_for_code) {\n var filter = that._filter_for_code[k];\n if (!filter) {\n continue;\n }\n result.push({\n \"code\": k,\n \"filter1\": filter.filter1,\n \"op\": filter.op,\n \"filter2\": filter.filter2,\n \"filter_string\": filter.filter_string\n });\n };\n return result;\n };\n\n that.grouping = function(grouping) {\n if (grouping !== undefined) {\n that._grouping = grouping;\n }\n return that._grouping;\n };\n\n that.add_sort = function(sort) {\n that._sorts[sort.string] = sort;\n };\n\n that.delete_sort = function(sort) {\n that._sorts[sort] = undefined;\n };\n\n that.sorts = function() {\n var result = [];\n for (var k in that._sorts) {\n if (that._sorts[k] !== undefined) {\n result.push(that._sorts[k]);\n }\n }\n return result;\n };\n\n return that;\n };\n})();\n","/**\n * The modal window that allows the user to specify the sorting of the columns.\n *\n * The modal window is a bootstrap modal.\n */\n(function() {\n // If the global DataQ object does not exist, create it.\n window.DataQ = window.DataQ || {};\n\n // The callback to trigger whent he modal is closed.\n var callback;\n\n // The DataQ.Query object being built.\n var query;\n\n /**\n * Launch the modal.\n *\n * @param q - The DataQ.Query object being built.\n * @param cb - The callback to trigger when the user finishes specifying sorts. It is executed as\n * cb().\n */\n DataQ.SortModal = function(q, cb) {\n // Set the instance variables.\n query = q;\n callback = cb;\n\n // If the modal HTML does not exist, add it to the page.\n var modal = $(\"#dq-sort-modal\");\n if (modal.length === 0) {\n var html = DataQ.templates['dq-sort-modal']();\n $('body').append(html);\n }\n\n // Display the modal (disable Esc)\n $('#dq-sort-modal').modal({\n keyboard: false\n });\n\n // When the modal is shown, populate the columns.\n $(\"#dq-sort-modal\").on(\"shown.bs.modal\", function() {\n update_list();\n });\n\n // Handle modal close when clicking backdrop.\n $(\".modal-backdrop\").click(function() {\n $(\"#dq-sort-modal\").remove();\n $(\".modal-backdrop\").remove();\n callback();\n })\n };\n\n // Handle dropdown item click.\n $(document).on(\"click\", \".dq-sort-modal-dropdown-btn\", function() {\n var list = $('.dq-sort-modal-dropdown');\n list.html(\"\");\n\n // When a dropdown link is clicked, add it to the list of selected columns.\n create_items_in_dropdown().forEach(function(item) {\n list.append(DataQ.templates[\"dq-sort-dropdown-li\"]({\n \"item\": item\n }));\n });\n\n });\n\n // Create the items in the sort dropdown menu.\n var create_items_in_dropdown = function() {\n // Identify the sorts that have already been used.\n var used_dict = {};\n query.sorts().forEach(function(sort) {\n used_dict[sort.string] = true;\n });\n\n // Iterate through every column of every selected table and, if it isn't used, add it to the\n // list of items.\n var items = [];\n query.get_selected_tables().forEach(function(selected_table) {\n query.selected_columns(selected_table).forEach(function(column) {\n var string = selected_table + \".\" + column.name;\n if (column.agg !== \"none\") {\n string = column.agg + \"(\" + selected_table + \".\" + column.name + \")\";\n }\n\n // Don't add any item to dropdown if it's already used.\n if (used_dict[string]) {\n return;\n }\n\n items.push({\n \"string\": string,\n \"table\": selected_table,\n \"column\": column\n });\n\n });\n });\n\n return items;\n };\n\n // Add the list items to list of used sorts.\n var update_list = function() {\n var list = $('.dq-sort-item-list');\n list.html(\"\");\n query.sorts().forEach(function(sort) {\n var html = DataQ.templates[\"dq-sort-list-item\"]({\n \"item\": sort\n });\n list.append(html);\n });\n };\n\n // Handle modal close.\n $(document).on(\"click\", \".dq-sort-modal-quit\", function() {\n $('#dq-sort-modal').remove();\n $(\".modal-backdrop\").remove();\n callback();\n });\n\n // Handle sort clicked.\n $(document).on(\"click\", \".dq-sort-link\", function() {\n var li = $(this);\n var name = li.data(\"columnname\");\n var type = li.data(\"columntype\");\n var agg = li.data(\"aggregate\");\n var table = li.data(\"table\");\n var string = li.data(\"string\");\n\n if (agg === undefined || agg === null) {\n agg = \"none\";\n }\n\n var item = {\n \"column\": {\n \"name\": name,\n \"type\": type,\n \"agg\": agg\n },\n \"string\": string,\n \"table\": table\n };\n\n query.add_sort(item);\n update_list();\n\n });\n\n // Handle modal close.\n $(document).on(\"click\", \".dq-sort-modal-done-btn\", function() {\n $('#dq-sort-modal').remove();\n $(\".modal-backdrop\").remove();\n callback();\n });\n\n // Handle delete sort.\n $(document).on(\"click\", \".dq-sort-delete-btn\", function() {\n var li = $(this).parent().parent();\n var string = li.data(\"string\");\n query.delete_sort(string);\n update_list();\n });\n\n})();\n","/**\n * The modal window that allows the user to select the tables (and columns from these tables) that\n * they want to use in their query.\n *\n * This modal can be used to either ADD a new table (and some of its columns) to the query or to\n * EDIT an existing table (and its columns) that is already in the query.\n *\n * The modal window is a bootstrap modal.\n */\n(function() {\n // If the global DataQ object does not exist, create it.\n window.DataQ = window.DataQ || {};\n\n // The callback to trigger when the modal is closed.\n var cb;\n\n // The table we have selected.\n var table;\n\n // The DataQ.Query object being built.\n var query;\n\n /**\n * Launch the modal.\n *\n * @param q - The DataQ.Query object being built.\n *\n * @param table_name - The name of the table to modify. This must be either null (in which case\n * the \"Add Table\" modal is displayed), or a table which the current user is associated with.\n *\n * @param callback - The callback that is executed after the user finishes updating selections.\n * It is executed as callback()\n */\n DataQ.TableModal = function(q, table_name, callback) {\n table = table_name;\n cb = callback;\n query = q;\n\n // If the modal HTML does not exist, add it to the page.\n var modal = $(\"#dq-table-modal\");\n if (modal.length === 0) {\n var html = DataQ.templates['dq-table-modal']({\n \"table_name\": table\n });\n $('body').append(html);\n }\n\n // Display the modal (disable Esc and clicking the backdrop to exit modal)\n $('#dq-table-modal').modal({\n keyboard: false\n });\n\n // Don't allow clicking Done until the user selects a table.\n $(\".dq-modal-done-btn\").hide();\n\n // When the modal is shown, populate the columns.\n $(\"#dq-table-modal\").on(\"shown.bs.modal\", function() {\n if (table) {\n populate_column_list(table);\n }\n });\n\n // Handle modal close when clicking backdrop.\n $(\".modal-backdrop\").click(function() {\n $(\"#dq-table-modal\").remove();\n $(\".modal-backdrop\").remove();\n cb();\n })\n };\n\n // If the user quits, trigger the callback.\n $(document).on('click', '.dq-modal-quit', function() {\n $(\"#dq-table-modal\").remove();\n $(\".modal-backdrop\").remove();\n cb();\n });\n\n\n // If the user clicks the table dropdown, populate the list with the list\n // of available tables.\n $(document).on(\"click\", \".dq-modal-dropdown-btn\", function() {\n var dropdown = $(\".dq-modal-dropdown\");\n dropdown.html(\"\");\n DataQ.API.get_tables(query.repo(), function(data) {\n data.tables.forEach(function(table) {\n var html = DataQ.templates[\"dq-modal-dropdown-item\"]({\n \"item_name\": table\n });\n dropdown.append(html);\n }); // end foreach\n }) // get_tables\n }); // document on click\n\n\n // When a table is selected from the dropdown, create the column list.\n $(document).on(\"click\", \".dq-modal-dropdown-link\", function() {\n // Set the content of the dropdown.\n var item_name = $(this).data(\"item_name\");\n table = item_name;\n $('.dq-modal-table-dropdown-text').text(table);\n populate_column_list();\n });\n\n // Populate the list of columns with the schema of the given table.\n var populate_column_list = function() {\n // Get the schema for the selected tables.\n DataQ.API.get_schema(query.repo(), table, function(data) {\n $(\".dq-modal-done-btn\").show();\n\n // Sort the columns by name.\n query.schema(table, data.schema).sort(function(a, b) {return a[0] > b[0]});\n\n // Create the HTML and add it to the UI.\n var html = DataQ.templates[\"dq-modal-columns\"]({\n \"columns\": query.schema(table)\n });\n $('.dq-column-list').html(html);\n\n if (query.selected_columns(table)) {\n // Iterate through the columns for the selected table.\n query.selected_columns(table).forEach(function(column) {\n // Extract the data entries from the element (we find the element by selecting\n // .dq-modal-column[data-columnname=\"colname\"]\n var element = $('.dq-modal-column[data-columnname=\"'+column.name+'\"]');\n element.data(\"columnname\", column.name);\n element.data(\"columntype\", column.type);\n element.data(\"currentaggregate\", column.agg || \"none\")\n element.find(\"input[type=checkbox]\").prop('checked', true);\n if (column.agg !== \"none\") {\n element.find(\"button\").text(column.agg + \"(\" + column.name + \")\");\n }\n }); // end forEach\n } // if selected columns\n });\n };\n\n // Handle column aggregate trigger.\n $(document).on(\"click\", \".dq-modal-column button\", function() {\n // Extract the data entries.\n var parent_li = $(this).parent();\n var columnname = parent_li.data(\"columnname\");\n var columntype = parent_li.data(\"columntype\");\n var currentaggregate = parent_li.data(\"currentaggregate\")\n\n // Compute the next aggregate operator to apply.\n var nextaggregate = DataQ.next_aggregate(columntype, currentaggregate);\n\n // If an aggregate has already been applied, don't apply another.\n if (query.operated_column() !== table + \".\" + columnname && query.operated_column() !== null) {\n nextaggregate = \"none\";\n }\n\n // If the aggregate has been turned off, turn off the operated column.\n // Else if this is the new operated column, indicate so.\n if (nextaggregate === \"none\") {\n if (query.operated_column() === table + \".\" + columnname) {\n query.operated_column(null);\n }\n $(this).text(columnname);\n } else {\n $(this).text(nextaggregate + \"(\" + columnname + \")\");\n query.operated_column(table + \".\" + columnname);\n }\n parent_li.data(\"currentaggregate\", nextaggregate);\n });\n\n // When this is clicked, return the selected columns.\n $(document).on(\"click\", \".dq-modal-done-btn\", function() {\n // Figure out the selected columns.\n var columns = [];\n var is_op_col_checked = false;\n\n // Iterate through each of the columns.\n $('.dq-modal-column').each(function() {\n var li = $(this);\n\n // If the column is checked.\n if (li.find(\"input\").is(\":checked\")) {\n\n // Extract the data entries.\n var agg = li.data(\"currentaggregate\");\n var type = li.data(\"columntype\");\n var name = li.data(\"columnname\");\n\n // If the operated column has been selected, then mark it so.\n if (table + \".\" + name === query.operated_column()) {\n is_op_col_checked = true;\n }\n\n if (agg === null || agg === undefined) {\n agg = \"none\";\n }\n\n columns.push({\n \"name\": name,\n \"type\": type,\n \"agg\": agg\n });\n\n }\n });\n\n // If the operated column should be in this table and has not been selected,\n // set the operated column as null.\n if (query.operated_column() &&\n query.operated_column().split(\".\")[0] === table &&\n !is_op_col_checked) {\n query.operated_column(null);\n }\n\n // Mark the selected columns for this table, and recompute the tables.\n query.selected_columns(table, columns);\n query.update_grouping();\n\n // Remove the modals from the page.\n $(\"#dq-table-modal\").remove();\n $(\".modal-backdrop\").remove();\n console.log('help me');\n console.log('now');\n\n cb();\n });\n\n})();\n"],"sourceRoot":"/source/"} \ No newline at end of file +{"version":3,"sources":["dataq.js","dq-api.js","dq-filter-modal.js","dq-grouping-modal.js","dq-next-aggregate.js","dq-query-builder.js","dq-query.js","dq-rls-policy-builder.js","dq-rls-policy.js","dq-sort-modal.js","dq-table-modal.js"],"names":["window","DataQ","query","policy","callback","DQ","repo_name","cb","container","$","templates","append","Query","repo","click","remove","DQ_rls_policy","Policy","display_query","html","get_selected_tables","forEach","selected_table","selected_columns","column_list","selected_column","agg","push","name","table_name","join","get_filters","filter","group_strings","grouping","group","string","length","sort_strings","sorts","sort","document","on","TableModal","this","data","operated_column","split","update_grouping","FilterModal","code","parent","delete_filter","GroupingModal","SortModal","build_query","build_policy","API","get_repos","get","get_tables","get_schema","table","q","modal","columns","schema","column","column_name","full_name","keyboard","filter1","val","filter2","op","add_filter","alert","replace","sortable","forcePlaceholderSize","new_grouping","each","current_group","li","number_types","is_number","i","next_numeric_aggregate","none","max","min","sum","count","avg","next_nonnumeric_aggregate","next_aggregate","columntype","current_aggregate","select_list","from_list","where_list","group_list","order_list","undefined","query_string","trim","that","_schema_for_table_name","_repo_name","_operated_column","_selected_columns_for_table","_filter_for_code","_grouping","_sorts","has_operated_column","tbls","k","filter_string","md5","Date","getTime","result","add_sort","delete_sort","policy_name","command","role_list","roles","using_expr","using_expression","check_expr","check_expression","policy_string","_name","_table","_command","_roles","_using_expr","_check_expr","cmd","Array","expr","update_list","list","create_items_in_dropdown","item","used_dict","items","type","hide","populate_column_list","dropdown","tables","item_name","text","show","a","b","element","find","prop","parent_li","columnname","currentaggregate","nextaggregate","is_op_col_checked","is"],"mappings":"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","file":"dataq.min.js","sourcesContent":["/**\n * Defines the DataQ.DQ object, which is what the user of the library will interact with.\n *\n * Simply call DataQ.DQ(repo_name, callback) and DataQ will launch. After the user builds a query,\n * the callback is executed as callback(query), where query is a String representing the SQL query\n * or null if the query was not built successfully.\n */\n(function() {\n // Create the global DataQ object if it doesn't exist.\n window.DataQ = window.DataQ || {};\n\n // The DataQ.Query that may be built.\n query = null;\n\n // The DataQ.Policy object that may be built.\n policy = null;\n\n // The callback to execute after the { query | policy } is built. It is executed as\n // { cb(query) | cb(policy) } where { query | policy } is a String representing the\n // SQL { query | policy } or null if the { query | policy } was not built.\n var callback;\n\n /**\n * @param repo_name - The name of the repo that DataQ should work on.\n * @param cb - The callback to trigger when the query is built.\n */\n DataQ.DQ = function(repo_name, cb) {\n // Set the callback.\n callback = cb;\n\n // Add the container to the page.\n var container = $(DataQ.templates[\"dataq-container\"]());\n $('body').append(container);\n\n // Create the query object and set the repo name.\n query = DataQ.Query();\n query.repo(repo_name);\n\n // Handle DataQ close when clicking backdrop.\n $(\".dq-black-background\").click(function() {\n $(\".dq-black-background\").remove();\n $(\".dataq\").remove();\n callback(null);\n })\n };\n\n /**\n * @param repo_name - The name of the repo that DataQ should work on.\n * @param cb - The callback to trigger when the query is built.\n */\n DataQ.DQ_rls_policy = function(repo_name, cb) {\n // Set the callback.\n callback = cb;\n\n // Add the container to the page.\n var container = $(DataQ.templates[\"dataq-container-rls-policy\"]());\n $('body').append(container);\n\n // Create the policy object and set the repo name.\n policy = DataQ.Policy();\n policy.repo(repo_name);\n\n // Handle DataQ close when clicking backdrop.\n $(\".dq-black-background\").click(function() {\n $(\".dq-black-background\").remove();\n $(\".dataq\").remove();\n callback(null);\n });\n };\n\n /**\n * Update the UI to reflect the latest query.\n */\n var display_query = function() {\n\n /**********************************/\n /*** 1: Selected Tables/Columns ***/\n /**********************************/\n $('.dq-selected-tables').html(\"\");\n query.get_selected_tables().forEach(function(selected_table) {\n var selected_columns = query.selected_columns(selected_table);\n if (selected_columns === null) {\n return;\n }\n\n // Go through each column for the table and add it to the column list.\n var column_list = [];\n selected_columns.forEach(function(selected_column) {\n if (selected_column.agg === \"none\") {\n column_list.push(selected_column.name);\n } else {\n // If col has an aggregate, write it as \"aggregate(col)\".\n column_list.push(selected_column.agg + \"(\" + selected_column.name + \")\");\n }\n });\n\n // Add the table and column list to the UI.\n var html = DataQ.templates[\"dq-selected-table\"]({\n \"table_name\": selected_table,\n \"column_list\": column_list.join(\", \")\n });\n $('.dq-selected-tables').append(html);\n });\n\n /***************************/\n /*** 2: Selected Filters ***/\n /***************************/\n $('.dq-filter-list').html(\"\");\n query.get_filters().forEach(function(filter) {\n $('.dq-filter-list').append(DataQ.templates['dq-filter-list-item']({\n \"filter\": filter\n }));\n });\n\n /**************************/\n /*** 3: Selected Groups ***/\n /**************************/\n // Identify which groups are checked.\n var group_strings = [];\n query.grouping().forEach(function(group) {\n group_strings.push(group.string);\n });\n\n // Display the groups.\n if (group_strings.length > 0) {\n $(\".dq-grouping-text\").html(group_strings.join(\", \"));\n } else {\n $(\".dq-grouping-text\").html(\"No Grouping...\");\n }\n\n /************************************/\n /*** 4: Identify the Sort Columns ***/\n /************************************/\n var sort_strings = [];\n query.sorts().forEach(function(sort) {\n sort_strings.push(sort.string);\n });\n\n // Display the sorts.\n if (sort_strings.length > 0) {\n $(\".dq-sorting-text\").html(sort_strings.join(\", \"));\n } else {\n $(\".dq-sorting-text\").html(\"No Sorting...\");\n }\n\n }; // end display_query\n\n // Handle table additions.\n $(document).on(\"click\", \".dq-btn-add-table\", function() {\n DataQ.TableModal(query, null, display_query);\n });\n\n // Handle table deletes.\n $(document).on(\"click\", \".dq-btn-delete-table\", function() {\n var table_name = $(this).data(\"tablename\");\n if (query.operated_column() && query.operated_column().split(\".\")[0] === table_name) {\n query.operated_column(null);\n }\n query.selected_columns(table_name, null);\n query.update_grouping();\n display_query();\n });\n\n // Handle table edits.\n $(document).on(\"click\", \".dq-btn-edit-table\", function() {\n DataQ.TableModal(query, $(this).data(\"tablename\"), display_query);\n });\n\n // Handle filter additions.\n $(document).on(\"click\", \".dq-btn-add-filter\", function() {\n DataQ.FilterModal(query, display_query);\n });\n\n // Handle filter deletion.\n $(document).on(\"click\", \".dq-btn-delete-filter\", function() {\n var code = $(this).parent().data(\"code\");\n query.delete_filter(code);\n display_query();\n });\n\n // Handle grouping edit.\n $(document).on(\"click\", \".dq-btn-edit-grouping\", function() {\n DataQ.GroupingModal(query, display_query);\n });\n\n // Handle sorting edit.\n $(document).on(\"click\", \".dq-btn-edit-sorting\", function() {\n DataQ.SortModal(query, display_query);\n });\n\n // Handle DataQ cancel.\n $(document).on(\"click\", \".dq-btn-cancel-query\", function() {\n $(\".dq-black-background\").remove();\n $(\".dataq\").remove();\n callback(null);\n });\n\n // Handle DataQ run query.\n $(document).on(\"click\", \".dq-btn-run-query\", function() {\n $(\".dq-black-background\").remove();\n $(\".dataq\").remove();\n callback(DataQ.build_query(query));\n });\n\n // Handle DataQ run policy.\n $(document).on(\"click\", \".dq-btn-run-policy\", function() {\n // Build policy object\n \n\n // Close DataQ\n $(\".dq-black-background\").remove();\n $(\".dataq\").remove();\n\n // Build policy string\n callback(DataQ.build_policy(policy));\n });\n})();\n","/**\n * Helper for accessing DataQ API.\n */\n(function() {\n // Create the global DataQ object if it doesn't exist.\n window.DataQ = window.DataQ || {};\n\n DataQ.API = {};\n\n // See dataq/views.py for description of result.\n DataQ.API.get_repos = function(callback) {\n $.get(\"/apps/dataq/api/\", callback);\n };\n\n // See dataq/views.py for description of result.\n DataQ.API.get_tables = function(repo, callback) {\n $.get(\"/apps/dataq/api/\" + repo + \"/\", callback);\n };\n\n // See dataq/views.py for description of result.\n DataQ.API.get_schema = function(repo, table, callback) {\n $.get(\"/apps/dataq/api/\" + repo + \"/\" + table + \"/\", callback);\n };\n})();\n","/**\n * The modal window that allows the user to create filters (of the form <val1> <operation> <val2>).\n *\n * The modal window is a bootstrap modal.\n */\n(function() {\n // If the global DataQ object does not exist, create it.\n window.DataQ = window.DataQ || {};\n\n // The callback to trigger when the modal is closed. This is executed as callback().\n var callback;\n\n // The DataQ.Query object being built.\n var query;\n\n /**\n * Launch the modal.\n *\n * @param q - The DataQ.Query object being built.\n * @param cb - The callback to execute when the filter has been added. It will be executed\n * as cb().\n */\n DataQ.FilterModal = function(q, cb) {\n // Set the instance variables.\n callback = cb;\n query = q;\n\n // If the modal does not exist, create it.\n var modal = $(\"#dq-filter-modal\");\n if (modal.length === 0) {\n var columns = [];\n\n // Iterate through each column of each selected table and add to the list of columns.\n query.get_selected_tables().forEach(function(selected_table) {\n query.schema(selected_table).forEach(function(column) {\n columns.push({\n \"column_name\": column[0],\n \"table_name\": selected_table,\n \"full_name\": selected_table + \".\" + column[0]\n });\n });\n });\n\n // Create the HTML for the filter modal.\n var html = DataQ.templates['dq-filter-modal']({\n \"columns\": columns,\n \"repo\": query.repo()\n });\n\n // Add the modal to the page.\n $('body').append(html);\n }\n\n // Display the modal (disable Esc)\n $('#dq-filter-modal').modal({\n keyboard: false\n });\n\n // Handle modal close when clicking backdrop.\n $(\".modal-backdrop\").click(function() {\n callback();\n $(\"#dq-filter-modal\").remove();\n })\n } // End FilterModal\n\n // Handle modal close.\n $(document).on(\"click\", \".dq-filter-quit\", function() {\n callback();\n $(\"#dq-filter-modal\").remove();\n $(\".modal-backdrop\").remove();\n });\n\n // Handle modal done.\n $(document).on(\"click\", \".dq-filter-done\", function() {\n var filter1 = $('.dq-filter-1-text').val();\n var filter2 = $('.dq-filter-2-text').val();\n var op = $('.dq-filter-op-text').val();\n if (filter1.length > 0 && filter2.length > 0 && op.length > 0) {\n query.add_filter(filter1, op, filter2);\n callback();\n $('#dq-filter-modal').modal('hide');\n $(\"#dq-filter-modal\").remove();\n $(\".modal-backdrop\").remove();\n } else {\n alert(\"You need to fill out the three text boxes\");\n }\n });\n\n // Handle filter1 dropdown link click (in <filter1> <operation> <filter2>)\n $(document).on(\"click\", \".dq-filter-1-link\", function() {\n $('.dq-filter-1-text').val($(this).html());\n });\n\n // Handle filter2 dropdown link click (in <filter1> <operation> <filter2>)\n $(document).on(\"click\", \".dq-filter-2-link\", function() {\n $('.dq-filter-2-text').val($(this).html());\n });\n\n // Handle operation dropdown link click (in <filter1> <operation> <filter2>)\n $(document).on(\"click\", \".dq-filter-op-link\", function() {\n // Unescape html for > and <\n var op = $(this).html().replace(\">\", \">\").replace(\"<\", \"<\");\n $('.dq-filter-op-text').val(op);\n });\n\n})();\n","/***\n * The modal window for editing the groupings.\n *\n * The modal window is a bootstrap modal.\n */\n(function() {\n // If the global DataQ object does not exist, create it.\n window.DataQ = window.DataQ || {};\n\n // The callback to trigger when the modal is closed. This is executed as callback().\n var callback;\n\n // The DataQ.Query object being built.\n var query;\n\n /**\n * Launch the modal.\n *\n * @param q - The DataQ.Query object being built.\n * @param cb - The callback to execute when the grouping has been modified. It will be executed\n * as cb().\n */\n DataQ.GroupingModal = function(q, cb) {\n // Set the instance variables.\n query = q;\n callback = cb;\n\n // Create the modal HTML if it doesn't exist.\n var modal = $(\"#dq-grouping-modal\");\n if (modal.length === 0) {\n var html = DataQ.templates[\"dq-grouping-modal\"]({\n columns: query.grouping()\n });\n $(\"body\").append(html);\n }\n\n // Display the modal. (disable Esc)\n $(\"#dq-grouping-modal\").modal({\n keyboard: false\n });\n\n // When the modal is displayed, enable HTML5Sortable.\n $(\"#dq-grouping-modal\").on(\"shown.bs.modal\", function() {\n $(\".dq-grouping-modal-list\").sortable({\n forcePlaceholderSize: true\n });\n });\n\n // Handle modal close when clicking backdrop.\n $(\".modal-backdrop\").click(function() {\n callback();\n $(\"#dq-grouping-modal\").remove();\n $(\".modal-backdrop\").remove();\n })\n }; // End GroupingModal\n\n // Handler for close modal.\n $(document).on(\"click\", \"#dq-grouping-modal-quit-btn\", function() {\n callback();\n $(\"#dq-grouping-modal\").remove();\n $(\".modal-backdrop\").remove();\n });\n\n // Handle for finishing edits.\n $(document).on(\"click\", \"#dq-grouping-modal-done-btn\", function() {\n var new_grouping = [];\n\n // Iterate through each list item (in order).\n $(\".dq-grouping-list-item\").each(function() {\n var li = $(this);\n var string = li.data(\"string\");\n var grouping = query.grouping();\n var current_group;\n\n // Find the group associated with this list item.\n query.grouping().forEach(function(group) {\n if (group.string === string) {\n current_group = group;\n }\n });\n\n new_grouping.push(current_group);\n });\n\n query.grouping(new_grouping);\n callback();\n $(\"#dq-grouping-modal\").remove();\n $(\".modal-backdrop\").remove();\n });\n\n})();\n","/**\n * Helper function to return the next aggregate for a given column type.\n * ex. max, min, avg, sum, count.\n *\n * This is useful to cycle through the possible aggregates as user clicks a column.\n * col1 --click--> max(col1) --click--> min(col1), etc.\n */\n(function() {\n window.DataQ = window.DataQ || {};\n\n // Numeric types supported in PostgreSQL.\n var number_types = [\"bigint\", \"int8\", \"bigserial\", \"serial8\", \"double precision\", \"float8\", \n \"integer\", \"int\", \"int4\", \"real\", \"float4\", \"smallint\", \"int2\", \"serial\", \"serial4\"];\n\n // Turn number_types into a Javascript object for more efficient lookup.\n // key = PostgreSQL type, val = true iff key is a numeric type.\n var is_number = {};\n for (var i = 0; i < number_types; i++) {\n is_number[number_types[i]] = true;\n }\n\n // Helper for cycling through numeric aggregates.\n // key = aggregate, value = next aggregate\n // For example, a cycle (beginning at none) would be:\n // none -> max -> min -> sum -> count -> avg -> none\n var next_numeric_aggregate = {\n \"none\": \"max\",\n \"max\": \"min\",\n \"min\": \"sum\",\n \"sum\": \"count\",\n \"count\": \"avg\",\n \"avg\": \"none\"\n };\n\n // Helper for cycling through non-numeric aggregates.\n var next_nonnumeric_aggregate = {\n \"none\": \"count\",\n \"count\": \"none\"\n };\n\n\n /**\n * Determine the next aggregate in the cycle for the given column type and the current aggregate.\n *\n * @param columntype - The PostgreSQL type of the column.\n * @param current_aggregate - The current aggregate operator applied to the column (ex. max, min)\n * If no aggregate has been applied, this value should be either null\n * or \"none\".\n * @return The next aggregate in the cycle.\n */\n DataQ.next_aggregate = function(columntype, current_aggregate) {\n if (current_aggregate === null) {\n current_aggregate = \"none\";\n }\n if (is_number[columntype]) {\n return next_numeric_aggregate[current_aggregate];\n } else {\n return next_nonnumeric_aggregate[current_aggregate];\n }\n };\n})();\n","/**\n * Logic for constructing a SQL query string from a DataQ.Query object.\n */\n(function() {\n // If the global DataQ object does not exist, create it.\n window.DataQ = window.DataQ || {};\n\n /**\n * Take a DataQ.Query object and generate a SQL query string from it.\n *\n * @param query - The DataQ.Query object.\n * @return A String representing the SQL query.\n */\n window.DataQ.build_query = function(query) {\n\n // The list of columns to select.\n var select_list = [];\n\n // The list of tables to select from.\n var from_list = [];\n\n // The filters to apply.\n var where_list = [];\n\n // The grouping to perform.\n var group_list = [];\n\n // The sorting to apply.\n var order_list = [];\n\n // Get the current repo name - we'll need to prepend this to some of the table/column names.\n var repo = query.repo();\n\n // Create the FROM clause. It is simply the list of tables that the user has selected.\n // Each item in the list is a String of the form: \"repo.table\".\n query.get_selected_tables().forEach(function(table) {\n from_list.push(repo + \".\" + table);\n });\n\n // Create the SELECT clause.\n // Iterate through every selected column of every selected table and add the column to the \n // select list (and write the aggregate if possible).\n query.get_selected_tables().forEach(function(table) {\n query.selected_columns(table).forEach(function(column) {\n if (column.agg === undefined || column.agg === null || column.agg === \"none\") {\n select_list.push(repo + \".\" + table + \".\" + column.name);\n } else {\n // When an aggregate \"agg\" on column \"col\" in table \"table\" and repo \"repo\" appears, mark\n // \"agg(repo.table.col) as agg_table_col\".\n select_list.push(column.agg + \"(\" + repo + \".\" + table + \".\" + column.name + \")\" + \n \" as \" + column.agg + \"_\" + table + \"_\"\n + column.name);\n }\n });\n });\n\n // Create the WHERE clause.\n // Simply iterate through each filter and add it to the list.\n query.get_filters().forEach(function(filter) {\n where_list.push(filter.filter1 + \" \" + filter.op + \" \" + filter.filter2);\n });\n\n // Create the GROUP BY clause.\n query.grouping().forEach(function(group) {\n var agg = group.column.agg;\n\n // We can only add a group by if it's not the aggregate column.\n if (agg === null || agg === undefined || agg === \"none\") {\n group_list.push(repo + \".\" + group.string);\n } \n });\n\n // Create the ORDER BY clause.\n query.sorts().forEach(function(sort) {\n var agg = sort.column.agg;\n if (agg === null || agg === undefined || agg === \"none\") {\n order_list.push(repo + \".\" + sort.string);\n } else {\n order_list.push(agg + \"_\" + sort.table + \"_\" + sort.column.name);\n }\n });\n\n // Set the query string.\n if (select_list.length === 0) {\n return \"\";\n }\n var query_string = \"SELECT \" + select_list.join(\", \")\n + \" FROM \" + from_list.join(\", \");\n\n // Set the where list.\n if (where_list.length > 0) {\n query_string += \" WHERE \" + where_list.join(\" AND \");\n }\n\n // Set the group list.\n if (group_list.length > 0) {\n query_string += \" GROUP BY \" + group_list.join(\", \")\n }\n\n // Set the order list.\n if (order_list.length > 0) {\n query_string += \" ORDER BY \" + order_list.join(\", \")\n }\n\n // Remove leading and trailing spaces and then append semicolon.\n query_string.trim();\n query_string += \";\";\n return query_string;\n };\n})();\n","/**\n * The object for building a query.\n */\n(function() {\n // If the DataQ object doesn't exist, create it.\n window.DataQ = window.DataQ || {};\n\n DataQ.Query = function() {\n \n // Create the object and initialize the objects.\n var that = {};\n that._schema_for_table_name = {};\n that._repo_name = null;\n that._operated_column = null;\n that._selected_columns_for_table = {};\n that._filter_for_code = {};\n that._grouping = [];\n that._sorts = {};\n\n /**\n * Get or set the schema for a given table.\n *\n * @param table_name - The name of the table.\n * @param schema - If this argument is omitted (or undefined), this function acts as a getter\n * returning the schema for the given table. Otherwise, the function acts as a \n * setter, setting the schema for table_name.\n *\n * @return The schema for the table name.\n */\n that.schema = function(table_name, schema) {\n if (schema !== undefined) {\n that._schema_for_table_name[table_name] = schema;\n } \n return that._schema_for_table_name[table_name];\n };\n\n /**\n * Get or set the repo name.\n *\n * @param repo_name - If this argument is omitted (or undefined), this function acts as a \n * getter. Otherwise, it acts as a setter, setting the repo name.\n *\n * @return The name of the repo.\n */\n that.repo = function(repo_name) {\n if (repo_name !== undefined) {\n that._repo_name = repo_name;\n }\n return that._repo_name;\n };\n\n /**\n * Get or set the operated column.\n *\n * @param operated_column - if this argument is omitted (or undefined), this function acts as\n * a getter. Otherwise, it acts a setter, setting the operated column.\n *\n * @return The name of the operated column (\"table.col\"). This may be null.\n */\n that.operated_column = function(operated_column) {\n if (operated_column !== undefined) {\n that._operated_column = operated_column;\n }\n return that._operated_column;\n };\n\n that.update_grouping = function() {\n that._grouping = [];\n var has_operated_column = false;\n for (var table in that._selected_columns_for_table) {\n if (!that._selected_columns_for_table[table]) {\n continue;\n }\n that._selected_columns_for_table[table].forEach(function(column) {\n if (column.agg === \"none\") {\n that._grouping.push({\n \"string\": table + \".\" + column.name,\n \"table\": table,\n \"column\": column\n });\n } else {\n has_operated_column = true;\n }\n });\n } // end for each table\n\n if (!has_operated_column) {\n that._grouping = [];\n }\n };\n\n that.selected_columns = function(table_name, selected_columns) {\n if (selected_columns !== undefined) {\n that._selected_columns_for_table[table_name] = selected_columns;\n }\n return that._selected_columns_for_table[table_name];\n };\n\n that.get_selected_tables = function() {\n var tbls = [];\n for (var k in that._selected_columns_for_table) {\n tbls.push(k);\n }\n return tbls;\n };\n\n that.add_filter = function(filter1, op, filter2) {\n var filter_string = filter1 + \" \" + op + \" \" + filter2;\n var code = md5((new Date()).getTime() + filter_string);\n that._filter_for_code[code] = {\n \"filter1\": filter1,\n \"op\": op,\n \"filter2\": filter2,\n \"filter_string\": filter_string\n };\n return that._filter_for_code[code];\n };\n\n that.delete_filter = function(code) {\n that._filter_for_code[code] = undefined;\n };\n\n that.get_filters = function() {\n var result = [];\n for (var k in that._filter_for_code) {\n var filter = that._filter_for_code[k];\n if (!filter) {\n continue;\n }\n result.push({\n \"code\": k,\n \"filter1\": filter.filter1,\n \"op\": filter.op,\n \"filter2\": filter.filter2,\n \"filter_string\": filter.filter_string\n });\n };\n return result;\n };\n\n that.grouping = function(grouping) {\n if (grouping !== undefined) {\n that._grouping = grouping;\n }\n return that._grouping;\n };\n\n that.add_sort = function(sort) {\n that._sorts[sort.string] = sort;\n };\n\n that.delete_sort = function(sort) {\n that._sorts[sort] = undefined;\n };\n\n that.sorts = function() {\n var result = [];\n for (var k in that._sorts) {\n if (that._sorts[k] !== undefined) {\n result.push(that._sorts[k]);\n }\n }\n return result;\n };\n\n return that;\n };\n})();\n","/**\n* Logic for constructing a PostgreSQL CREATE POLICY command from a\n* DataQ.policy object.\n*/\n(function() {\n // If the global DataQ object does not exist, create it.\n window.DataQ = window.DataQ || {};\n\n /**\n * Take a DataQ.Policy object and generate a CREATE POLICY string from it.\n * A CREATE POLICY command looks like:\n *\n * CREATE POLICY name ON table_name\n * [ FOR { ALL | SELECT | INSERT | UPDATE | DELETE } ]\n * [ TO { role_name | PUBLIC | CURRENT_USER | SESSION_USER } [, ...] ]\n * [ USING ( using_expression ) ]\n * [ WITH CHECK ( check_expression ) ]\n *\n * see https://www.postgresql.org/docs/9.5/static/sql-createpolicy.html\n *\n * @param policy - The DataQ.policy object.\n * @return A String representing the CREATE POLICY command.\n */\n window.DataQ.build_policy = function(policy) {\n\n // Name of policy to be created.\n var policy_name = policy.name();\n\n // Name of table to which the policy applies.\n var table_name = policy.repo() + \".\" + policy.name();\n\n // Command to which the policy applies.\n var command = policy.command();\n\n // List of roles to which the policy applies.\n var role_list = policy.roles();\n\n // SQL conditional expression to control row visibility.\n // Rows for which the expression returns true will be visible.\n var using_expr = policy.using_expression();\n\n // SQL conditional expression to control INSERT and UPDATE privileges.\n // Only rows for which the expression evaluates to true will be allowed.\n var check_expr = policy.check_expression();\n\n /* Build policy string */\n var policy_string = \"CREATE POLICY \" + policy_name;\n\n // ON clause\n policy_string += \" ON \" + table_name;\n\n // FOR clause\n policy_string += \" FOR \" + command;\n\n // TO clause\n policy_string += \" TO \" + role_list.join(\", \");\n\n // USING clause\n policy_string += \" USING \" + using_expr;\n\n // WITH CHECK clause\n if (command !== \"SELECT\") {\n // A SELECT policy cannot have a WITH CHECK expression, as it only applies\n // in cases where records are being retrieved from the relation.\n policy_string += \" WITH CHECK \" + check_expr;\n }\n\n // Remove leading and trailing spaces and then append semicolon.\n policy_string.trim();\n policy_string += \";\";\n\n return policy_string;\n\n };\n})();\n","/**\n* The object for building a row-level security policy.\n*/\n(function() {\n // If the DataQ object doesn't exist, create it.\n window.DataQ = window.DataQ || {};\n\n DataQ.Policy = function() {\n\n // Create the object and initialize its contents.\n var that = {};\n that._repo_name = null;\n that._name = null;\n that._table = null;\n that._command = null;\n that._roles = [];\n that._using_expr = null;\n that._check_expr = null;\n\n /**\n * Get or set the repo name.\n *\n * @param repo_name - If this argument is omitted (or undefined), this function acts as a\n * getter. Otherwise, it acts as a setter, setting the repo name.\n *\n * @return The name of the repo.\n */\n that.repo = function(repo_name) {\n if (repo_name !== undefined) {\n that._repo_name = repo_name;\n }\n return that._repo_name;\n };\n\n /**\n * Get or set the name of the policy.\n *\n * @param policy_name - If this argument is omitted (or undefined), this function acts as a\n * getter. Otherwise, it acts as a setter, setting the repo name.\n *\n * @return The name of the policy.\n */\n that.name = function(policy_name) {\n if (policy_name !== undefined) {\n that._name = policy_name;\n }\n return that._name;\n };\n\n /**\n * Get or set the name of the table to which this policy will apply.\n *\n * @param table_name - If this argument is omitted (or undefined), this function acts as a\n * getter. Otherwise, it acts as a setter, setting the repo name.\n *\n * @return The name of the table.\n */\n that.table = function(table_name) {\n if (table_name !== undefined) {\n that._table = table_name;\n }\n return that._table;\n };\n\n /**\n * Get or set the command { ALL | SELECT | INSERT| UPDATE | DELETE } to which\n * this policy will apply.\n *\n * @param cmd - If this argument is omitted (or undefined), this function acts as a\n * getter. Otherwise, it acts as a setter, setting the repo name.\n *\n * @return The name of the command.\n */\n that.command = function(cmd) {\n if (cmd !== undefined) {\n that._command = cmd;\n }\n return that._command;\n };\n\n /**\n * Get or set the Roles to which this policy will apply.\n *\n * @param role_list - If this argument is omitted (or undefined), this function acts as a\n * getter. Otherwise, it acts as a setter, setting the repo name.\n *\n * @return The list of Roles.\n */\n that.roles = function(role_list) {\n if (role_list !== undefined) {\n if (!(role_list instanceof Array)) {\n role_list = [role_list]\n }\n that._roles = role_list;\n }\n return that._roles;\n };\n\n /**\n * Get or set the policy's using_expression.\n *\n * @param expr - If this argument is omitted (or undefined), this function acts as a\n * getter. Otherwise, it acts as a setter, setting the repo name.\n *\n * @return The full using_expression.\n */\n that.using_expression = function(expr) {\n if (expr !== undefined) {\n that._using_expr = expr;\n }\n return that._using_expr;\n };\n\n /**\n * Get or set the policy's check_expression.\n *\n * @param expr - If this argument is omitted (or undefined), this function acts as a\n * getter. Otherwise, it acts as a setter, setting the repo name.\n *\n * @return The full check_expression.\n */\n that.check_expression = function(expr) {\n if (expr !== undefined) {\n that._check_expr = expr;\n }\n return that._check_expr;\n };\n\n return that;\n\n };\n})();\n","/**\n * The modal window that allows the user to specify the sorting of the columns.\n *\n * The modal window is a bootstrap modal.\n */\n(function() {\n // If the global DataQ object does not exist, create it.\n window.DataQ = window.DataQ || {};\n\n // The callback to trigger whent he modal is closed.\n var callback;\n\n // The DataQ.Query object being built.\n var query;\n\n /**\n * Launch the modal.\n *\n * @param q - The DataQ.Query object being built.\n * @param cb - The callback to trigger when the user finishes specifying sorts. It is executed as\n * cb().\n */\n DataQ.SortModal = function(q, cb) {\n // Set the instance variables.\n query = q;\n callback = cb;\n\n // If the modal HTML does not exist, add it to the page.\n var modal = $(\"#dq-sort-modal\");\n if (modal.length === 0) {\n var html = DataQ.templates['dq-sort-modal']();\n $('body').append(html);\n }\n\n // Display the modal (disable Esc)\n $('#dq-sort-modal').modal({\n keyboard: false\n });\n\n // When the modal is shown, populate the columns.\n $(\"#dq-sort-modal\").on(\"shown.bs.modal\", function() {\n update_list();\n });\n\n // Handle modal close when clicking backdrop.\n $(\".modal-backdrop\").click(function() {\n $(\"#dq-sort-modal\").remove();\n $(\".modal-backdrop\").remove();\n callback();\n })\n };\n\n // Handle dropdown item click.\n $(document).on(\"click\", \".dq-sort-modal-dropdown-btn\", function() {\n var list = $('.dq-sort-modal-dropdown');\n list.html(\"\");\n\n // When a dropdown link is clicked, add it to the list of selected columns.\n create_items_in_dropdown().forEach(function(item) {\n list.append(DataQ.templates[\"dq-sort-dropdown-li\"]({\n \"item\": item\n }));\n });\n\n });\n\n // Create the items in the sort dropdown menu.\n var create_items_in_dropdown = function() {\n // Identify the sorts that have already been used.\n var used_dict = {};\n query.sorts().forEach(function(sort) {\n used_dict[sort.string] = true;\n });\n\n // Iterate through every column of every selected table and, if it isn't used, add it to the\n // list of items.\n var items = [];\n query.get_selected_tables().forEach(function(selected_table) {\n query.selected_columns(selected_table).forEach(function(column) {\n var string = selected_table + \".\" + column.name;\n if (column.agg !== \"none\") {\n string = column.agg + \"(\" + selected_table + \".\" + column.name + \")\";\n }\n\n // Don't add any item to dropdown if it's already used.\n if (used_dict[string]) {\n return;\n }\n\n items.push({\n \"string\": string,\n \"table\": selected_table,\n \"column\": column\n });\n\n });\n });\n\n return items;\n };\n\n // Add the list items to list of used sorts.\n var update_list = function() {\n var list = $('.dq-sort-item-list');\n list.html(\"\");\n query.sorts().forEach(function(sort) {\n var html = DataQ.templates[\"dq-sort-list-item\"]({\n \"item\": sort\n });\n list.append(html);\n });\n };\n\n // Handle modal close.\n $(document).on(\"click\", \".dq-sort-modal-quit\", function() {\n $('#dq-sort-modal').remove();\n $(\".modal-backdrop\").remove();\n callback();\n });\n\n // Handle sort clicked.\n $(document).on(\"click\", \".dq-sort-link\", function() {\n var li = $(this);\n var name = li.data(\"columnname\");\n var type = li.data(\"columntype\");\n var agg = li.data(\"aggregate\");\n var table = li.data(\"table\");\n var string = li.data(\"string\");\n\n if (agg === undefined || agg === null) {\n agg = \"none\";\n }\n\n var item = {\n \"column\": {\n \"name\": name,\n \"type\": type,\n \"agg\": agg\n },\n \"string\": string,\n \"table\": table\n };\n\n query.add_sort(item);\n update_list();\n\n });\n\n // Handle modal close.\n $(document).on(\"click\", \".dq-sort-modal-done-btn\", function() {\n $('#dq-sort-modal').remove();\n $(\".modal-backdrop\").remove();\n callback();\n });\n\n // Handle delete sort.\n $(document).on(\"click\", \".dq-sort-delete-btn\", function() {\n var li = $(this).parent().parent();\n var string = li.data(\"string\");\n query.delete_sort(string);\n update_list();\n });\n\n})();\n","/**\n * The modal window that allows the user to select the tables (and columns from these tables) that\n * they want to use in their query.\n *\n * This modal can be used to either ADD a new table (and some of its columns) to the query or to\n * EDIT an existing table (and its columns) that is already in the query.\n *\n * The modal window is a bootstrap modal.\n */\n(function() {\n // If the global DataQ object does not exist, create it.\n window.DataQ = window.DataQ || {};\n\n // The callback to trigger when the modal is closed.\n var cb;\n\n // The table we have selected.\n var table;\n\n // The DataQ.Query object being built.\n var query;\n\n /**\n * Launch the modal.\n *\n * @param q - The DataQ.Query object being built.\n *\n * @param table_name - The name of the table to modify. This must be either null (in which case\n * the \"Add Table\" modal is displayed), or a table which the current user is associated with.\n *\n * @param callback - The callback that is executed after the user finishes updating selections.\n * It is executed as callback()\n */\n DataQ.TableModal = function(q, table_name, callback) {\n table = table_name;\n cb = callback;\n query = q;\n\n // If the modal HTML does not exist, add it to the page.\n var modal = $(\"#dq-table-modal\");\n if (modal.length === 0) {\n var html = DataQ.templates['dq-table-modal']({\n \"table_name\": table\n });\n $('body').append(html);\n }\n\n // Display the modal (disable Esc and clicking the backdrop to exit modal)\n $('#dq-table-modal').modal({\n keyboard: false\n });\n\n // Don't allow clicking Done until the user selects a table.\n $(\".dq-modal-done-btn\").hide();\n\n // When the modal is shown, populate the columns.\n $(\"#dq-table-modal\").on(\"shown.bs.modal\", function() {\n if (table) {\n populate_column_list(table);\n }\n });\n\n // Handle modal close when clicking backdrop.\n $(\".modal-backdrop\").click(function() {\n $(\"#dq-table-modal\").remove();\n $(\".modal-backdrop\").remove();\n cb();\n })\n };\n\n // If the user quits, trigger the callback.\n $(document).on('click', '.dq-modal-quit', function() {\n $(\"#dq-table-modal\").remove();\n $(\".modal-backdrop\").remove();\n cb();\n });\n\n\n // If the user clicks the table dropdown, populate the list with the list\n // of available tables.\n $(document).on(\"click\", \".dq-modal-dropdown-btn\", function() {\n var dropdown = $(\".dq-modal-dropdown\");\n dropdown.html(\"\");\n DataQ.API.get_tables(query.repo(), function(data) {\n data.tables.forEach(function(table) {\n var html = DataQ.templates[\"dq-modal-dropdown-item\"]({\n \"item_name\": table\n });\n dropdown.append(html);\n }); // end foreach\n }) // get_tables\n }); // document on click\n\n\n // When a table is selected from the dropdown, create the column list.\n $(document).on(\"click\", \".dq-modal-dropdown-link\", function() {\n // Set the content of the dropdown.\n var item_name = $(this).data(\"item_name\");\n table = item_name;\n $('.dq-modal-table-dropdown-text').text(table);\n populate_column_list();\n });\n\n // Populate the list of columns with the schema of the given table.\n var populate_column_list = function() {\n // Get the schema for the selected tables.\n DataQ.API.get_schema(query.repo(), table, function(data) {\n $(\".dq-modal-done-btn\").show();\n\n // Sort the columns by name.\n query.schema(table, data.schema).sort(function(a, b) {return a[0] > b[0]});\n\n // Create the HTML and add it to the UI.\n var html = DataQ.templates[\"dq-modal-columns\"]({\n \"columns\": query.schema(table)\n });\n $('.dq-column-list').html(html);\n\n if (query.selected_columns(table)) {\n // Iterate through the columns for the selected table.\n query.selected_columns(table).forEach(function(column) {\n // Extract the data entries from the element (we find the element by selecting\n // .dq-modal-column[data-columnname=\"colname\"]\n var element = $('.dq-modal-column[data-columnname=\"'+column.name+'\"]');\n element.data(\"columnname\", column.name);\n element.data(\"columntype\", column.type);\n element.data(\"currentaggregate\", column.agg || \"none\")\n element.find(\"input[type=checkbox]\").prop('checked', true);\n if (column.agg !== \"none\") {\n element.find(\"button\").text(column.agg + \"(\" + column.name + \")\");\n }\n }); // end forEach\n } // if selected columns\n });\n };\n\n // Handle column aggregate trigger.\n $(document).on(\"click\", \".dq-modal-column button\", function() {\n // Extract the data entries.\n var parent_li = $(this).parent();\n var columnname = parent_li.data(\"columnname\");\n var columntype = parent_li.data(\"columntype\");\n var currentaggregate = parent_li.data(\"currentaggregate\")\n\n // Compute the next aggregate operator to apply.\n var nextaggregate = DataQ.next_aggregate(columntype, currentaggregate);\n\n // If an aggregate has already been applied, don't apply another.\n if (query.operated_column() !== table + \".\" + columnname && query.operated_column() !== null) {\n nextaggregate = \"none\";\n }\n\n // If the aggregate has been turned off, turn off the operated column.\n // Else if this is the new operated column, indicate so.\n if (nextaggregate === \"none\") {\n if (query.operated_column() === table + \".\" + columnname) {\n query.operated_column(null);\n }\n $(this).text(columnname);\n } else {\n $(this).text(nextaggregate + \"(\" + columnname + \")\");\n query.operated_column(table + \".\" + columnname);\n }\n parent_li.data(\"currentaggregate\", nextaggregate);\n });\n\n // When this is clicked, return the selected columns.\n $(document).on(\"click\", \".dq-modal-done-btn\", function() {\n // Figure out the selected columns.\n var columns = [];\n var is_op_col_checked = false;\n\n // Iterate through each of the columns.\n $('.dq-modal-column').each(function() {\n var li = $(this);\n\n // If the column is checked.\n if (li.find(\"input\").is(\":checked\")) {\n\n // Extract the data entries.\n var agg = li.data(\"currentaggregate\");\n var type = li.data(\"columntype\");\n var name = li.data(\"columnname\");\n\n // If the operated column has been selected, then mark it so.\n if (table + \".\" + name === query.operated_column()) {\n is_op_col_checked = true;\n }\n\n if (agg === null || agg === undefined) {\n agg = \"none\";\n }\n\n columns.push({\n \"name\": name,\n \"type\": type,\n \"agg\": agg\n });\n\n }\n });\n\n // If the operated column should be in this table and has not been selected,\n // set the operated column as null.\n if (query.operated_column() &&\n query.operated_column().split(\".\")[0] === table &&\n !is_op_col_checked) {\n query.operated_column(null);\n }\n\n // Mark the selected columns for this table, and recompute the tables.\n query.selected_columns(table, columns);\n query.update_grouping();\n\n // Remove the modals from the page.\n $(\"#dq-table-modal\").remove();\n $(\".modal-backdrop\").remove();\n cb();\n });\n\n})();\n"],"sourceRoot":"/source/"} \ No newline at end of file diff --git a/src/browser/static/dataq/templates.js b/src/browser/static/dataq/templates.js index 2fb62941..23b40789 100644 --- a/src/browser/static/dataq/templates.js +++ b/src/browser/static/dataq/templates.js @@ -1,5 +1,64 @@ this["DataQ"] = this["DataQ"] || {}; this["DataQ"]["templates"] = this["DataQ"]["templates"] || {}; +this["DataQ"]["templates"]["dataq-container-rls-policy"] = Handlebars.template({"1":function(depth0,helpers,partials,data,depths) { + var lambda=this.lambda, escapeExpression=this.escapeExpression; + return " <li><a href=\"#\" class=\"dq-filter-1-link\" data-columnname=\"" + + escapeExpression(lambda((depth0 != null ? depth0.column_name : depth0), depth0)) + + "\" data-reponame=\"" + + escapeExpression(lambda((depths[1] != null ? depths[1].repo : depths[1]), depth0)) + + "\" data-tablename=\"" + + escapeExpression(lambda((depth0 != null ? depth0.table_name : depth0), depth0)) + + "\">" + + escapeExpression(lambda((depth0 != null ? depth0.full_name : depth0), depth0)) + + "</a></li>\n"; +},"3":function(depth0,helpers,partials,data,depths) { + var lambda=this.lambda, escapeExpression=this.escapeExpression; + return " <li><a href=\"#\" class=\"dq-filter-2-link\" data-columnname=\"" + + escapeExpression(lambda((depth0 != null ? depth0.column_name : depth0), depth0)) + + "\" data-reponame=\"" + + escapeExpression(lambda((depths[1] != null ? depths[1].repo : depths[1]), depth0)) + + "\" data-tablename=\"" + + escapeExpression(lambda((depth0 != null ? depth0.table_name : depth0), depth0)) + + "\">" + + escapeExpression(lambda((depth0 != null ? depth0.full_name : depth0), depth0)) + + "</a></li>\n"; +},"5":function(depth0,helpers,partials,data,depths) { + var lambda=this.lambda, escapeExpression=this.escapeExpression; + return " <li><a href=\"#\" class=\"dq-filter-1-link\" data-columnname=\"" + + escapeExpression(lambda((depth0 != null ? depth0.column_name : depth0), depth0)) + + "\" data-reponame=\"" + + escapeExpression(lambda((depths[1] != null ? depths[1].repo : depths[1]), depth0)) + + "\" data-tablename=\"" + + escapeExpression(lambda((depth0 != null ? depth0.table_name : depth0), depth0)) + + "\">" + + escapeExpression(lambda((depth0 != null ? depth0.full_name : depth0), depth0)) + + "</a></li>\n"; +},"7":function(depth0,helpers,partials,data,depths) { + var lambda=this.lambda, escapeExpression=this.escapeExpression; + return " <li><a href=\"#\" class=\"dq-filter-2-link\" data-columnname=\"" + + escapeExpression(lambda((depth0 != null ? depth0.column_name : depth0), depth0)) + + "\" data-reponame=\"" + + escapeExpression(lambda((depths[1] != null ? depths[1].repo : depths[1]), depth0)) + + "\" data-tablename=\"" + + escapeExpression(lambda((depth0 != null ? depth0.table_name : depth0), depth0)) + + "\">" + + escapeExpression(lambda((depth0 != null ? depth0.full_name : depth0), depth0)) + + "</a></li>\n"; +},"compiler":[6,">= 2.0.0-beta.1"],"main":function(depth0,helpers,partials,data,depths) { + var stack1, buffer = "<!-- HTML file containing an extension of the overall DataQ interface.\n This interface is for building DataQ.Policy objects. -->\n\n<!-- Create a translucent black background -->\n<div class=\"dq-black-background\"></div>\n\n<!-- The DataQ container -->\n<div class=\"dataq container-fluid\">\n\n <h4>\n DataQ Policy Builder allows you to write PostgreSQL CREATE POLICY commands using a simple checklist-like user interface.\n </h4>\n\n<div class=\"panel-group\">\n <div class=\"panel panel-default\">\n <div class=\"panel-heading dq-panel\" data-toggle=\"collapse\" href=\"#collapseOne\">\n <h4 class=\"panel-title\">\n Policy Name\n </h4>\n </div>\n </div>\n <div id=\"collapseOne\" class=\"panel-collapse collapse in\">\n <div class=\"panel-body\">\n <input id=\"dq-policy-name\" type=\"text\" class=\"form-control\" placeholder=\"Policy Name\" />\n </div>\n </div>\n\n <div class=\"panel panel-default\">\n <div class=\"panel-heading dq-panel\" data-toggle=\"collapse\" href=\"#collapseTwo\">\n <h4 class=\"panel-title\">\n Allowed Commands\n </h4>\n </div>\n </div>\n <div id=\"collapseTwo\" class=\"panel-collapse collapse in\">\n <div class=\"panel-body\">\n <div class=\"dropdown\" id=\"dq-policy-commands-dropdown\">\n <button class=\"btn btn-primary dropdown-toggle\" type=\"button\" data-toggle=\"dropdown\">Choose command\n <span class=\"caret\"></span></button>\n <ul class=\"dropdown-menu\">\n <li><a href=\"#\">SELECT</a></li>\n <li><a href=\"#\">INSERT</a></li>\n <li><a href=\"#\">UPDATE</a></li>\n <li><a href=\"#\">DELETE</a></li>\n <li><a href=\"#\">ALL</a></li>\n </ul>\n </div>\n </div>\n </div>\n\n <div class=\"panel panel-default\">\n <div class=\"panel-heading dq-panel\" data-toggle=\"collapse\" href=\"#collapseThree\">\n <h4 class=\"panel-title\">\n Select role(s) this policy will apply to:\n </h4>\n </div>\n </div>\n <div id=\"collapseThree\" class=\"panel-collapse collapse in\">\n <div class=\"panel-body\">\n <input id=\"dq-policy-role-list\" type=\"text\" class=\"form-control\" placeholder=\"Comma separated role names. e.g. CURRENT_USER, admin, myGroupRole\" />\n </div>\n </div>\n\n <div class=\"panel panel-default\">\n <div class=\"panel-heading dq-panel\" data-toggle=\"collapse\" href=\"#collapseFour\">\n <h4 class=\"panel-title\">\n USING expression: Rows for which this expression returns true will be visible.\n </h4>\n </div>\n </div>\n <div id=\"collapseFour\" class=\"panel-collapse collapse in\">\n <div class=\"panel-body\">\n <!-- The first dropdown where the user can specify <val1> in the filter <val1> <operation> <val2> -->\n <div class=\"input-group col-xs-4\">\n <!-- Text box for entering the filter text -->\n <input type=\"text\" class=\"form-control dq-filter-1-text\" placeholder=\"table1.col1 * 3\">\n <!-- Dropdown for selecting a column to add to filter -->\n <div class=\"input-group-btn\">\n <button type=\"button\" class=\"btn btn-default dropdown-toggle\" data-toggle=\"dropdown\" aria-expanded=\"false\"><span class=\"caret\"></span></button>\n <ul class=\"dropdown-menu dropdown-menu-right\" role=\"menu\">\n"; + stack1 = helpers.each.call(depth0, (depth0 != null ? depth0.columns : depth0), {"name":"each","hash":{},"fn":this.program(1, data, depths),"inverse":this.noop,"data":data}); + if (stack1 != null) { buffer += stack1; } + buffer += " </ul>\n </div>\n </div> <!-- end filter1 -->\n\n <!-- The first dropdown where the user can specify <operation> in the filter <val1> <operation> <val2> -->\n <div class=\"input-group col-xs-3\">\n <!-- Text box for entering the operation text -->\n <input type=\"text\" class=\"form-control dq-filter-op-text\" value=\"=\">\n <!-- The possible operations are <, <=, >, >=, =, LIKE, IN, and NOT IN -->\n <div class=\"input-group-btn\">\n <button type=\"button\" class=\"btn btn-default dropdown-toggle\" data-toggle=\"dropdown\" aria-expanded=\"false\"><span class=\"caret\"></span></button>\n <ul class=\"dropdown-menu dropdown-menu-right\" role=\"menu\">\n <li><a href=\"#\" class=\"dq-filter-op-link\"><</a></li>\n <li><a href=\"#\" class=\"dq-filter-op-link\"><=</a></li>\n <li><a href=\"#\" class=\"dq-filter-op-link\">=</a></li>\n <li><a href=\"#\" class=\"dq-filter-op-link\">></a></li>\n <li><a href=\"#\" class=\"dq-filter-op-link\">>=</a></li>\n <li><a href=\"#\" class=\"dq-filter-op-link\">LIKE</a></li>\n <li><a href=\"#\" class=\"dq-filter-op-link\">IN</a></li>\n <li><a href=\"#\" class=\"dq-filter-op-link\">NOT IN</a></li>\n </ul>\n </div>\n </div> <!-- end filterop -->\n\n <!-- The second dropdown where the user can specify <val2> in the filter <val1> <operation> <val2> -->\n <div class=\"input-group col-xs-4\">\n <!-- Text box for entering the filter text -->\n <input type=\"text\" class=\"form-control dq-filter-2-text\" placeholder=\"table2.col2+3\">\n <!-- Dropdown for selecting a column to add to filter -->\n <div class=\"input-group-btn\">\n <button type=\"button\" class=\"btn btn-default dropdown-toggle\" data-toggle=\"dropdown\" aria-expanded=\"false\"><span class=\"caret\"></span></button>\n <ul class=\"dropdown-menu dropdown-menu-right\" role=\"menu\">\n"; + stack1 = helpers.each.call(depth0, (depth0 != null ? depth0.columns : depth0), {"name":"each","hash":{},"fn":this.program(3, data, depths),"inverse":this.noop,"data":data}); + if (stack1 != null) { buffer += stack1; } + buffer += " </ul>\n </div>\n </div> <!-- end filter2 -->\n</div>\n</div>\n\n <div class=\"panel panel-default\">\n <div class=\"panel-heading dq-panel\" data-toggle=\"collapse\" href=\"#collapseFive\">\n <h4 class=\"panel-title\">\n WITH CHECK expression: New rows for which this expression returns true will be allowed.\n </h4>\n </div>\n <div id=\"collapseFive\" class=\"panel-collapse collapse in\">\n <div class=\"panel-body\">\n <div class=\"panel-body\">\n <!-- The first dropdown where the user can specify <val1> in the filter <val1> <operation> <val2> -->\n <div class=\"input-group col-xs-4\">\n <!-- Text box for entering the filter text -->\n <input type=\"text\" class=\"form-control dq-filter-1-text\" placeholder=\"table1.col1 * 3\">\n <!-- Dropdown for selecting a column to add to filter -->\n <div class=\"input-group-btn\">\n <button type=\"button\" class=\"btn btn-default dropdown-toggle\" data-toggle=\"dropdown\" aria-expanded=\"false\"><span class=\"caret\"></span></button>\n <ul class=\"dropdown-menu dropdown-menu-right\" role=\"menu\">\n"; + stack1 = helpers.each.call(depth0, (depth0 != null ? depth0.columns : depth0), {"name":"each","hash":{},"fn":this.program(5, data, depths),"inverse":this.noop,"data":data}); + if (stack1 != null) { buffer += stack1; } + buffer += " </ul>\n </div>\n </div> <!-- end filter1 -->\n\n <!-- The first dropdown where the user can specify <operation> in the filter <val1> <operation> <val2> -->\n <div class=\"input-group col-xs-3\">\n <!-- Text box for entering the operation text -->\n <input type=\"text\" class=\"form-control dq-filter-op-text\" value=\"=\">\n <!-- The possible operations are <, <=, >, >=, =, LIKE, IN, and NOT IN -->\n <div class=\"input-group-btn\">\n <button type=\"button\" class=\"btn btn-default dropdown-toggle\" data-toggle=\"dropdown\" aria-expanded=\"false\"><span class=\"caret\"></span></button>\n <ul class=\"dropdown-menu dropdown-menu-right\" role=\"menu\">\n <li><a href=\"#\" class=\"dq-filter-op-link\"><</a></li>\n <li><a href=\"#\" class=\"dq-filter-op-link\"><=</a></li>\n <li><a href=\"#\" class=\"dq-filter-op-link\">=</a></li>\n <li><a href=\"#\" class=\"dq-filter-op-link\">></a></li>\n <li><a href=\"#\" class=\"dq-filter-op-link\">>=</a></li>\n <li><a href=\"#\" class=\"dq-filter-op-link\">LIKE</a></li>\n <li><a href=\"#\" class=\"dq-filter-op-link\">IN</a></li>\n <li><a href=\"#\" class=\"dq-filter-op-link\">NOT IN</a></li>\n </ul>\n </div>\n </div> <!-- end filterop -->\n\n <!-- The first dropdown where the user can specify <val2> in the filter <val1> <operation> <val2> -->\n <div class=\"input-group col-xs-4\">\n <!-- Text box for entering the filter text -->\n <input type=\"text\" class=\"form-control dq-filter-2-text\" placeholder=\"table2.col2+3\">\n <!-- Dropdown for selecting a column to add to filter -->\n <div class=\"input-group-btn\">\n <button type=\"button\" class=\"btn btn-default dropdown-toggle\" data-toggle=\"dropdown\" aria-expanded=\"false\"><span class=\"caret\"></span></button>\n <ul class=\"dropdown-menu dropdown-menu-right\" role=\"menu\">\n"; + stack1 = helpers.each.call(depth0, (depth0 != null ? depth0.columns : depth0), {"name":"each","hash":{},"fn":this.program(7, data, depths),"inverse":this.noop,"data":data}); + if (stack1 != null) { buffer += stack1; } + return buffer + " </ul>\n </div>\n </div> <!-- end filter2 -->\n </div>\n\n </div>\n </div>\n</div>\n\n <!-- Button to run query -->\n <div class=\"row\">\n <button class=\"btn btn-primary col-xs-3 dq-btn-create-policy col-xs-offset-3\">Create Policy</button>\n <button class=\"btn btn-default col-xs-3 dq-btn-cancel-create-policy\">Cancel</button>\n </div>\n</div>\n"; +},"useData":true,"useDepths":true}); this["DataQ"]["templates"]["dataq-container"] = Handlebars.template({"compiler":[6,">= 2.0.0-beta.1"],"main":function(depth0,helpers,partials,data) { return "<!-- HTML file containing the overall DataQ interface -->\n\n<!-- Create a translucent black background -->\n<div class=\"dq-black-background\"></div>\n\n<!-- The DataQ container -->\n<div class=\"dataq container-fluid\">\n\n <h4>\n DataQ allows you to write SQL queries using a simple checklist-like user interface.\n </h4>\n\n <div class=\"panel-group\">\n <div class=\"panel panel-default\">\n <div class=\"panel-heading dq-panel\" data-toggle=\"collapse\" href=\"#collapseOne\">\n <h4 class=\"panel-title\">\n Select Columns\n </h4>\n </div>\n <div id=\"collapseOne\" class=\"panel-collapse collapse in\">\n <div class=\"panel-body\">\n \n <button class=\"btn btn-primary dq-btn-add-table\">Add Table</button>\n \n <!-- The table section HTML goes inside this div -->\n <div class=\"dq-table-section-container\">\n <div class=\"dq-selected-tables\"></div>\n </div>\n\n </div>\n </div>\n </div>\n <div class=\"panel panel-default\">\n <div class=\"panel-heading dq-panel\" data-toggle=\"collapse\" href=\"#collapseTwo\">\n <h4 class=\"panel-title\">\n Filter Rows\n </h4>\n </div>\n <div id=\"collapseTwo\" class=\"panel-collapse collapse in\">\n <div class=\"panel-body\">\n \n <!-- Button to add a new filter -->\n <button class=\"btn btn-primary dq-btn-add-filter\">Add Filter</button>\n\n <!-- The filter section HTML goes inside this div -->\n <div class=\"dq-selected-filters\">\n <ul class=\"dq-filter-list\"></ul>\n </div>\n\n </div>\n </div>\n </div>\n <div class=\"panel panel-default\">\n <div class=\"panel-heading dq-panel\" data-toggle=\"collapse\" href=\"#collapseThree\">\n <h4 class=\"panel-title\">\n Group Columns\n </h4>\n </div>\n <div id=\"collapseThree\" class=\"panel-collapse collapse in\">\n <div class=\"panel-body\">\n \n <!-- Button for adding groupings -->\n <button class=\"btn btn-primary dq-btn-edit-grouping\">Edit Grouping</button>\n\n <!-- The grouping is displayed this header -->\n <div class=\"dq-grouping-section-container\">\n <h4 class=\"dq-grouping-text\">No Groupings...</h4>\n </div>\n\n </div>\n </div>\n </div>\n <div class=\"panel panel-default\">\n <div class=\"panel-heading dq-panel\" data-toggle=\"collapse\" href=\"#collapseFour\">\n <h4 class=\"panel-title\">\n Sort Data\n </h4>\n </div>\n <div id=\"collapseFour\" class=\"panel-collapse collapse in\">\n <div class=\"panel-body\">\n \n <!-- Button to add a sorting -->\n <button class=\"btn btn-primary dq-btn-edit-sorting\">Edit Sorting</button>\n\n <!-- The sorting is displayed in this header -->\n <div class=\"dq-sorting-section-container\">\n <h4 class=\"dq-sorting-text\">No Sorting...</h4>\n </div>\n\n </div>\n </div>\n </div>\n\n <!-- Button to run query -->\n <div class=\"row\">\n <button class=\"btn btn-primary col-xs-3 dq-btn-run-query col-xs-offset-3\">Query</button>\n <button class=\"btn btn-default col-xs-3 dq-btn-cancel-query\">Cancel</button>\n </div>\n</div>\n"; },"useData":true}); diff --git a/src/browser/static/docs/html/_static/custom.css b/src/browser/static/docs/html/_static/custom.css new file mode 100644 index 00000000..2a924f1d --- /dev/null +++ b/src/browser/static/docs/html/_static/custom.css @@ -0,0 +1 @@ +/* This file intentionally left blank. */ diff --git a/src/browser/templates/security-policies.html b/src/browser/templates/security-policies.html index 732131ee..578c4823 100644 --- a/src/browser/templates/security-policies.html +++ b/src/browser/templates/security-policies.html @@ -240,8 +240,6 @@ <h4 class="modal-title" id="confirm-modal-title">Update Security Policy</h4> <script> $(document).on("click", "#btn-dataq-rls-policy", function(e) { e.preventDefault(); - console.log(DataQ); - // DataQ.DQ("{{repo}}", function(query) { DataQ.DQ_rls_policy("{{repo}}", function(query) { $("#txt-sql").val(query); }); From b7f9cbf43c43c79d5c047a2ac0f40c20da2e1bc4 Mon Sep 17 00:00:00 2001 From: Roberto Soto <robsoto@mit.edu> Date: Tue, 30 May 2017 10:49:06 -0400 Subject: [PATCH 09/10] collecting DataQ.Policy fields, still need to submit command to DB --- src/apps/dataq/client_src/js/dataq.js | 14 ++++++++++++-- src/browser/templates/security-policies.html | 10 +++++++--- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/src/apps/dataq/client_src/js/dataq.js b/src/apps/dataq/client_src/js/dataq.js index 1465b3f4..35619f48 100644 --- a/src/apps/dataq/client_src/js/dataq.js +++ b/src/apps/dataq/client_src/js/dataq.js @@ -48,7 +48,7 @@ * @param repo_name - The name of the repo that DataQ should work on. * @param cb - The callback to trigger when the query is built. */ - DataQ.DQ_rls_policy = function(repo_name, cb) { + DataQ.DQ_rls_policy = function(repo_name, table_name, cb) { // Set the callback. callback = cb; @@ -59,6 +59,7 @@ // Create the policy object and set the repo name. policy = DataQ.Policy(); policy.repo(repo_name); + policy.table(table_name); // Handle DataQ close when clicking backdrop. $(".dq-black-background").click(function() { @@ -205,7 +206,8 @@ // Handle DataQ run policy. $(document).on("click", ".dq-btn-run-policy", function() { // Build policy object - + policy.name($("#dq-policy-name").val()); + policy.table // Close DataQ $(".dq-black-background").remove(); @@ -214,4 +216,12 @@ // Build policy string callback(DataQ.build_policy(policy)); }); + + // Handle DataQ close + $(document).con("click", ".dq-btn-cancel-create-policy", function() { + console.log('click'); + $(".dq-black-background").remove(); + $(".dataq").remove(); + callback(null); + }); })(); diff --git a/src/browser/templates/security-policies.html b/src/browser/templates/security-policies.html index 578c4823..1d93fae0 100644 --- a/src/browser/templates/security-policies.html +++ b/src/browser/templates/security-policies.html @@ -16,14 +16,18 @@ <h4 class="inline-block"> <form role="form" method="POST" action="{% url 'security-policy_query' repo_base repo table %}"> {% csrf_token %} <div class="row"> - <div class="form-group col-sm-10"> + + <div class="col-sm-2"> + <button class="btn btn-primary btn-xsm" id="btn-dataq-rls-policy" type="button">Row-Level Policy Builder</button> + </div> + + <div class="form-group col-sm-7"> <input id="txt-sql" type="text" class="form-control" name="q" placeholder="Security Policy e.g.: grant SELECT access to OTHERUSER on REPO.TABLE where ID=1" /> </div> - <div class="col-sm-2"> + <div class="col-sm-3"> <button class="btn btn-primary btn-xsm" id="btn-run" type="submit">Execute</button> <button class="btn btn-primary btn-xsm modal-upload-dialog" type="button" target-modal="#security-policy-modal" title="New Policy"> New Table-Level Policy </button> - <button class="btn btn-primary btn-xsm" id="btn-dataq-rls-policy" type="button">Row-Level Policy Builder</button> </div> </div> From 7d91a4736b6e4524fdf5d3d222b5ee56b627b870 Mon Sep 17 00:00:00 2001 From: Roberto Soto <robsoto@mit.edu> Date: Thu, 1 Jun 2017 14:13:15 -0400 Subject: [PATCH 10/10] More UI - backend connections --- src/apps/dataq/client_src/js/dataq.js | 37 ++++++++++++++++--- .../client_src/js/dq-rls-policy-builder.js | 24 +++++++++--- src/apps/dataq/client_src/js/dq-rls-policy.js | 26 +++++++------ .../templates/dataq-container-rls-policy.hbs | 24 ++++++------ src/browser/templates/security-policies.html | 2 +- 5 files changed, 79 insertions(+), 34 deletions(-) diff --git a/src/apps/dataq/client_src/js/dataq.js b/src/apps/dataq/client_src/js/dataq.js index 35619f48..0e685d0e 100644 --- a/src/apps/dataq/client_src/js/dataq.js +++ b/src/apps/dataq/client_src/js/dataq.js @@ -49,6 +49,9 @@ * @param cb - The callback to trigger when the query is built. */ DataQ.DQ_rls_policy = function(repo_name, table_name, cb) { + console.log(repo_name); + console.log(table_name); + // Set the callback. callback = cb; @@ -203,11 +206,31 @@ callback(DataQ.build_query(query)); }); - // Handle DataQ run policy. - $(document).on("click", ".dq-btn-run-policy", function() { + // Handle DataQ create policy. + $(document).on("click", ".dq-btn-create-policy", function() { // Build policy object policy.name($("#dq-policy-name").val()); - policy.table + + policy.command($("#dq-policy-command-selected").text()); + + roles = $("#dq-policy-role-list").val().split(",").map(function(r) { + return r.trim(); + }); + policy.roles(roles); + + using_expr_obj = { + "filter1": $("#dq-policy-using-expr-filter-1").val(), + "op" : $("#dq-policy-using-expr-op").val(), + "filter2": $("#dq-policy-using-expr-filter-2").val() + }; + policy.using_expression(using_expr_obj); + + check_expr_obj = { + "filter1": $("#dq-policy-check-expr-filter-1").val(), + "op" : $("#dq-policy-check-expr-op").val(), + "filter2": $("#dq-policy-check-expr-filter-2").val() + }; + policy.using_expression(check_expr_obj); // Close DataQ $(".dq-black-background").remove(); @@ -217,9 +240,13 @@ callback(DataQ.build_policy(policy)); }); + // Handle policy command dropdown selection + $(document).on("click", "#dq-policy-dropdown-menu a", function() { + $('#dq-policy-command-selected').text($(this).text()); + }); + // Handle DataQ close - $(document).con("click", ".dq-btn-cancel-create-policy", function() { - console.log('click'); + $(document).on("click", ".dq-btn-cancel-create-policy", function() { $(".dq-black-background").remove(); $(".dataq").remove(); callback(null); diff --git a/src/apps/dataq/client_src/js/dq-rls-policy-builder.js b/src/apps/dataq/client_src/js/dq-rls-policy-builder.js index 61010c1c..7ed03910 100644 --- a/src/apps/dataq/client_src/js/dq-rls-policy-builder.js +++ b/src/apps/dataq/client_src/js/dq-rls-policy-builder.js @@ -27,7 +27,7 @@ var policy_name = policy.name(); // Name of table to which the policy applies. - var table_name = policy.repo() + "." + policy.name(); + var table_name = policy.repo() + "." + policy.table(); // Command to which the policy applies. var command = policy.command(); @@ -56,13 +56,27 @@ policy_string += " TO " + role_list.join(", "); // USING clause - policy_string += " USING " + using_expr; + if (command !== "INSERT") { + policy_string += " USING " + using_expr; + } else if (using_expr !== null) { + var err_msg = "An INSERT policy cannot have a USING expression, as USING is" + + " used for reading existing records, not adding new ones."; + alert(err_msg); + return null; + } // WITH CHECK clause - if (command !== "SELECT") { - // A SELECT policy cannot have a WITH CHECK expression, as it only applies - // in cases where records are being retrieved from the relation. + if (command !== "SELECT" && command !== "DELETE") { + // Neither a SELECT or DELETE policy can have a WITH CHECK expression, + // as this expression is used for validating new records, not reading or + // deleting existing ones. policy_string += " WITH CHECK " + check_expr; + } else if (check_expr !== null) { + var err_msg = "Neither a SELECT or DELETE policy can have a WITH CHECK expression" + + ", as this expression is used for validating new records, not" + + " reading or deleting existing ones."; + alert(err_msg); + return null; } // Remove leading and trailing spaces and then append semicolon. diff --git a/src/apps/dataq/client_src/js/dq-rls-policy.js b/src/apps/dataq/client_src/js/dq-rls-policy.js index 249c5615..264e36c6 100644 --- a/src/apps/dataq/client_src/js/dq-rls-policy.js +++ b/src/apps/dataq/client_src/js/dq-rls-policy.js @@ -63,11 +63,11 @@ }; /** - * Get or set the command { ALL | SELECT | INSERT| UPDATE | DELETE } to which + * Get or set the command { ALL | SELECT | INSERT | UPDATE | DELETE } to which * this policy will apply. * * @param cmd - If this argument is omitted (or undefined), this function acts as a - * getter. Otherwise, it acts as a setter, setting the repo name. + * getter. Otherwise, it acts as a setter, setting the allowed command. * * @return The name of the command. */ @@ -82,7 +82,7 @@ * Get or set the Roles to which this policy will apply. * * @param role_list - If this argument is omitted (or undefined), this function acts as a - * getter. Otherwise, it acts as a setter, setting the repo name. + * getter. Otherwise, it acts as a setter, setting the list of roles. * * @return The list of Roles. */ @@ -100,13 +100,14 @@ * Get or set the policy's using_expression. * * @param expr - If this argument is omitted (or undefined), this function acts as a - * getter. Otherwise, it acts as a setter, setting the repo name. + * getter. Otherwise, it acts as a setter, setting the USING expression. * - * @return The full using_expression. + * @return The full USING expression. */ - that.using_expression = function(expr) { - if (expr !== undefined) { - that._using_expr = expr; + that.using_expression = function(expr_obj) { + if (expr_obj !== undefined) { + using_expression = expr_obj.filter1 + " " + expr_obj.op + " " + expr_obj.filter2; + that._using_expr = using_expression; } return that._using_expr; }; @@ -115,12 +116,13 @@ * Get or set the policy's check_expression. * * @param expr - If this argument is omitted (or undefined), this function acts as a - * getter. Otherwise, it acts as a setter, setting the repo name. + * getter. Otherwise, it acts as a setter, setting the WITH CHECK expression. * - * @return The full check_expression. + * @return The full WITH CHECK expression. */ - that.check_expression = function(expr) { - if (expr !== undefined) { + that.check_expression = function(expr_obj) { + if (expr_obj !== undefined) { + check_expression = expr_obj.filter1 + " " + expr_obj.op + " " + expr_obj.filter2; that._check_expr = expr; } return that._check_expr; diff --git a/src/apps/dataq/client_src/templates/dataq-container-rls-policy.hbs b/src/apps/dataq/client_src/templates/dataq-container-rls-policy.hbs index 352ae66f..0c01cda2 100644 --- a/src/apps/dataq/client_src/templates/dataq-container-rls-policy.hbs +++ b/src/apps/dataq/client_src/templates/dataq-container-rls-policy.hbs @@ -21,7 +21,7 @@ </div> <div id="collapseOne" class="panel-collapse collapse in"> <div class="panel-body"> - <input id="dq-policy-name" type="text" class="form-control" placeholder="Policy Name" /> + <input id="dq-policy-name" type="text" placeholder="Policy Name" /> </div> </div> @@ -35,9 +35,11 @@ <div id="collapseTwo" class="panel-collapse collapse in"> <div class="panel-body"> <div class="dropdown" id="dq-policy-commands-dropdown"> - <button class="btn btn-primary dropdown-toggle" type="button" data-toggle="dropdown">Choose command - <span class="caret"></span></button> - <ul class="dropdown-menu"> + <button class="btn btn-primary dropdown-toggle dq-modal-dropdown-btn" type="button" data-toggle="dropdown"> + <span id="dq-policy-command-selected">Choose command</span> + <span class="caret"></span> + </button> + <ul id="dq-policy-dropdown-menu" class="dropdown-menu"> <li><a href="#">SELECT</a></li> <li><a href="#">INSERT</a></li> <li><a href="#">UPDATE</a></li> @@ -57,7 +59,7 @@ </div> <div id="collapseThree" class="panel-collapse collapse in"> <div class="panel-body"> - <input id="dq-policy-role-list" type="text" class="form-control" placeholder="Comma separated role names. e.g. CURRENT_USER, admin, myGroupRole" /> + <input id="dq-policy-role-list" type="text" placeholder="Comma separated role names. e.g. CURRENT_USER, admin, myGroupRole" /> </div> </div> @@ -73,7 +75,7 @@ <!-- The first dropdown where the user can specify <val1> in the filter <val1> <operation> <val2> --> <div class="input-group col-xs-4"> <!-- Text box for entering the filter text --> - <input type="text" class="form-control dq-filter-1-text" placeholder="table1.col1 * 3"> + <input type="text" id="dq-policy-using-expr-filter-1" class="dq-filter-1-text" placeholder="table1.col1 * 3"> <!-- Dropdown for selecting a column to add to filter --> <div class="input-group-btn"> <button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown" aria-expanded="false"><span class="caret"></span></button> @@ -88,7 +90,7 @@ <!-- The first dropdown where the user can specify <operation> in the filter <val1> <operation> <val2> --> <div class="input-group col-xs-3"> <!-- Text box for entering the operation text --> - <input type="text" class="form-control dq-filter-op-text" value="="> + <input type="text" id="dq-policy-using-expr-op" class="dq-filter-op-text" value="="> <!-- The possible operations are <, <=, >, >=, =, LIKE, IN, and NOT IN --> <div class="input-group-btn"> <button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown" aria-expanded="false"><span class="caret"></span></button> @@ -108,7 +110,7 @@ <!-- The second dropdown where the user can specify <val2> in the filter <val1> <operation> <val2> --> <div class="input-group col-xs-4"> <!-- Text box for entering the filter text --> - <input type="text" class="form-control dq-filter-2-text" placeholder="table2.col2+3"> + <input type="text" id="dq-policy-using-expr-filter-2" class="dq-filter-2-text" placeholder="table2.col2+3"> <!-- Dropdown for selecting a column to add to filter --> <div class="input-group-btn"> <button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown" aria-expanded="false"><span class="caret"></span></button> @@ -134,7 +136,7 @@ <!-- The first dropdown where the user can specify <val1> in the filter <val1> <operation> <val2> --> <div class="input-group col-xs-4"> <!-- Text box for entering the filter text --> - <input type="text" class="form-control dq-filter-1-text" placeholder="table1.col1 * 3"> + <input type="text" id="dq-policy-check-expr-filter-1" class="dq-filter-1-text" placeholder="table1.col1 * 3"> <!-- Dropdown for selecting a column to add to filter --> <div class="input-group-btn"> <button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown" aria-expanded="false"><span class="caret"></span></button> @@ -149,7 +151,7 @@ <!-- The first dropdown where the user can specify <operation> in the filter <val1> <operation> <val2> --> <div class="input-group col-xs-3"> <!-- Text box for entering the operation text --> - <input type="text" class="form-control dq-filter-op-text" value="="> + <input type="text" id="dq-policy-check-expr-op" class="dq-filter-op-text" value="="> <!-- The possible operations are <, <=, >, >=, =, LIKE, IN, and NOT IN --> <div class="input-group-btn"> <button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown" aria-expanded="false"><span class="caret"></span></button> @@ -169,7 +171,7 @@ <!-- The first dropdown where the user can specify <val2> in the filter <val1> <operation> <val2> --> <div class="input-group col-xs-4"> <!-- Text box for entering the filter text --> - <input type="text" class="form-control dq-filter-2-text" placeholder="table2.col2+3"> + <input type="text" id="dq-policy-check-expr-filter-2" class="dq-filter-2-text" placeholder="table2.col2+3"> <!-- Dropdown for selecting a column to add to filter --> <div class="input-group-btn"> <button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown" aria-expanded="false"><span class="caret"></span></button> diff --git a/src/browser/templates/security-policies.html b/src/browser/templates/security-policies.html index 1d93fae0..e71f7a5b 100644 --- a/src/browser/templates/security-policies.html +++ b/src/browser/templates/security-policies.html @@ -244,7 +244,7 @@ <h4 class="modal-title" id="confirm-modal-title">Update Security Policy</h4> <script> $(document).on("click", "#btn-dataq-rls-policy", function(e) { e.preventDefault(); - DataQ.DQ_rls_policy("{{repo}}", function(query) { + DataQ.DQ_rls_policy("{{repo}}", "{{table}}", function(query) { $("#txt-sql").val(query); }); });