[Bps-public-commit] rt-extension-rightsdebugger branch, master, updated. 78eacafa3fc8d203d089693eca2ba23cdb389819
Shawn Moore
shawn at bestpractical.com
Tue Mar 7 12:54:28 EST 2017
The branch, master has been updated
via 78eacafa3fc8d203d089693eca2ba23cdb389819 (commit)
via 6fb27e1bd63ad72cbfaf7dc5fc065cc48d9e1545 (commit)
via 7a39182e93d16bd2374d20f43bbb6b649e61135d (commit)
via 9eb1e283c8f45c089bc3ae314dd57a895dfc1f67 (commit)
via b947d9b018c7aab88cccd55048911b82fd4a1312 (commit)
via 1418689efa49906ed713afb6dffafa2d4d35d195 (commit)
via d0b8fb5be4ecc29f4dd955dcbc1da7a0118e5069 (commit)
via e968b96855948b646e38fa9408b88f6b99bd8a27 (commit)
via 8200ecb7a18d503a25fcf68f3cf552c9aafd4611 (commit)
via acd22f7f5da97724a4ab172923f4b913b8a31cfc (commit)
via ff256c00d17a21b98df707cc53f669c2ec84922e (commit)
via 3a74dff552e010bb16ccf4cd677ff60455232fe6 (commit)
via e9b092dffcf8627fa2c8e034a95258751a1b5412 (commit)
via 4f5dfb6c19a947f92a665d8c2b4bc1b6c0417760 (commit)
via 24396d15038d435d19e36173471e7a9894b65636 (commit)
via 0a9ac7e46dfc9c366c9508725706285ed26ba023 (commit)
via f73920b67c1b8ecb42cb6ef3b546b579f853ba0c (commit)
via 5efa0447aebae97b785788e302c5f8d4da7d5dc0 (commit)
from 261bfdcf56f1e7be5e540e00db9594b1cac598ab (commit)
Summary of changes:
html/Admin/RightsDebugger/index.html | 7 +
html/Helpers/RightsDebugger/Search | 96 +-----------
lib/RT/Extension/RightsDebugger.pm | 275 ++++++++++++++++++++++++++++++++---
static/css/rights-debugger.css | 27 +++-
static/js/rights-debugger.js | 78 +++++++---
5 files changed, 349 insertions(+), 134 deletions(-)
- Log -----------------------------------------------------------------
commit 5efa0447aebae97b785788e302c5f8d4da7d5dc0
Author: Shawn M Moore <shawn at bestpractical.com>
Date: Tue Mar 7 15:20:56 2017 +0000
Make revoke cell only as wide as it needs to be
diff --git a/static/css/rights-debugger.css b/static/css/rights-debugger.css
index 53c94e4..6057eeb 100644
--- a/static/css/rights-debugger.css
+++ b/static/css/rights-debugger.css
@@ -4,6 +4,11 @@
width: 15em;
}
+#rights-debugger .results .result .cell.revoke {
+ display: inline-block;
+ width: auto;
+}
+
#rights-debugger .search .loading {
display: none;
}
commit f73920b67c1b8ecb42cb6ef3b546b579f853ba0c
Author: Shawn M Moore <shawn at bestpractical.com>
Date: Tue Mar 7 15:29:46 2017 +0000
Truncate long object names
diff --git a/static/css/rights-debugger.css b/static/css/rights-debugger.css
index 6057eeb..9c7ff77 100644
--- a/static/css/rights-debugger.css
+++ b/static/css/rights-debugger.css
@@ -4,6 +4,11 @@
width: 15em;
}
+#rights-debugger .results .result .cell {
+ text-overflow: ellipsis;
+ overflow: hidden;
+}
+
#rights-debugger .results .result .cell.revoke {
display: inline-block;
width: auto;
commit 0a9ac7e46dfc9c366c9508725706285ed26ba023
Author: Shawn M Moore <shawn at bestpractical.com>
Date: Tue Mar 7 15:30:31 2017 +0000
Use ->Subject rather than nonexistent ->Name for rendering Tickets
diff --git a/lib/RT/Extension/RightsDebugger.pm b/lib/RT/Extension/RightsDebugger.pm
index 099ea6a..30a468b 100644
--- a/lib/RT/Extension/RightsDebugger.pm
+++ b/lib/RT/Extension/RightsDebugger.pm
@@ -82,6 +82,10 @@ sub LabelForRecord {
my $self = shift;
my $record = shift;
+ if ($record->isa('RT::Ticket')) {
+ return $record->Subject;
+ }
+
return $record->Name;
}
commit 24396d15038d435d19e36173471e7a9894b65636
Author: Shawn M Moore <shawn at bestpractical.com>
Date: Tue Mar 7 15:34:16 2017 +0000
Move search implementation from /Helpers into lib/
diff --git a/html/Helpers/RightsDebugger/Search b/html/Helpers/RightsDebugger/Search
index c5d0698..6481e45 100644
--- a/html/Helpers/RightsDebugger/Search
+++ b/html/Helpers/RightsDebugger/Search
@@ -1,97 +1,6 @@
<%INIT>
-my @results;
-my %search;
-
-my $ACL = RT::ACL->new($session{CurrentUser});
-
-my $has_search = 0;
-
-if ($ARGS{right}) {
- $has_search = 1;
- for my $word (split ' ', $ARGS{right}) {
- $ACL->Limit(
- FIELD => 'RightName',
- OPERATOR => 'LIKE',
- VALUE => $word,
- CASESENSITIVE => 0,
- ENTRYAGGREGATOR => 'OR',
- );
- }
-}
-
-$ACL->UnLimit unless $has_search;
-
-for my $key (qw/principal object right/) {
- if (my $search = $ARGS{$key}) {
- my @matchers;
- for my $word (split ' ', $search) {
- push @matchers, qr/\Q$word\E/i;
- }
- $search{$key} = \@matchers;
- }
-}
-
-my $escape_html = sub {
- my $s = shift;
- RT::Interface::Web::EscapeHTML(\$s);
- return $s;
-};
-
-my $highlight_term = sub {
- my ($text, $re) = @_;
-
- $text =~ s{
- \G # where we left off the previous iteration thanks to /g
- (.*?) # non-matching text before the match
- ($re|$) # matching text, or the end of the line (to escape any
- # text after the last match)
- }{
- $escape_html->($1) .
- (length $2 ? '<span class="match">' . $escape_html->($2) . '</span>' : '')
- }xeg;
-
- return $text; # now escape as html
-};
-
-ACE: while (my $ACE = $ACL->Next) {
- my $serialized = RT::Extension::RightsDebugger->SerializeACE($ACE);
-
- # this is hacky, but doing the searching in SQL is absolutely a nonstarter
- for my $key (qw/principal object/) {
- if (my $matchers = $search{$key}) {
- my $record = $serialized->{$key};
- for my $re (@$matchers) {
- next ACE unless $record->{class} =~ $re
- || $record->{id} =~ $re
- || $record->{label} =~ $re
- || $record->{detail} =~ $re;
- }
- }
- }
-
- # highlight matching words
- $serialized->{right_highlighted} = $highlight_term->($serialized->{right}, join '|', @{ $search{right} || [] });
-
- for my $key (qw/principal object/) {
- my $record = $serialized->{$key};
-
- if (my $matchers = $search{$key}) {
- my $re = join '|', @$matchers;
- for my $column (qw/label detail/) {
- $record->{$column . '_highlighted'} = $highlight_term->($record->{$column}, $re);
- }
- }
-
- for my $column (qw/label detail/) {
- # make sure we escape html if there was no search
- $record->{$column . '_highlighted'} //= $escape_html->($record->{$column});
- }
- }
-
- push @results, $serialized;
-}
-
+my $results = RT::Extension::RightsDebugger->Search(%ARGS);
$r->content_type('application/json; charset=utf-8');
-$m->out(JSON({ count => $ACL->Count, results => \@results }));
+$m->out(JSON({ results => $results }));
$m->abort;
</%INIT>
diff --git a/lib/RT/Extension/RightsDebugger.pm b/lib/RT/Extension/RightsDebugger.pm
index 30a468b..d124740 100644
--- a/lib/RT/Extension/RightsDebugger.pm
+++ b/lib/RT/Extension/RightsDebugger.pm
@@ -11,6 +11,110 @@ RT->AddJavaScript("handlebars-4.0.6.min.js");
$RT::Interface::Web::WHITELISTED_COMPONENT_ARGS{'/Admin/RightsDebugger/index.html'} = ['Principal', 'Object', 'Right'];
+sub Search {
+ my $self = shift;
+ my %args = (
+ principal => '',
+ object => '',
+ right => '',
+ @_,
+ );
+
+ my @results;
+ my %search;
+
+ my $ACL = RT::ACL->new($HTML::Mason::Commands::session{CurrentUser});
+
+ my $has_search = 0;
+
+ if ($args{right}) {
+ $has_search = 1;
+ for my $word (split ' ', $args{right}) {
+ $ACL->Limit(
+ FIELD => 'RightName',
+ OPERATOR => 'LIKE',
+ VALUE => $word,
+ CASESENSITIVE => 0,
+ ENTRYAGGREGATOR => 'OR',
+ );
+ }
+ }
+
+ $ACL->UnLimit unless $has_search;
+
+ for my $key (qw/principal object right/) {
+ if (my $search = $args{$key}) {
+ my @matchers;
+ for my $word (split ' ', $search) {
+ push @matchers, qr/\Q$word\E/i;
+ }
+ $search{$key} = \@matchers;
+ }
+ }
+
+ my $escape_html = sub {
+ my $s = shift;
+ RT::Interface::Web::EscapeHTML(\$s);
+ return $s;
+ };
+
+ my $highlight_term = sub {
+ my ($text, $re) = @_;
+
+ $text =~ s{
+ \G # where we left off the previous iteration thanks to /g
+ (.*?) # non-matching text before the match
+ ($re|$) # matching text, or the end of the line (to escape any
+ # text after the last match)
+ }{
+ $escape_html->($1) .
+ (length $2 ? '<span class="match">' . $escape_html->($2) . '</span>' : '')
+ }xeg;
+
+ return $text; # now escape as html
+ };
+
+ ACE: while (my $ACE = $ACL->Next) {
+ my $serialized = $self->SerializeACE($ACE);
+
+ # this is hacky, but doing the searching in SQL is absolutely a nonstarter
+ for my $key (qw/principal object/) {
+ if (my $matchers = $search{$key}) {
+ my $record = $serialized->{$key};
+ for my $re (@$matchers) {
+ next ACE unless $record->{class} =~ $re
+ || $record->{id} =~ $re
+ || $record->{label} =~ $re
+ || $record->{detail} =~ $re;
+ }
+ }
+ }
+
+ # highlight matching words
+ $serialized->{right_highlighted} = $highlight_term->($serialized->{right}, join '|', @{ $search{right} || [] });
+
+ for my $key (qw/principal object/) {
+ my $record = $serialized->{$key};
+
+ if (my $matchers = $search{$key}) {
+ my $re = join '|', @$matchers;
+ for my $column (qw/label detail/) {
+ $record->{$column . '_highlighted'} = $highlight_term->($record->{$column}, $re);
+ }
+ }
+
+ for my $column (qw/label detail/) {
+ # make sure we escape html if there was no search
+ $record->{$column . '_highlighted'} //= $escape_html->($record->{$column});
+ }
+ }
+
+ push @results, $serialized;
+ }
+
+ return \@results;
+}
+
sub SerializeACE {
my $self = shift;
my $ACE = shift;
commit 4f5dfb6c19a947f92a665d8c2b4bc1b6c0417760
Author: Shawn M Moore <shawn at bestpractical.com>
Date: Tue Mar 7 15:36:54 2017 +0000
Refactor lexical subs as private functions
diff --git a/lib/RT/Extension/RightsDebugger.pm b/lib/RT/Extension/RightsDebugger.pm
index d124740..c5f078f 100644
--- a/lib/RT/Extension/RightsDebugger.pm
+++ b/lib/RT/Extension/RightsDebugger.pm
@@ -11,6 +11,28 @@ RT->AddJavaScript("handlebars-4.0.6.min.js");
$RT::Interface::Web::WHITELISTED_COMPONENT_ARGS{'/Admin/RightsDebugger/index.html'} = ['Principal', 'Object', 'Right'];
+sub _EscapeHTML {
+ my $s = shift;
+ RT::Interface::Web::EscapeHTML(\$s);
+ return $s;
+}
+
+sub _HighlightTerm {
+ my ($text, $re) = @_;
+
+ $text =~ s{
+ \G # where we left off the previous iteration thanks to /g
+ (.*?) # non-matching text before the match
+ ($re|$) # matching text, or the end of the line (to escape any
+ # text after the last match)
+ }{
+ _EscapeHTML($1) .
+ (length $2 ? '<span class="match">' . _EscapeHTML($2) . '</span>' : '')
+ }xeg;
+
+ return $text; # now escaped as html
+}
+
sub Search {
my $self = shift;
my %args = (
@@ -52,28 +74,6 @@ sub Search {
}
}
- my $escape_html = sub {
- my $s = shift;
- RT::Interface::Web::EscapeHTML(\$s);
- return $s;
- };
-
- my $highlight_term = sub {
- my ($text, $re) = @_;
-
- $text =~ s{
- \G # where we left off the previous iteration thanks to /g
- (.*?) # non-matching text before the match
- ($re|$) # matching text, or the end of the line (to escape any
- # text after the last match)
- }{
- $escape_html->($1) .
- (length $2 ? '<span class="match">' . $escape_html->($2) . '</span>' : '')
- }xeg;
-
- return $text; # now escape as html
- };
-
ACE: while (my $ACE = $ACL->Next) {
my $serialized = $self->SerializeACE($ACE);
@@ -91,7 +91,7 @@ sub Search {
}
# highlight matching words
- $serialized->{right_highlighted} = $highlight_term->($serialized->{right}, join '|', @{ $search{right} || [] });
+ $serialized->{right_highlighted} = _HighlightTerm($serialized->{right}, join '|', @{ $search{right} || [] });
for my $key (qw/principal object/) {
my $record = $serialized->{$key};
@@ -99,13 +99,13 @@ sub Search {
if (my $matchers = $search{$key}) {
my $re = join '|', @$matchers;
for my $column (qw/label detail/) {
- $record->{$column . '_highlighted'} = $highlight_term->($record->{$column}, $re);
+ $record->{$column . '_highlighted'} = _HighlightTerm($record->{$column}, $re);
}
}
for my $column (qw/label detail/) {
# make sure we escape html if there was no search
- $record->{$column . '_highlighted'} //= $escape_html->($record->{$column});
+ $record->{$column . '_highlighted'} //= _EscapeHTML($record->{$column});
}
}
commit e9b092dffcf8627fa2c8e034a95258751a1b5412
Author: Shawn M Moore <shawn at bestpractical.com>
Date: Tue Mar 7 15:41:12 2017 +0000
Factor out _HighlightSerializedForSearch
diff --git a/lib/RT/Extension/RightsDebugger.pm b/lib/RT/Extension/RightsDebugger.pm
index c5f078f..0edf1b8 100644
--- a/lib/RT/Extension/RightsDebugger.pm
+++ b/lib/RT/Extension/RightsDebugger.pm
@@ -33,6 +33,32 @@ sub _HighlightTerm {
return $text; # now escaped as html
}
+sub _HighlightSerializedForSearch {
+ my $serialized = shift;
+ my $search = shift;
+
+ # highlight matching words
+ $serialized->{right_highlighted} = _HighlightTerm($serialized->{right}, join '|', @{ $search->{right} || [] });
+
+ for my $key (qw/principal object/) {
+ my $record = $serialized->{$key};
+
+ if (my $matchers = $search->{$key}) {
+ my $re = join '|', @$matchers;
+ for my $column (qw/label detail/) {
+ $record->{$column . '_highlighted'} = _HighlightTerm($record->{$column}, $re);
+ }
+ }
+
+ for my $column (qw/label detail/) {
+ # make sure we escape html if there was no search
+ $record->{$column . '_highlighted'} //= _EscapeHTML($record->{$column});
+ }
+ }
+
+ return;
+}
+
sub Search {
my $self = shift;
my %args = (
@@ -90,24 +116,7 @@ sub Search {
}
}
- # highlight matching words
- $serialized->{right_highlighted} = _HighlightTerm($serialized->{right}, join '|', @{ $search{right} || [] });
-
- for my $key (qw/principal object/) {
- my $record = $serialized->{$key};
-
- if (my $matchers = $search{$key}) {
- my $re = join '|', @$matchers;
- for my $column (qw/label detail/) {
- $record->{$column . '_highlighted'} = _HighlightTerm($record->{$column}, $re);
- }
- }
-
- for my $column (qw/label detail/) {
- # make sure we escape html if there was no search
- $record->{$column . '_highlighted'} //= _EscapeHTML($record->{$column});
- }
- }
+ _HighlightSerializedForSearch($serialized, \%search);
push @results, $serialized;
}
commit 3a74dff552e010bb16ccf4cd677ff60455232fe6
Author: Shawn M Moore <shawn at bestpractical.com>
Date: Tue Mar 7 15:47:32 2017 +0000
Support ORing principal and object search results
RightName already works this way
We'll have special support queue:1 etc for helping to avoid spurious
matches
diff --git a/lib/RT/Extension/RightsDebugger.pm b/lib/RT/Extension/RightsDebugger.pm
index 0edf1b8..fbaf13f 100644
--- a/lib/RT/Extension/RightsDebugger.pm
+++ b/lib/RT/Extension/RightsDebugger.pm
@@ -104,15 +104,18 @@ sub Search {
my $serialized = $self->SerializeACE($ACE);
# this is hacky, but doing the searching in SQL is absolutely a nonstarter
- for my $key (qw/principal object/) {
+ KEY: for my $key (qw/principal object/) {
if (my $matchers = $search{$key}) {
my $record = $serialized->{$key};
for my $re (@$matchers) {
- next ACE unless $record->{class} =~ $re
- || $record->{id} =~ $re
- || $record->{label} =~ $re
- || $record->{detail} =~ $re;
+ next KEY if $record->{class} =~ $re
+ || $record->{id} =~ $re
+ || $record->{label} =~ $re
+ || $record->{detail} =~ $re;
}
+
+ # no matches
+ next ACE;
}
}
commit ff256c00d17a21b98df707cc53f669c2ec84922e
Author: Shawn M Moore <shawn at bestpractical.com>
Date: Tue Mar 7 16:13:09 2017 +0000
Implement chunked search result loading
I'm sensitive to performance since this is not using SQL for searching
Our modest sized RT has ~1500 ACLs, so waiting for the entire resultset
to be processed might take long enough that (assuming we don't time out) the
user will get annoyed
diff --git a/html/Admin/RightsDebugger/index.html b/html/Admin/RightsDebugger/index.html
index 38ebce1..7af2af6 100644
--- a/html/Admin/RightsDebugger/index.html
+++ b/html/Admin/RightsDebugger/index.html
@@ -10,6 +10,7 @@
</div>
<div class="results">
</div>
+ <span class="loading"><img src="<%RT->Config->Get('WebPath')%>/static/images/loading.gif" alt="<%loc('Loading')%>" title="<%loc('Loading')%>" /></span>
</form>
<script type="text/x-template" id="debugger-record-template">
diff --git a/html/Helpers/RightsDebugger/Search b/html/Helpers/RightsDebugger/Search
index 6481e45..9ce83e6 100644
--- a/html/Helpers/RightsDebugger/Search
+++ b/html/Helpers/RightsDebugger/Search
@@ -1,6 +1,6 @@
<%INIT>
my $results = RT::Extension::RightsDebugger->Search(%ARGS);
$r->content_type('application/json; charset=utf-8');
-$m->out(JSON({ results => $results }));
+$m->out(JSON($results));
$m->abort;
</%INIT>
diff --git a/lib/RT/Extension/RightsDebugger.pm b/lib/RT/Extension/RightsDebugger.pm
index fbaf13f..bab43c2 100644
--- a/lib/RT/Extension/RightsDebugger.pm
+++ b/lib/RT/Extension/RightsDebugger.pm
@@ -88,8 +88,19 @@ sub Search {
}
}
+ if ($args{continueAfter}) {
+ $has_search = 1;
+ $ACL->Limit(
+ FIELD => 'id',
+ OPERATOR => '>',
+ VALUE => $args{continueAfter},
+ );
+ }
+
$ACL->UnLimit unless $has_search;
+ $ACL->RowsPerPage(100);
+
for my $key (qw/principal object right/) {
if (my $search = $args{$key}) {
my @matchers;
@@ -100,7 +111,10 @@ sub Search {
}
}
+ my $continueAfter;
+
ACE: while (my $ACE = $ACL->Next) {
+ $continueAfter = $ACE->Id;
my $serialized = $self->SerializeACE($ACE);
# this is hacky, but doing the searching in SQL is absolutely a nonstarter
@@ -124,7 +138,14 @@ sub Search {
push @results, $serialized;
}
- return \@results;
+ # if we didn't fill the whole page, then we know there are
+ # no more rows to consider
+ undef $continueAfter if $ACL->Count < $ACL->RowsPerPage;
+
+ return {
+ results => \@results,
+ continueAfter => $continueAfter,
+ };
}
sub SerializeACE {
diff --git a/static/css/rights-debugger.css b/static/css/rights-debugger.css
index 9c7ff77..07c7fb7 100644
--- a/static/css/rights-debugger.css
+++ b/static/css/rights-debugger.css
@@ -14,17 +14,23 @@
width: auto;
}
-#rights-debugger .search .loading {
+#rights-debugger .search .loading,
+#rights-debugger > .loading {
display: none;
}
#rights-debugger .search .loading img,
+#rights-debugger > .loading img,
#rights-debugger .results .revoke img {
height: 1.5em;
width: 1.5em;
}
-#rights-debugger.refreshing .search .loading {
+#rights-debugger.awaiting-first-result .search .loading {
+ display: inline;
+}
+
+#rights-debugger.continuing-load > .loading {
display: inline;
}
@@ -33,7 +39,7 @@
font-weight: bold;
}
-#rights-debugger.refreshing .results {
+#rights-debugger.awaiting-first-result .results {
opacity: 0.5;
}
diff --git a/static/js/rights-debugger.js b/static/js/rights-debugger.js
index 9d72fcc..ab408db 100644
--- a/static/js/rights-debugger.js
+++ b/static/js/rights-debugger.js
@@ -28,33 +28,33 @@ jQuery(function () {
};
var displayRevoking = function (button) {
+ if (button.hasClass('ui-state-disabled')) {
+ return;
+ }
+
button.addClass('ui-state-disabled').prop('disabled', true);
button.after(loading.clone());
};
- var refreshResults = function () {
- form.addClass('refreshing');
- form.find('button').addClass('ui-state-disabled').prop('disabled', true);
-
- var serialized = form.serializeArray();
- var search = {};
- jQuery.each(serialized, function(i, field){
- search[field.name] = field.value;
- });
-
- if (existingRequest) {
- existingRequest.abort();
- }
+ var requestPage;
+ requestPage = function (search, continueAfter) {
+ search.continueAfter = continueAfter;
existingRequest = jQuery.ajax({
url: form.attr('action'),
data: search,
timeout: 30000, /* 30 seconds */
success: function (response) {
- form.removeClass('refreshing').removeClass('error');
- display.empty();
+ form.removeClass('error');
var items = response.results;
+
+ /* change UI only after we find a result */
+ if (items.length && form.hasClass('awaiting-first-result')) {
+ display.empty();
+ form.removeClass('awaiting-first-result').addClass('continuing-load');
+ }
+
jQuery.each(items, function (i, item) {
display.append(renderItem({ search: search, item: item }));
});
@@ -63,19 +63,50 @@ jQuery(function () {
var revokeButton = buttonForAction(key);
displayRevoking(revokeButton);
});
+
+ if (response.continueAfter) {
+ requestPage(search, response.continueAfter);
+ }
+ else {
+ form.removeClass('continuing-load');
+
+ if (form.hasClass('awaiting-first-result')) {
+ display.empty();
+ form.removeClass('awaiting-first-result');
+ display.text('No results');
+ }
+ }
},
error: function (xhr, reason) {
if (reason == 'abort') {
return;
}
- form.removeClass('refreshing').addClass('error');
+ form.removeClass('awaiting-first-result').removeClass('continuing-load').addClass('error');
display.empty();
display.text('Error: ' + xhr.statusText);
}
});
};
+ var beginSearch = function () {
+ form.removeClass('continuing-load').addClass('awaiting-first-result');
+ form.find('button').addClass('ui-state-disabled').prop('disabled', true);
+
+ var serialized = form.serializeArray();
+ var search = {};
+
+ jQuery.each(serialized, function(i, field){
+ search[field.name] = field.value;
+ });
+
+ if (existingRequest) {
+ existingRequest.abort();
+ }
+
+ requestPage(search, 0);
+ };
+
display.on('click', '.revoke button', function (e) {
e.preventDefault();
var button = jQuery(e.target);
@@ -108,8 +139,8 @@ jQuery(function () {
});
form.find('.search input').on('input', function () {
- refreshResults();
+ beginSearch();
});
- refreshResults();
+ beginSearch();
});
commit acd22f7f5da97724a4ab172923f4b913b8a31cfc
Author: Shawn M Moore <shawn at bestpractical.com>
Date: Tue Mar 7 16:37:47 2017 +0000
Handle successful error responses from server
e.g. unable to find user
diff --git a/static/js/rights-debugger.js b/static/js/rights-debugger.js
index ab408db..e3f03f0 100644
--- a/static/js/rights-debugger.js
+++ b/static/js/rights-debugger.js
@@ -36,6 +36,12 @@ jQuery(function () {
button.after(loading.clone());
};
+ var displayError = function (message) {
+ form.removeClass('awaiting-first-result').removeClass('continuing-load').addClass('error');
+ display.empty();
+ display.text('Error: ' + message);
+ }
+
var requestPage;
requestPage = function (search, continueAfter) {
search.continueAfter = continueAfter;
@@ -45,6 +51,11 @@ jQuery(function () {
data: search,
timeout: 30000, /* 30 seconds */
success: function (response) {
+ if (response.error) {
+ displayError(response.error);
+ return;
+ }
+
form.removeClass('error');
var items = response.results;
@@ -82,9 +93,7 @@ jQuery(function () {
return;
}
- form.removeClass('awaiting-first-result').removeClass('continuing-load').addClass('error');
- display.empty();
- display.text('Error: ' + xhr.statusText);
+ displayError(xhr.statusText);
}
});
};
commit 8200ecb7a18d503a25fcf68f3cf552c9aafd4611
Author: Shawn M Moore <shawn at bestpractical.com>
Date: Tue Mar 7 16:51:18 2017 +0000
Factor out a ->CurrentUser
diff --git a/lib/RT/Extension/RightsDebugger.pm b/lib/RT/Extension/RightsDebugger.pm
index bab43c2..ea1afe3 100644
--- a/lib/RT/Extension/RightsDebugger.pm
+++ b/lib/RT/Extension/RightsDebugger.pm
@@ -11,6 +11,10 @@ RT->AddJavaScript("handlebars-4.0.6.min.js");
$RT::Interface::Web::WHITELISTED_COMPONENT_ARGS{'/Admin/RightsDebugger/index.html'} = ['Principal', 'Object', 'Right'];
+sub CurrentUser {
+ return $HTML::Mason::Commands::session{CurrentUser};
+}
+
sub _EscapeHTML {
my $s = shift;
RT::Interface::Web::EscapeHTML(\$s);
@@ -71,7 +75,7 @@ sub Search {
my @results;
my %search;
- my $ACL = RT::ACL->new($HTML::Mason::Commands::session{CurrentUser});
+ my $ACL = RT::ACL->new($self->CurrentUser);
my $has_search = 0;
commit e968b96855948b646e38fa9408b88f6b99bd8a27
Author: Shawn M Moore <shawn at bestpractical.com>
Date: Tue Mar 7 16:51:42 2017 +0000
Inspecting a single principal and its group memberships
When you specify e.g. user:root, it will also look up all groups root is in
for ACLs. This is part of the answer to the question "why does this specific
user have this right on this record?"
diff --git a/lib/RT/Extension/RightsDebugger.pm b/lib/RT/Extension/RightsDebugger.pm
index ea1afe3..8f884a9 100644
--- a/lib/RT/Extension/RightsDebugger.pm
+++ b/lib/RT/Extension/RightsDebugger.pm
@@ -63,6 +63,35 @@ sub _HighlightSerializedForSearch {
return;
}
+sub _PrincipalForSpec {
+ my $self = shift;
+ my $type = shift;
+ my $identifier = shift;
+
+ if ($type =~ /^g/i) {
+ my $group = RT::Group->new($self->CurrentUser);
+ if ( $identifier =~ /^\d+$/ ) {
+ $group->LoadByCols(
+ id => $identifier,
+ );
+ } else {
+ $group->LoadByCols(
+ Domain => 'UserDefined',
+ Name => $identifier,
+ );
+ }
+
+ return $group->PrincipalObj if $group->Id;
+ }
+ else {
+ my $user = RT::User->new($self->CurrentUser);
+ $user->Load($identifier);
+ return $user->PrincipalObj if $user->Id;
+ }
+
+ return undef;
+}
+
sub Search {
my $self = shift;
my %args = (
@@ -78,6 +107,53 @@ sub Search {
my $ACL = RT::ACL->new($self->CurrentUser);
my $has_search = 0;
+ my %use_regex_search_for = (
+ principal => 1,
+ object => 1,
+ );
+
+ if ($args{principal}) {
+ for my $word (split ' ', $args{principal}) {
+ if (my ($type, $identifier) = $word =~ m{
+ ^
+ (u|user|g|group)
+ [:#]
+ (\S+)
+ $
+ }xi) {
+ my $principal = $self->_PrincipalForSpec($type, $identifier);
+ if (!$principal) {
+ return { error => 'Unable to find row' };
+ }
+
+ $has_search = 1;
+ $use_regex_search_for{principal} = 0;
+
+ my $principal_alias = $ACL->Join(
+ ALIAS1 => 'main',
+ FIELD1 => 'PrincipalId',
+ TABLE2 => 'Principals',
+ FIELD2 => 'id',
+ );
+ my $cgm_alias = $ACL->Join(
+ ALIAS1 => 'main',
+ FIELD1 => 'PrincipalId',
+ TABLE2 => 'CachedGroupMembers',
+ FIELD2 => 'GroupId',
+ );
+ $ACL->Limit(
+ ALIAS => $cgm_alias,
+ FIELD => 'MemberId',
+ VALUE => $principal->Id,
+ );
+ $ACL->Limit(
+ ALIAS => $cgm_alias,
+ FIELD => 'Disabled',
+ VALUE => 0,
+ );
+ }
+ }
+ }
if ($args{right}) {
$has_search = 1;
@@ -123,6 +199,8 @@ sub Search {
# this is hacky, but doing the searching in SQL is absolutely a nonstarter
KEY: for my $key (qw/principal object/) {
+ next KEY unless $use_regex_search_for{$key};
+
if (my $matchers = $search{$key}) {
my $record = $serialized->{$key};
for my $re (@$matchers) {
commit d0b8fb5be4ecc29f4dd955dcbc1da7a0118e5069
Author: Shawn M Moore <shawn at bestpractical.com>
Date: Tue Mar 7 16:55:04 2017 +0000
Reuse principal and CGM joins
This also happens to fix a bug where "user:root user:Nobody" returned no
results, instead of the joined results for "user:root" OR "user:Nobody"
diff --git a/lib/RT/Extension/RightsDebugger.pm b/lib/RT/Extension/RightsDebugger.pm
index 8f884a9..cfc4f8e 100644
--- a/lib/RT/Extension/RightsDebugger.pm
+++ b/lib/RT/Extension/RightsDebugger.pm
@@ -113,6 +113,8 @@ sub Search {
);
if ($args{principal}) {
+ my ($principal_alias, $cgm_alias);
+
for my $word (split ' ', $args{principal}) {
if (my ($type, $identifier) = $word =~ m{
^
@@ -129,28 +131,32 @@ sub Search {
$has_search = 1;
$use_regex_search_for{principal} = 0;
- my $principal_alias = $ACL->Join(
+ $principal_alias ||= $ACL->Join(
ALIAS1 => 'main',
FIELD1 => 'PrincipalId',
TABLE2 => 'Principals',
FIELD2 => 'id',
);
- my $cgm_alias = $ACL->Join(
- ALIAS1 => 'main',
- FIELD1 => 'PrincipalId',
- TABLE2 => 'CachedGroupMembers',
- FIELD2 => 'GroupId',
- );
+
+ if (!$cgm_alias) {
+ $cgm_alias = $ACL->Join(
+ ALIAS1 => 'main',
+ FIELD1 => 'PrincipalId',
+ TABLE2 => 'CachedGroupMembers',
+ FIELD2 => 'GroupId',
+ );
+ $ACL->Limit(
+ ALIAS => $cgm_alias,
+ FIELD => 'Disabled',
+ VALUE => 0,
+ );
+ }
+
$ACL->Limit(
ALIAS => $cgm_alias,
FIELD => 'MemberId',
VALUE => $principal->Id,
);
- $ACL->Limit(
- ALIAS => $cgm_alias,
- FIELD => 'Disabled',
- VALUE => 0,
- );
}
}
}
commit 1418689efa49906ed713afb6dffafa2d4d35d195
Author: Shawn M Moore <shawn at bestpractical.com>
Date: Tue Mar 7 16:59:59 2017 +0000
Avoid caching
diff --git a/html/Helpers/RightsDebugger/Search b/html/Helpers/RightsDebugger/Search
index 9ce83e6..912689f 100644
--- a/html/Helpers/RightsDebugger/Search
+++ b/html/Helpers/RightsDebugger/Search
@@ -1,6 +1,7 @@
<%INIT>
my $results = RT::Extension::RightsDebugger->Search(%ARGS);
$r->content_type('application/json; charset=utf-8');
+RT::Interface::Web::CacheControlExpiresHeaders( Time => 'no-cache' );
$m->out(JSON($results));
$m->abort;
</%INIT>
commit b947d9b018c7aab88cccd55048911b82fd4a1312
Author: Shawn M Moore <shawn at bestpractical.com>
Date: Tue Mar 7 17:08:10 2017 +0000
Order by ACL ID
Now that we're joining we need to be extra careful to maintain a consistent
ordering so that chunked loading doesn't get lost in the woods
diff --git a/lib/RT/Extension/RightsDebugger.pm b/lib/RT/Extension/RightsDebugger.pm
index cfc4f8e..8c7809c 100644
--- a/lib/RT/Extension/RightsDebugger.pm
+++ b/lib/RT/Extension/RightsDebugger.pm
@@ -183,6 +183,12 @@ sub Search {
);
}
+ $ACL->OrderBy(
+ ALIAS => 'main',
+ FIELD => 'id',
+ ORDER => 'ASC',
+ );
+
$ACL->UnLimit unless $has_search;
$ACL->RowsPerPage(100);
commit 9eb1e283c8f45c089bc3ae314dd57a895dfc1f67
Author: Shawn M Moore <shawn at bestpractical.com>
Date: Tue Mar 7 17:30:08 2017 +0000
Display primary record under parent record
e.g. if you assign a right to Everyone then search for user:root, then
we display this last line here:
Everyone
System Group
Contains root
diff --git a/html/Admin/RightsDebugger/index.html b/html/Admin/RightsDebugger/index.html
index 7af2af6..8e8444d 100644
--- a/html/Admin/RightsDebugger/index.html
+++ b/html/Admin/RightsDebugger/index.html
@@ -29,6 +29,12 @@
(disabled)
{{/if}}
</span>
+
+ {{#each primary_records}}
+ <span class="primary">
+ Contains {{this.label}}
+ </span>
+ {{/each}}
</span>
</script>
diff --git a/lib/RT/Extension/RightsDebugger.pm b/lib/RT/Extension/RightsDebugger.pm
index 8c7809c..6f4e4a5 100644
--- a/lib/RT/Extension/RightsDebugger.pm
+++ b/lib/RT/Extension/RightsDebugger.pm
@@ -111,6 +111,10 @@ sub Search {
principal => 1,
object => 1,
);
+ my %primary_records = (
+ principal => [],
+ object => [],
+ );
if ($args{principal}) {
my ($principal_alias, $cgm_alias);
@@ -131,6 +135,8 @@ sub Search {
$has_search = 1;
$use_regex_search_for{principal} = 0;
+ push @{ $primary_records{principal} }, $principal;
+
$principal_alias ||= $ACL->Join(
ALIAS1 => 'main',
FIELD1 => 'PrincipalId',
@@ -207,10 +213,11 @@ sub Search {
ACE: while (my $ACE = $ACL->Next) {
$continueAfter = $ACE->Id;
- my $serialized = $self->SerializeACE($ACE);
+ my $serialized = $self->SerializeACE($ACE, \%primary_records);
- # this is hacky, but doing the searching in SQL is absolutely a nonstarter
KEY: for my $key (qw/principal object/) {
+ # filtering on the serialized record is hacky, but doing the
+ # searching in SQL is absolutely a nonstarter
next KEY unless $use_regex_search_for{$key};
if (my $matchers = $search{$key}) {
@@ -245,10 +252,11 @@ sub Search {
sub SerializeACE {
my $self = shift;
my $ACE = shift;
+ my $primary_records = shift;
return {
- principal => $self->SerializeRecord($ACE->PrincipalObj),
- object => $self->SerializeRecord($ACE->Object),
+ principal => $self->SerializeRecord($ACE->PrincipalObj, $primary_records->{principal}),
+ object => $self->SerializeRecord($ACE->Object, $primary_records->{object}),
right => $ACE->RightName,
ace => { id => $ACE->Id },
disable_revoke => $self->DisableRevoke($ACE),
@@ -275,9 +283,39 @@ sub DisableRevoke {
return 0;
}
+sub IsRecordDescendent {
+ my $self = shift;
+ my $parent = shift;
+ my $child = shift;
+
+ if ($parent->isa('RT::Group')) {
+ if ($child->isa('RT::Group') || $child->isa('RT::User') || $child->isa('RT::Principal')) {
+ return $parent->HasMember($child->id);
+ }
+ }
+
+ return 0;
+}
+
+sub SerializePrimaryRecords {
+ my $self = shift;
+ my $record = shift;
+ my $primary_records = shift;
+
+ my %seen;
+
+ return [
+ map { $self->SerializeRecord($_) }
+ grep { $self->IsRecordDescendent($record, $_) }
+ grep { !$seen{ref($_) . '-' . $_->id}++ }
+ @$primary_records
+ ];
+}
+
sub SerializeRecord {
my $self = shift;
my $record = shift;
+ my $primary_records = shift;
if ($record->isa('RT::Principal')) {
$record = $record->Object;
@@ -300,12 +338,13 @@ sub SerializeRecord {
}
return {
- class => ref($record),
- id => $record->id,
- label => $self->LabelForRecord($record),
- detail => $self->DetailForRecord($record),
- url => $self->URLForRecord($record),
- disabled => $self->DisabledForRecord($record) ? JSON::true : JSON::false,
+ class => ref($record),
+ id => $record->id,
+ label => $self->LabelForRecord($record),
+ detail => $self->DetailForRecord($record),
+ url => $self->URLForRecord($record),
+ disabled => $self->DisabledForRecord($record) ? JSON::true : JSON::false,
+ primary_records => $self->SerializePrimaryRecords($record, $primary_records),
};
}
diff --git a/static/css/rights-debugger.css b/static/css/rights-debugger.css
index 07c7fb7..0afae25 100644
--- a/static/css/rights-debugger.css
+++ b/static/css/rights-debugger.css
@@ -67,3 +67,8 @@
text-decoration: line-through;
}
+#rights-debugger .results .result .primary {
+ font-size: 80%;
+ font-style: italic;
+ display: block;
+}
commit 7a39182e93d16bd2374d20f43bbb6b649e61135d
Author: Shawn M Moore <shawn at bestpractical.com>
Date: Tue Mar 7 17:43:29 2017 +0000
Avoid multi-principal/multi-object matching
It's looking to be intractable to re-construct the relationships after the CGM
SQL query for displaying "Contains $user" or "Contains $group"
diff --git a/html/Admin/RightsDebugger/index.html b/html/Admin/RightsDebugger/index.html
index 8e8444d..12f694e 100644
--- a/html/Admin/RightsDebugger/index.html
+++ b/html/Admin/RightsDebugger/index.html
@@ -30,11 +30,11 @@
{{/if}}
</span>
- {{#each primary_records}}
+ {{#if primary_record}}
<span class="primary">
- Contains {{this.label}}
+ Contains {{primary_record.label}}
</span>
- {{/each}}
+ {{/if}}
</span>
</script>
diff --git a/lib/RT/Extension/RightsDebugger.pm b/lib/RT/Extension/RightsDebugger.pm
index 6f4e4a5..36a0196 100644
--- a/lib/RT/Extension/RightsDebugger.pm
+++ b/lib/RT/Extension/RightsDebugger.pm
@@ -112,58 +112,55 @@ sub Search {
object => 1,
);
my %primary_records = (
- principal => [],
- object => [],
+ principal => undef,
+ object => undef,
);
if ($args{principal}) {
- my ($principal_alias, $cgm_alias);
-
- for my $word (split ' ', $args{principal}) {
- if (my ($type, $identifier) = $word =~ m{
- ^
- (u|user|g|group)
- [:#]
- (\S+)
- $
- }xi) {
- my $principal = $self->_PrincipalForSpec($type, $identifier);
- if (!$principal) {
- return { error => 'Unable to find row' };
- }
+ if (my ($type, $identifier) = $args{principal} =~ m{
+ ^
+ \s*
+ (u|user|g|group)
+ \s*
+ [:#]
+ \s*
+ (.+?)
+ \s*
+ $
+ }xi) {
+ my $principal = $self->_PrincipalForSpec($type, $identifier);
+ if (!$principal) {
+ return { error => 'Unable to find row' };
+ }
- $has_search = 1;
- $use_regex_search_for{principal} = 0;
-
- push @{ $primary_records{principal} }, $principal;
-
- $principal_alias ||= $ACL->Join(
- ALIAS1 => 'main',
- FIELD1 => 'PrincipalId',
- TABLE2 => 'Principals',
- FIELD2 => 'id',
- );
-
- if (!$cgm_alias) {
- $cgm_alias = $ACL->Join(
- ALIAS1 => 'main',
- FIELD1 => 'PrincipalId',
- TABLE2 => 'CachedGroupMembers',
- FIELD2 => 'GroupId',
- );
- $ACL->Limit(
- ALIAS => $cgm_alias,
- FIELD => 'Disabled',
- VALUE => 0,
- );
- }
+ $has_search = 1;
+ $use_regex_search_for{principal} = 0;
- $ACL->Limit(
- ALIAS => $cgm_alias,
- FIELD => 'MemberId',
- VALUE => $principal->Id,
- );
- }
+ $primary_records{principal} = $principal;
+
+ my $principal_alias = $ACL->Join(
+ ALIAS1 => 'main',
+ FIELD1 => 'PrincipalId',
+ TABLE2 => 'Principals',
+ FIELD2 => 'id',
+ );
+
+ my $cgm_alias = $ACL->Join(
+ ALIAS1 => 'main',
+ FIELD1 => 'PrincipalId',
+ TABLE2 => 'CachedGroupMembers',
+ FIELD2 => 'GroupId',
+ );
+ $ACL->Limit(
+ ALIAS => $cgm_alias,
+ FIELD => 'Disabled',
+ VALUE => 0,
+ );
+ $ACL->Limit(
+ ALIAS => $cgm_alias,
+ FIELD => 'MemberId',
+ VALUE => $principal->Id,
+ );
}
}
@@ -202,7 +199,7 @@ sub Search {
for my $key (qw/principal object right/) {
if (my $search = $args{$key}) {
my @matchers;
- for my $word (split ' ', $search) {
+ for my $word ($key eq 'right' ? (split ' ', $search) : $search) {
push @matchers, qr/\Q$word\E/i;
}
$search{$key} = \@matchers;
@@ -283,39 +280,12 @@ sub DisableRevoke {
return 0;
}
-sub IsRecordDescendent {
- my $self = shift;
- my $parent = shift;
- my $child = shift;
-
- if ($parent->isa('RT::Group')) {
- if ($child->isa('RT::Group') || $child->isa('RT::User') || $child->isa('RT::Principal')) {
- return $parent->HasMember($child->id);
- }
- }
-
- return 0;
-}
-
-sub SerializePrimaryRecords {
- my $self = shift;
- my $record = shift;
- my $primary_records = shift;
-
- my %seen;
-
- return [
- map { $self->SerializeRecord($_) }
- grep { $self->IsRecordDescendent($record, $_) }
- grep { !$seen{ref($_) . '-' . $_->id}++ }
- @$primary_records
- ];
-}
-
sub SerializeRecord {
my $self = shift;
my $record = shift;
- my $primary_records = shift;
+ my $primary_record = shift;
+
+ return undef unless $record;
if ($record->isa('RT::Principal')) {
$record = $record->Object;
@@ -337,15 +307,17 @@ sub SerializeRecord {
}
}
- return {
+ my $serialized = {
class => ref($record),
id => $record->id,
label => $self->LabelForRecord($record),
detail => $self->DetailForRecord($record),
url => $self->URLForRecord($record),
disabled => $self->DisabledForRecord($record) ? JSON::true : JSON::false,
- primary_records => $self->SerializePrimaryRecords($record, $primary_records),
+ primary_record => $self->SerializeRecord($primary_record),
};
+
+ return $serialized;
}
sub LabelForRecord {
commit 6fb27e1bd63ad72cbfaf7dc5fc065cc48d9e1545
Author: Shawn M Moore <shawn at bestpractical.com>
Date: Tue Mar 7 17:47:54 2017 +0000
No need to special case these role group names
RoleClass handles them all just fine
diff --git a/lib/RT/Extension/RightsDebugger.pm b/lib/RT/Extension/RightsDebugger.pm
index 36a0196..50492b4 100644
--- a/lib/RT/Extension/RightsDebugger.pm
+++ b/lib/RT/Extension/RightsDebugger.pm
@@ -355,16 +355,7 @@ sub DetailForRecord {
# like RT::Group->SelfDescription but without the redundant labels
if ($record->isa('RT::Group')) {
- if ($record->Domain eq 'RT::System-Role') {
- return "System Role";
- }
- elsif ($record->Domain eq 'RT::Queue-Role') {
- return "Queue Role";
- }
- elsif ($record->Domain eq 'RT::Ticket-Role') {
- return "Ticket Role";
- }
- elsif ($record->RoleClass) {
+ if ($record->RoleClass) {
my $class = $record->RoleClass;
$class =~ s/^RT:://i;
return "$class Role";
commit 78eacafa3fc8d203d089693eca2ba23cdb389819
Author: Shawn M Moore <shawn at bestpractical.com>
Date: Tue Mar 7 17:48:53 2017 +0000
word -> term
diff --git a/lib/RT/Extension/RightsDebugger.pm b/lib/RT/Extension/RightsDebugger.pm
index 50492b4..12c8922 100644
--- a/lib/RT/Extension/RightsDebugger.pm
+++ b/lib/RT/Extension/RightsDebugger.pm
@@ -41,7 +41,7 @@ sub _HighlightSerializedForSearch {
my $serialized = shift;
my $search = shift;
- # highlight matching words
+ # highlight matching terms
$serialized->{right_highlighted} = _HighlightTerm($serialized->{right}, join '|', @{ $search->{right} || [] });
for my $key (qw/principal object/) {
@@ -166,11 +166,11 @@ sub Search {
if ($args{right}) {
$has_search = 1;
- for my $word (split ' ', $args{right}) {
+ for my $term (split ' ', $args{right}) {
$ACL->Limit(
FIELD => 'RightName',
OPERATOR => 'LIKE',
- VALUE => $word,
+ VALUE => $term,
CASESENSITIVE => 0,
ENTRYAGGREGATOR => 'OR',
);
@@ -199,8 +199,8 @@ sub Search {
for my $key (qw/principal object right/) {
if (my $search = $args{$key}) {
my @matchers;
- for my $word ($key eq 'right' ? (split ' ', $search) : $search) {
- push @matchers, qr/\Q$word\E/i;
+ for my $term ($key eq 'right' ? (split ' ', $search) : $search) {
+ push @matchers, qr/\Q$term\E/i;
}
$search{$key} = \@matchers;
}
-----------------------------------------------------------------------
More information about the Bps-public-commit
mailing list