diff --git a/Bugzilla/API/V1/BugGraph.pm b/Bugzilla/API/V1/BugGraph.pm new file mode 100644 index 0000000000..c75793736f --- /dev/null +++ b/Bugzilla/API/V1/BugGraph.pm @@ -0,0 +1,121 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# This Source Code Form is "Incompatible With Secondary Licenses", as +# defined by the Mozilla Public License, v. 2.0. + +package Bugzilla::API::V1::BugGraph; + +use 5.10.1; +use Mojo::Base qw( Mojolicious::Controller ); + +use List::Util qw(any none); +use PerlX::Maybe; +use Try::Tiny; + +use Bugzilla; +use Bugzilla::Constants; +use Bugzilla::Error; +use Bugzilla::Logging; +use Bugzilla::Report::Graph; + +sub setup_routes { + my ($class, $r) = @_; + $r->get('/bug/:id/graph')->to('V1::BugGraph#graph'); +} + +sub graph { + my ($self, $params) = @_; + my $user = $self->bugzilla->login; + + Bugzilla->usage_mode(USAGE_MODE_MOJO_REST); + + my $bug_id = $self->param('id'); + my $relationship = $self->param('relationship') || 'dependencies'; + my $depth = $self->param('depth') || 3; + my $ids_only = $self->param('ids_only') ? 1 : 0; + my $show_resolved = $self->param('show_resolved') ? 1 : 0; + + if ($bug_id !~ /^\d+$/) { + ThrowCodeError('param_invalid', + {function => 'bug//graph', param => 'bug_id'}); + } + + if ($depth !~ /^\d+$/ || ($depth > 9 || $depth < 1)) { + ThrowCodeError('param_invalid', + {function => 'bug//graph', param => 'depth'}); + } + + my %relationships = ( + dependencies => ['dependson,blocked', 'blocked,dependson'], + duplicates => ['dupe,dupe_of', 'dupe_of,dupe'], + regressions => ['regresses,regressed_by', 'regressed_by,regresses'], + ); + + if (none { $relationship eq $_ } keys %relationships) { + ThrowCodeError('param_invalid', + {function => 'bug//graph ', param => 'relationship'}); + } + + my $result = {}; + try { + foreach my $fields (@{$relationships{$relationship}}) { + Bugzilla->switch_to_shadow_db(); + + my ($source, $sink) = split /,/, $fields; + + my $report = Bugzilla::Report::Graph->new( + bug_id => $bug_id, + table => $relationship, + source => $source, + sink => $sink, + depth => $depth, + ); + + # Remove any secure bugs that user cannot see + $report->prune_graph(sub { $user->visible_bugs($_[0]) }); + + # If we do not want resolved bugs (default) then filter those + # by passing in reference to the subroutine for filtering out + # resolved bugs + if (!$show_resolved) { + $report->prune_graph(sub { $self->_prune_resolved($_[0]) }); + } + + if (!$ids_only) { + my $bugs = Bugzilla::Bug->new_from_list([$report->graph->vertices]); + foreach my $bug (@$bugs) { + $report->graph->set_vertex_attributes($bug->id, $bug->to_hash); + } + } + + $result->{$source} = $report->tree; + } + } + catch { + FATAL($_); + $result = {exception => 'Internal Error', request_id => $self->req->request_id}; + }; + + return $self->render(json => $result); +} + +# This method takes a set of bugs and using a single SQL statement, +# removes any bugs from the list which have a non-empty resolution (unresolved) +sub _prune_resolved { + my ($self, $bugs) = @_; + my $dbh = Bugzilla->dbh; + + return $bugs if !$bugs->size; + + my $placeholders = join ',', split //, '?' x $bugs->size; + my $query + = "SELECT bug_id FROM bugs WHERE (resolution IS NULL OR resolution = '') AND bug_id IN ($placeholders)"; + my $filtered_bugs + = Bugzilla->dbh->selectcol_arrayref($query, undef, $bugs->elements); + + return $filtered_bugs; +} + +1; diff --git a/Bugzilla/Report/Graph.pm b/Bugzilla/Report/Graph.pm new file mode 100644 index 0000000000..11a2dded98 --- /dev/null +++ b/Bugzilla/Report/Graph.pm @@ -0,0 +1,121 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# This Source Code Form is "Incompatible With Secondary Licenses", as +# defined by the Mozilla Public License, v. 2.0. + +package Bugzilla::Report::Graph; +use 5.10.1; +use Moo; + +use Graph::Directed; +use Graph::Traversal::DFS; +use PerlX::Maybe 'maybe'; +use Type::Utils qw(class_type); +use Types::Standard qw(Bool Enum Int Str ArrayRef); +use Set::Object qw(set); + +use Bugzilla; +use Bugzilla::Logging; +use Bugzilla::Types qw(DB); + +our $valid_tables = [qw(dependencies duplicates regressions)]; +our $valid_fields = [qw(blocked dependson dupe dupe_of regresses regressed_by)]; + +has bug_id => (is => 'ro', isa => Int, required => 1); +has table => + (is => 'ro', isa => Enum $valid_tables, default => 'dependencies',); +has depth => (is => 'ro', isa => Int, default => 3); +has source => (is => 'ro', isa => Enum $valid_fields, default => 'dependson',); +has sink => (is => 'ro', isa => Enum $valid_fields, default => 'blocked',); +has limit => (is => 'ro', isa => Int, default => 10_000); +has paths => (is => 'lazy', isa => ArrayRef [ArrayRef]); +has graph => (is => 'lazy', isa => class_type({class => 'Graph'})); +has query => (is => 'lazy', isa => Str); + +# Run the query that will list of the paths from the parent bug +# down to the last child in the tree +sub _build_paths { + my ($self) = @_; + return Bugzilla->dbh->selectall_arrayref($self->query, undef, $self->bug_id); +} + +# Builds a new directed graph +sub _build_graph { + my ($self) = @_; + my $paths = $self->paths; + my $graph = Graph::Directed->new; + + foreach my $path (@$paths) { + pop @$path until defined $path->[-2]; + $graph->add_path(@$path); + } + + return $graph; +} + +sub _build_query { + my ($self) = @_; + my $table = $self->table; + my $alias = substr $table, 0, 1; + my $depth = $self->depth; + my $source = $self->source; + my $sink = $self->sink; + my $limit = $self->limit; + + # WITH RECURSIVE is available in MySQL 8.x and newer as + # well as recent versions of PostgreSQL and SQLite. + my $query = "WITH RECURSIVE RelationshipTree AS ( + SELECT t.$source, t.$sink, 1 AS depth FROM $table t WHERE t.$source = ? + UNION ALL + SELECT t.$source, t.$sink, rt.depth + 1 AS depth FROM $table t + JOIN RelationshipTree rt ON t.$source = rt.$sink WHERE rt.depth <= $depth LIMIT $limit) + SELECT rt.$source, rt.$sink FROM RelationshipTree rt"; + + return $query; +} + +# Using a callback filter being passed in, remove any unwanted vertices +# in the graph such as secure bugs if the user cannot see them. Then +# remove any unreachable vertices as well. +sub prune_graph { + my ($self, $filter) = @_; + + my $all_vertices = set($self->graph->vertices); + my $filtered_vertices = set(@{$filter->($all_vertices)}); + my $pruned_vertices = $all_vertices - $filtered_vertices; + $self->graph->delete_vertices($pruned_vertices->members); + + # Finally remove any vertices that are now unreachable + my $reachable_vertices + = set($self->bug_id, $self->graph->all_reachable($self->bug_id)); + my $unreachable_vertices = $filtered_vertices - $reachable_vertices; + $self->graph->delete_vertices($unreachable_vertices->members); + + return $pruned_vertices + $unreachable_vertices; +} + +# Generates the final tree stucture based on the directed graph +sub tree { + my ($self) = @_; + my $graph = $self->graph; + + my %nodes = map { $_ => {maybe bug => $graph->get_vertex_attributes($_)} } + $graph->vertices; + + my $search = Graph::Traversal::DFS->new( + $graph, + start => $self->bug_id, + tree_edge => sub { + my ($u, $v) = @_; + $nodes{$u}{$v} = $nodes{$v}; + } + ); + $search->dfs; + + return $nodes{$self->bug_id} || {}; +} + + +1; diff --git a/Bugzilla/Types.pm b/Bugzilla/Types.pm index d464bb890f..64c1a6567a 100644 --- a/Bugzilla/Types.pm +++ b/Bugzilla/Types.pm @@ -12,10 +12,11 @@ use strict; use warnings; use Type::Library -base, - -declare => qw( Bug User Group Attachment Comment JSONBool URI URL Task ); + -declare => qw( DB Bug User Group Attachment Comment JSONBool URI URL Task ); use Type::Utils -all; use Types::Standard -types; +class_type DB, {class => 'Bugzilla::DB'}; class_type Bug, {class => 'Bugzilla::Bug'}; class_type User, {class => 'Bugzilla::User'}; class_type Group, {class => 'Bugzilla::Group'}; diff --git a/Dockerfile b/Dockerfile index c0d8374b2c..45791cca62 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM mozillabteam/bmo-perl-slim:20231023.1 +FROM mozillabteam/bmo-perl-slim:20231024.1 ENV DEBIAN_FRONTEND noninteractive diff --git a/Makefile.PL b/Makefile.PL index 137e1c4b6d..ff8d31bdac 100755 --- a/Makefile.PL +++ b/Makefile.PL @@ -64,6 +64,7 @@ my %requires = ( 'Email::Sender' => 0, 'FFI::Platypus' => 0, 'Future' => '0.34', + 'Graph' => 0, 'HTML::Escape' => '1.10', 'IO::Async' => '0.71', 'IPC::System::Simple' => 0, @@ -94,6 +95,7 @@ my %requires = ( 'Role::Tiny' => '2.000003', 'Scope::Guard' => '0.21', 'Sereal' => '4.004', + 'Set::Object' => 0, 'Sub::Quote' => '2.005000', 'Template' => '2.24', 'Text::CSV_XS' => '1.26', diff --git a/cpanfile b/cpanfile index baa44a3648..2a4260248e 100644 --- a/cpanfile +++ b/cpanfile @@ -54,6 +54,8 @@ requires 'GD', '1.20'; requires 'GD::Barcode', '== 1.15'; requires 'GD::Graph'; requires 'GD::Text'; +requires 'Graph'; +requires 'Graph::D3'; requires 'HTML::Escape', '1.10'; requires 'HTML::Parser', '3.67'; requires 'HTML::Scrubber'; @@ -102,6 +104,7 @@ requires 'SOAP::Lite', '0.712'; requires 'SQL::Tokenizer'; requires 'Scope::Guard', '0.21'; requires 'Sereal', '4.004'; +requires 'Set::Object'; requires 'Sub::Quote', '2.005000'; requires 'Sys::Syslog'; requires 'Template', '2.24'; diff --git a/cpanfile.snapshot b/cpanfile.snapshot index e73f3ba916..e4c784ed3a 100644 --- a/cpanfile.snapshot +++ b/cpanfile.snapshot @@ -3258,6 +3258,45 @@ DISTRIBUTIONS MooseX::Clone 0.04 MooseX::Storage 0.23 Test::More 0 + Graph-0.9727 + pathname: E/ET/ETJ/Graph-0.9727.tar.gz + provides: + Graph 0.9727 + Graph::AdjacencyMap undef + Graph::AdjacencyMap::Light undef + Graph::AdjacencyMatrix undef + Graph::Attribute undef + Graph::BitMatrix undef + Graph::Directed undef + Graph::MSTHeapElem undef + Graph::Matrix undef + Graph::SPTHeapElem undef + Graph::TransitiveClosure undef + Graph::TransitiveClosure::Matrix undef + Graph::Traversal undef + Graph::Traversal::BFS undef + Graph::Traversal::DFS undef + Graph::Undirected undef + Graph::UnionFind undef + requirements: + ExtUtils::MakeMaker 0 + Heap 0.80 + List::Util 1.45 + Safe 0 + Scalar::Util 0 + Set::Object 1.40 + Storable 2.05 + perl 5.006 + Graph-D3-0.03 + pathname: S/SH/SHOHEIK/Graph-D3-0.03.tar.gz + provides: + Graph::D3 0.03 + requirements: + Graph 0.94 + JSON 2.53 + Module::Build::Tiny 0.016 + Moo 1.001000 + perl v5.6.0 Graphics-Color-0.31 pathname: G/GP/GPHAT/Graphics-Color-0.31.tar.gz provides: @@ -3597,6 +3636,23 @@ DISTRIBUTIONS perl 5.006 strict 0 warnings 0 + Heap-0.80 + pathname: J/JM/JMM/Heap-0.80.tar.gz + provides: + Heap 0.80 + Heap::Binary 0.80 + Heap::Binomial 0.80 + Heap::Elem 0.80 + Heap::Elem::Num 0.80 + Heap::Elem::NumRev 0.80 + Heap::Elem::Ref 0.80 + Heap::Elem::RefRev 0.80 + Heap::Elem::Str 0.80 + Heap::Elem::StrRev 0.80 + Heap::Fibonacci 0.80 + requirements: + ExtUtils::MakeMaker 0 + Test::Simple 0.45 IO-Async-0.802 pathname: P/PE/PEVANS/IO-Async-0.802.tar.gz provides: @@ -3949,32 +4005,6 @@ DISTRIBUTIONS IPC::Cmd 0 XSLoader 0.22 base 0 - List-SomeUtils-0.59 - pathname: D/DR/DROLSKY/List-SomeUtils-0.59.tar.gz - provides: - List::SomeUtils 0.59 - List::SomeUtils::PP 0.59 - requirements: - Carp 0 - Exporter 0 - ExtUtils::MakeMaker 0 - List::SomeUtils::XS 0.54 - List::Util 0 - Module::Implementation 0.04 - Text::ParseWords 0 - perl 5.006 - strict 0 - vars 0 - warnings 0 - List-SomeUtils-XS-0.58 - pathname: D/DR/DROLSKY/List-SomeUtils-XS-0.58.tar.gz - provides: - List::SomeUtils::XS 0.58 - requirements: - ExtUtils::MakeMaker 0 - XSLoader 0 - strict 0 - warnings 0 Log-Dispatch-2.71 pathname: D/DR/DROLSKY/Log-Dispatch-2.71.tar.gz provides: @@ -5684,20 +5714,6 @@ DISTRIBUTIONS base 0 strict 0 warnings 0 - PPIx-Utils-0.003 - pathname: D/DB/DBOOK/PPIx-Utils-0.003.tar.gz - provides: - PPIx::Utils 0.003 - PPIx::Utils::Classification 0.003 - PPIx::Utils::Language 0.003 - PPIx::Utils::Traversal 0.003 - requirements: - B::Keywords 1.09 - Exporter 0 - ExtUtils::MakeMaker 0 - PPI 1.250 - Scalar::Util 0 - perl 5.006 Package-DeprecationManager-0.18 pathname: D/DR/DROLSKY/Package-DeprecationManager-0.18.tar.gz provides: @@ -5865,249 +5881,257 @@ DISTRIBUTIONS strict 0 warnings 0 warnings::register 0 - Perl-Critic-1.152 - pathname: P/PE/PETDANCE/Perl-Critic-1.152.tar.gz - provides: - Perl::Critic 1.152 - Perl::Critic::Annotation 1.152 - Perl::Critic::Command 1.152 - Perl::Critic::Config 1.152 - Perl::Critic::Document 1.152 - Perl::Critic::Exception 1.152 - Perl::Critic::Exception::AggregateConfiguration 1.152 - Perl::Critic::Exception::Configuration 1.152 - Perl::Critic::Exception::Configuration::Generic 1.152 - Perl::Critic::Exception::Configuration::NonExistentPolicy 1.152 - Perl::Critic::Exception::Configuration::Option 1.152 - Perl::Critic::Exception::Configuration::Option::Global 1.152 - Perl::Critic::Exception::Configuration::Option::Global::ExtraParameter 1.152 - Perl::Critic::Exception::Configuration::Option::Global::ParameterValue 1.152 - Perl::Critic::Exception::Configuration::Option::Policy 1.152 - Perl::Critic::Exception::Configuration::Option::Policy::ExtraParameter 1.152 - Perl::Critic::Exception::Configuration::Option::Policy::ParameterValue 1.152 - Perl::Critic::Exception::Fatal 1.152 - Perl::Critic::Exception::Fatal::Generic 1.152 - Perl::Critic::Exception::Fatal::Internal 1.152 - Perl::Critic::Exception::Fatal::PolicyDefinition 1.152 - Perl::Critic::Exception::IO 1.152 - Perl::Critic::Exception::Parse 1.152 - Perl::Critic::OptionsProcessor 1.152 - Perl::Critic::Policy 1.152 - Perl::Critic::Policy::BuiltinFunctions::ProhibitBooleanGrep 1.152 - Perl::Critic::Policy::BuiltinFunctions::ProhibitComplexMappings 1.152 - Perl::Critic::Policy::BuiltinFunctions::ProhibitLvalueSubstr 1.152 - Perl::Critic::Policy::BuiltinFunctions::ProhibitReverseSortBlock 1.152 - Perl::Critic::Policy::BuiltinFunctions::ProhibitShiftRef 1.152 - Perl::Critic::Policy::BuiltinFunctions::ProhibitSleepViaSelect 1.152 - Perl::Critic::Policy::BuiltinFunctions::ProhibitStringyEval 1.152 - Perl::Critic::Policy::BuiltinFunctions::ProhibitStringySplit 1.152 - Perl::Critic::Policy::BuiltinFunctions::ProhibitUniversalCan 1.152 - Perl::Critic::Policy::BuiltinFunctions::ProhibitUniversalIsa 1.152 - Perl::Critic::Policy::BuiltinFunctions::ProhibitUselessTopic 1.152 - Perl::Critic::Policy::BuiltinFunctions::ProhibitVoidGrep 1.152 - Perl::Critic::Policy::BuiltinFunctions::ProhibitVoidMap 1.152 - Perl::Critic::Policy::BuiltinFunctions::RequireBlockGrep 1.152 - Perl::Critic::Policy::BuiltinFunctions::RequireBlockMap 1.152 - Perl::Critic::Policy::BuiltinFunctions::RequireGlobFunction 1.152 - Perl::Critic::Policy::BuiltinFunctions::RequireSimpleSortBlock 1.152 - Perl::Critic::Policy::ClassHierarchies::ProhibitAutoloading 1.152 - Perl::Critic::Policy::ClassHierarchies::ProhibitExplicitISA 1.152 - Perl::Critic::Policy::ClassHierarchies::ProhibitOneArgBless 1.152 - Perl::Critic::Policy::CodeLayout::ProhibitHardTabs 1.152 - Perl::Critic::Policy::CodeLayout::ProhibitParensWithBuiltins 1.152 - Perl::Critic::Policy::CodeLayout::ProhibitQuotedWordLists 1.152 - Perl::Critic::Policy::CodeLayout::ProhibitTrailingWhitespace 1.152 - Perl::Critic::Policy::CodeLayout::RequireConsistentNewlines 1.152 - Perl::Critic::Policy::CodeLayout::RequireTidyCode 1.152 - Perl::Critic::Policy::CodeLayout::RequireTrailingCommas 1.152 - Perl::Critic::Policy::ControlStructures::ProhibitCStyleForLoops 1.152 - Perl::Critic::Policy::ControlStructures::ProhibitCascadingIfElse 1.152 - Perl::Critic::Policy::ControlStructures::ProhibitDeepNests 1.152 - Perl::Critic::Policy::ControlStructures::ProhibitLabelsWithSpecialBlockNames 1.152 - Perl::Critic::Policy::ControlStructures::ProhibitMutatingListFunctions 1.152 - Perl::Critic::Policy::ControlStructures::ProhibitNegativeExpressionsInUnlessAndUntilConditions 1.152 - Perl::Critic::Policy::ControlStructures::ProhibitPostfixControls 1.152 - Perl::Critic::Policy::ControlStructures::ProhibitUnlessBlocks 1.152 - Perl::Critic::Policy::ControlStructures::ProhibitUnreachableCode 1.152 - Perl::Critic::Policy::ControlStructures::ProhibitUntilBlocks 1.152 - Perl::Critic::Policy::ControlStructures::ProhibitYadaOperator 1.152 - Perl::Critic::Policy::Documentation::PodSpelling 1.152 - Perl::Critic::Policy::Documentation::RequirePackageMatchesPodName 1.152 - Perl::Critic::Policy::Documentation::RequirePodAtEnd 1.152 - Perl::Critic::Policy::Documentation::RequirePodSections 1.152 - Perl::Critic::Policy::ErrorHandling::RequireCarping 1.152 - Perl::Critic::Policy::ErrorHandling::RequireCheckingReturnValueOfEval 1.152 - Perl::Critic::Policy::InputOutput::ProhibitBacktickOperators 1.152 - Perl::Critic::Policy::InputOutput::ProhibitBarewordDirHandles 1.152 - Perl::Critic::Policy::InputOutput::ProhibitBarewordFileHandles 1.152 - Perl::Critic::Policy::InputOutput::ProhibitExplicitStdin 1.152 - Perl::Critic::Policy::InputOutput::ProhibitInteractiveTest 1.152 - Perl::Critic::Policy::InputOutput::ProhibitJoinedReadline 1.152 - Perl::Critic::Policy::InputOutput::ProhibitOneArgSelect 1.152 - Perl::Critic::Policy::InputOutput::ProhibitReadlineInForLoop 1.152 - Perl::Critic::Policy::InputOutput::ProhibitTwoArgOpen 1.152 - Perl::Critic::Policy::InputOutput::RequireBracedFileHandleWithPrint 1.152 - Perl::Critic::Policy::InputOutput::RequireBriefOpen 1.152 - Perl::Critic::Policy::InputOutput::RequireCheckedClose 1.152 - Perl::Critic::Policy::InputOutput::RequireCheckedOpen 1.152 - Perl::Critic::Policy::InputOutput::RequireCheckedSyscalls 1.152 - Perl::Critic::Policy::InputOutput::RequireEncodingWithUTF8Layer 1.152 - Perl::Critic::Policy::Miscellanea::ProhibitFormats 1.152 - Perl::Critic::Policy::Miscellanea::ProhibitTies 1.152 - Perl::Critic::Policy::Miscellanea::ProhibitUnrestrictedNoCritic 1.152 - Perl::Critic::Policy::Miscellanea::ProhibitUselessNoCritic 1.152 - Perl::Critic::Policy::Modules::ProhibitAutomaticExportation 1.152 - Perl::Critic::Policy::Modules::ProhibitConditionalUseStatements 1.152 - Perl::Critic::Policy::Modules::ProhibitEvilModules 1.152 - Perl::Critic::Policy::Modules::ProhibitExcessMainComplexity 1.152 - Perl::Critic::Policy::Modules::ProhibitMultiplePackages 1.152 - Perl::Critic::Policy::Modules::RequireBarewordIncludes 1.152 - Perl::Critic::Policy::Modules::RequireEndWithOne 1.152 - Perl::Critic::Policy::Modules::RequireExplicitPackage 1.152 - Perl::Critic::Policy::Modules::RequireFilenameMatchesPackage 1.152 - Perl::Critic::Policy::Modules::RequireNoMatchVarsWithUseEnglish 1.152 - Perl::Critic::Policy::Modules::RequireVersionVar 1.152 - Perl::Critic::Policy::NamingConventions::Capitalization 1.152 - Perl::Critic::Policy::NamingConventions::ProhibitAmbiguousNames 1.152 - Perl::Critic::Policy::Objects::ProhibitIndirectSyntax 1.152 - Perl::Critic::Policy::References::ProhibitDoubleSigils 1.152 - Perl::Critic::Policy::RegularExpressions::ProhibitCaptureWithoutTest 1.152 - Perl::Critic::Policy::RegularExpressions::ProhibitComplexRegexes 1.152 - Perl::Critic::Policy::RegularExpressions::ProhibitEnumeratedClasses 1.152 - Perl::Critic::Policy::RegularExpressions::ProhibitEscapedMetacharacters 1.152 - Perl::Critic::Policy::RegularExpressions::ProhibitFixedStringMatches 1.152 - Perl::Critic::Policy::RegularExpressions::ProhibitSingleCharAlternation 1.152 - Perl::Critic::Policy::RegularExpressions::ProhibitUnusedCapture 1.152 - Perl::Critic::Policy::RegularExpressions::ProhibitUnusualDelimiters 1.152 - Perl::Critic::Policy::RegularExpressions::ProhibitUselessTopic 1.152 - Perl::Critic::Policy::RegularExpressions::RequireBracesForMultiline 1.152 - Perl::Critic::Policy::RegularExpressions::RequireDotMatchAnything 1.152 - Perl::Critic::Policy::RegularExpressions::RequireExtendedFormatting 1.152 - Perl::Critic::Policy::RegularExpressions::RequireLineBoundaryMatching 1.152 - Perl::Critic::Policy::Subroutines::ProhibitAmpersandSigils 1.152 - Perl::Critic::Policy::Subroutines::ProhibitBuiltinHomonyms 1.152 - Perl::Critic::Policy::Subroutines::ProhibitExcessComplexity 1.152 - Perl::Critic::Policy::Subroutines::ProhibitExplicitReturnUndef 1.152 - Perl::Critic::Policy::Subroutines::ProhibitManyArgs 1.152 - Perl::Critic::Policy::Subroutines::ProhibitNestedSubs 1.152 - Perl::Critic::Policy::Subroutines::ProhibitReturnSort 1.152 - Perl::Critic::Policy::Subroutines::ProhibitSubroutinePrototypes 1.152 - Perl::Critic::Policy::Subroutines::ProhibitUnusedPrivateSubroutines 1.152 - Perl::Critic::Policy::Subroutines::ProtectPrivateSubs 1.152 - Perl::Critic::Policy::Subroutines::RequireArgUnpacking 1.152 - Perl::Critic::Policy::Subroutines::RequireFinalReturn 1.152 - Perl::Critic::Policy::TestingAndDebugging::ProhibitNoStrict 1.152 - Perl::Critic::Policy::TestingAndDebugging::ProhibitNoWarnings 1.152 - Perl::Critic::Policy::TestingAndDebugging::ProhibitProlongedStrictureOverride 1.152 - Perl::Critic::Policy::TestingAndDebugging::RequireTestLabels 1.152 - Perl::Critic::Policy::TestingAndDebugging::RequireUseStrict 1.152 - Perl::Critic::Policy::TestingAndDebugging::RequireUseWarnings 1.152 - Perl::Critic::Policy::ValuesAndExpressions::ProhibitCommaSeparatedStatements 1.152 - Perl::Critic::Policy::ValuesAndExpressions::ProhibitComplexVersion 1.152 - Perl::Critic::Policy::ValuesAndExpressions::ProhibitConstantPragma 1.152 - Perl::Critic::Policy::ValuesAndExpressions::ProhibitEmptyQuotes 1.152 - Perl::Critic::Policy::ValuesAndExpressions::ProhibitEscapedCharacters 1.152 - Perl::Critic::Policy::ValuesAndExpressions::ProhibitImplicitNewlines 1.152 - Perl::Critic::Policy::ValuesAndExpressions::ProhibitInterpolationOfLiterals 1.152 - Perl::Critic::Policy::ValuesAndExpressions::ProhibitLeadingZeros 1.152 - Perl::Critic::Policy::ValuesAndExpressions::ProhibitLongChainsOfMethodCalls 1.152 - Perl::Critic::Policy::ValuesAndExpressions::ProhibitMagicNumbers 1.152 - Perl::Critic::Policy::ValuesAndExpressions::ProhibitMismatchedOperators 1.152 - Perl::Critic::Policy::ValuesAndExpressions::ProhibitMixedBooleanOperators 1.152 - Perl::Critic::Policy::ValuesAndExpressions::ProhibitNoisyQuotes 1.152 - Perl::Critic::Policy::ValuesAndExpressions::ProhibitQuotesAsQuotelikeOperatorDelimiters 1.152 - Perl::Critic::Policy::ValuesAndExpressions::ProhibitSpecialLiteralHeredocTerminator 1.152 - Perl::Critic::Policy::ValuesAndExpressions::ProhibitVersionStrings 1.152 - Perl::Critic::Policy::ValuesAndExpressions::RequireConstantVersion 1.152 - Perl::Critic::Policy::ValuesAndExpressions::RequireInterpolationOfMetachars 1.152 - Perl::Critic::Policy::ValuesAndExpressions::RequireNumberSeparators 1.152 - Perl::Critic::Policy::ValuesAndExpressions::RequireQuotedHeredocTerminator 1.152 - Perl::Critic::Policy::ValuesAndExpressions::RequireUpperCaseHeredocTerminator 1.152 - Perl::Critic::Policy::Variables::ProhibitAugmentedAssignmentInDeclaration 1.152 - Perl::Critic::Policy::Variables::ProhibitConditionalDeclarations 1.152 - Perl::Critic::Policy::Variables::ProhibitEvilVariables 1.152 - Perl::Critic::Policy::Variables::ProhibitLocalVars 1.152 - Perl::Critic::Policy::Variables::ProhibitMatchVars 1.152 - Perl::Critic::Policy::Variables::ProhibitPackageVars 1.152 - Perl::Critic::Policy::Variables::ProhibitPerl4PackageNames 1.152 - Perl::Critic::Policy::Variables::ProhibitPunctuationVars 1.152 - Perl::Critic::Policy::Variables::ProhibitReusedNames 1.152 - Perl::Critic::Policy::Variables::ProhibitUnusedVariables 1.152 - Perl::Critic::Policy::Variables::ProtectPrivateVars 1.152 - Perl::Critic::Policy::Variables::RequireInitializationForLocalVars 1.152 - Perl::Critic::Policy::Variables::RequireLexicalLoopIterators 1.152 - Perl::Critic::Policy::Variables::RequireLocalizedPunctuationVars 1.152 - Perl::Critic::Policy::Variables::RequireNegativeIndices 1.152 - Perl::Critic::PolicyConfig 1.152 - Perl::Critic::PolicyFactory 1.152 - Perl::Critic::PolicyListing 1.152 - Perl::Critic::PolicyParameter 1.152 - Perl::Critic::PolicyParameter::Behavior 1.152 - Perl::Critic::PolicyParameter::Behavior::Boolean 1.152 - Perl::Critic::PolicyParameter::Behavior::Enumeration 1.152 - Perl::Critic::PolicyParameter::Behavior::Integer 1.152 - Perl::Critic::PolicyParameter::Behavior::String 1.152 - Perl::Critic::PolicyParameter::Behavior::StringList 1.152 - Perl::Critic::ProfilePrototype 1.152 - Perl::Critic::Statistics 1.152 - Perl::Critic::TestUtils 1.152 - Perl::Critic::Theme 1.152 - Perl::Critic::ThemeListing 1.152 - Perl::Critic::UserProfile 1.152 - Perl::Critic::Utils 1.152 - Perl::Critic::Utils::Constants 1.152 - Perl::Critic::Utils::McCabe 1.152 - Perl::Critic::Utils::POD 1.152 - Perl::Critic::Utils::PPI 1.152 - Perl::Critic::Utils::Perl 1.152 - Perl::Critic::Violation 1.152 - Test::Perl::Critic::Policy 1.152 - requirements: - B::Keywords 1.23 + Perl-Critic-1.132 + pathname: P/PE/PETDANCE/Perl-Critic-1.132.tar.gz + provides: + Perl::Critic 1.132 + Perl::Critic::Annotation 1.132 + Perl::Critic::Command 1.132 + Perl::Critic::Config 1.132 + Perl::Critic::Document 1.132 + Perl::Critic::Exception 1.132 + Perl::Critic::Exception::AggregateConfiguration 1.132 + Perl::Critic::Exception::Configuration 1.132 + Perl::Critic::Exception::Configuration::Generic 1.132 + Perl::Critic::Exception::Configuration::NonExistentPolicy 1.132 + Perl::Critic::Exception::Configuration::Option 1.132 + Perl::Critic::Exception::Configuration::Option::Global 1.132 + Perl::Critic::Exception::Configuration::Option::Global::ExtraParameter 1.132 + Perl::Critic::Exception::Configuration::Option::Global::ParameterValue 1.132 + Perl::Critic::Exception::Configuration::Option::Policy 1.132 + Perl::Critic::Exception::Configuration::Option::Policy::ExtraParameter 1.132 + Perl::Critic::Exception::Configuration::Option::Policy::ParameterValue 1.132 + Perl::Critic::Exception::Fatal 1.132 + Perl::Critic::Exception::Fatal::Generic 1.132 + Perl::Critic::Exception::Fatal::Internal 1.132 + Perl::Critic::Exception::Fatal::PolicyDefinition 1.132 + Perl::Critic::Exception::IO 1.132 + Perl::Critic::Exception::Parse 1.132 + Perl::Critic::OptionsProcessor 1.132 + Perl::Critic::Policy 1.132 + Perl::Critic::Policy::BuiltinFunctions::ProhibitBooleanGrep 1.132 + Perl::Critic::Policy::BuiltinFunctions::ProhibitComplexMappings 1.132 + Perl::Critic::Policy::BuiltinFunctions::ProhibitLvalueSubstr 1.132 + Perl::Critic::Policy::BuiltinFunctions::ProhibitReverseSortBlock 1.132 + Perl::Critic::Policy::BuiltinFunctions::ProhibitSleepViaSelect 1.132 + Perl::Critic::Policy::BuiltinFunctions::ProhibitStringyEval 1.132 + Perl::Critic::Policy::BuiltinFunctions::ProhibitStringySplit 1.132 + Perl::Critic::Policy::BuiltinFunctions::ProhibitUniversalCan 1.132 + Perl::Critic::Policy::BuiltinFunctions::ProhibitUniversalIsa 1.132 + Perl::Critic::Policy::BuiltinFunctions::ProhibitUselessTopic 1.132 + Perl::Critic::Policy::BuiltinFunctions::ProhibitVoidGrep 1.132 + Perl::Critic::Policy::BuiltinFunctions::ProhibitVoidMap 1.132 + Perl::Critic::Policy::BuiltinFunctions::RequireBlockGrep 1.132 + Perl::Critic::Policy::BuiltinFunctions::RequireBlockMap 1.132 + Perl::Critic::Policy::BuiltinFunctions::RequireGlobFunction 1.132 + Perl::Critic::Policy::BuiltinFunctions::RequireSimpleSortBlock 1.132 + Perl::Critic::Policy::ClassHierarchies::ProhibitAutoloading 1.132 + Perl::Critic::Policy::ClassHierarchies::ProhibitExplicitISA 1.132 + Perl::Critic::Policy::ClassHierarchies::ProhibitOneArgBless 1.132 + Perl::Critic::Policy::CodeLayout::ProhibitHardTabs 1.132 + Perl::Critic::Policy::CodeLayout::ProhibitParensWithBuiltins 1.132 + Perl::Critic::Policy::CodeLayout::ProhibitQuotedWordLists 1.132 + Perl::Critic::Policy::CodeLayout::ProhibitTrailingWhitespace 1.132 + Perl::Critic::Policy::CodeLayout::RequireConsistentNewlines 1.132 + Perl::Critic::Policy::CodeLayout::RequireTidyCode 1.132 + Perl::Critic::Policy::CodeLayout::RequireTrailingCommas 1.132 + Perl::Critic::Policy::ControlStructures::ProhibitCStyleForLoops 1.132 + Perl::Critic::Policy::ControlStructures::ProhibitCascadingIfElse 1.132 + Perl::Critic::Policy::ControlStructures::ProhibitDeepNests 1.132 + Perl::Critic::Policy::ControlStructures::ProhibitLabelsWithSpecialBlockNames 1.132 + Perl::Critic::Policy::ControlStructures::ProhibitMutatingListFunctions 1.132 + Perl::Critic::Policy::ControlStructures::ProhibitNegativeExpressionsInUnlessAndUntilConditions 1.132 + Perl::Critic::Policy::ControlStructures::ProhibitPostfixControls 1.132 + Perl::Critic::Policy::ControlStructures::ProhibitUnlessBlocks 1.132 + Perl::Critic::Policy::ControlStructures::ProhibitUnreachableCode 1.132 + Perl::Critic::Policy::ControlStructures::ProhibitUntilBlocks 1.132 + Perl::Critic::Policy::ControlStructures::ProhibitYadaOperator 1.132 + Perl::Critic::Policy::Documentation::PodSpelling 1.132 + Perl::Critic::Policy::Documentation::RequirePackageMatchesPodName 1.132 + Perl::Critic::Policy::Documentation::RequirePodAtEnd 1.132 + Perl::Critic::Policy::Documentation::RequirePodLinksIncludeText 1.132 + Perl::Critic::Policy::Documentation::RequirePodSections 1.132 + Perl::Critic::Policy::ErrorHandling::RequireCarping 1.132 + Perl::Critic::Policy::ErrorHandling::RequireCheckingReturnValueOfEval 1.132 + Perl::Critic::Policy::InputOutput::ProhibitBacktickOperators 1.132 + Perl::Critic::Policy::InputOutput::ProhibitBarewordFileHandles 1.132 + Perl::Critic::Policy::InputOutput::ProhibitExplicitStdin 1.132 + Perl::Critic::Policy::InputOutput::ProhibitInteractiveTest 1.132 + Perl::Critic::Policy::InputOutput::ProhibitJoinedReadline 1.132 + Perl::Critic::Policy::InputOutput::ProhibitOneArgSelect 1.132 + Perl::Critic::Policy::InputOutput::ProhibitReadlineInForLoop 1.132 + Perl::Critic::Policy::InputOutput::ProhibitTwoArgOpen 1.132 + Perl::Critic::Policy::InputOutput::RequireBracedFileHandleWithPrint 1.132 + Perl::Critic::Policy::InputOutput::RequireBriefOpen 1.132 + Perl::Critic::Policy::InputOutput::RequireCheckedClose 1.132 + Perl::Critic::Policy::InputOutput::RequireCheckedOpen 1.132 + Perl::Critic::Policy::InputOutput::RequireCheckedSyscalls 1.132 + Perl::Critic::Policy::InputOutput::RequireEncodingWithUTF8Layer 1.132 + Perl::Critic::Policy::Miscellanea::ProhibitFormats 1.132 + Perl::Critic::Policy::Miscellanea::ProhibitTies 1.132 + Perl::Critic::Policy::Miscellanea::ProhibitUnrestrictedNoCritic 1.132 + Perl::Critic::Policy::Miscellanea::ProhibitUselessNoCritic 1.132 + Perl::Critic::Policy::Modules::ProhibitAutomaticExportation 1.132 + Perl::Critic::Policy::Modules::ProhibitConditionalUseStatements 1.132 + Perl::Critic::Policy::Modules::ProhibitEvilModules 1.132 + Perl::Critic::Policy::Modules::ProhibitExcessMainComplexity 1.132 + Perl::Critic::Policy::Modules::ProhibitMultiplePackages 1.132 + Perl::Critic::Policy::Modules::RequireBarewordIncludes 1.132 + Perl::Critic::Policy::Modules::RequireEndWithOne 1.132 + Perl::Critic::Policy::Modules::RequireExplicitPackage 1.132 + Perl::Critic::Policy::Modules::RequireFilenameMatchesPackage 1.132 + Perl::Critic::Policy::Modules::RequireNoMatchVarsWithUseEnglish 1.132 + Perl::Critic::Policy::Modules::RequireVersionVar 1.132 + Perl::Critic::Policy::NamingConventions::Capitalization 1.132 + Perl::Critic::Policy::NamingConventions::ProhibitAmbiguousNames 1.132 + Perl::Critic::Policy::Objects::ProhibitIndirectSyntax 1.132 + Perl::Critic::Policy::References::ProhibitDoubleSigils 1.132 + Perl::Critic::Policy::RegularExpressions::ProhibitCaptureWithoutTest 1.132 + Perl::Critic::Policy::RegularExpressions::ProhibitComplexRegexes 1.132 + Perl::Critic::Policy::RegularExpressions::ProhibitEnumeratedClasses 1.132 + Perl::Critic::Policy::RegularExpressions::ProhibitEscapedMetacharacters 1.132 + Perl::Critic::Policy::RegularExpressions::ProhibitFixedStringMatches 1.132 + Perl::Critic::Policy::RegularExpressions::ProhibitSingleCharAlternation 1.132 + Perl::Critic::Policy::RegularExpressions::ProhibitUnusedCapture 1.132 + Perl::Critic::Policy::RegularExpressions::ProhibitUnusualDelimiters 1.132 + Perl::Critic::Policy::RegularExpressions::ProhibitUselessTopic 1.132 + Perl::Critic::Policy::RegularExpressions::RequireBracesForMultiline 1.132 + Perl::Critic::Policy::RegularExpressions::RequireDotMatchAnything 1.132 + Perl::Critic::Policy::RegularExpressions::RequireExtendedFormatting 1.132 + Perl::Critic::Policy::RegularExpressions::RequireLineBoundaryMatching 1.132 + Perl::Critic::Policy::Subroutines::ProhibitAmpersandSigils 1.132 + Perl::Critic::Policy::Subroutines::ProhibitBuiltinHomonyms 1.132 + Perl::Critic::Policy::Subroutines::ProhibitExcessComplexity 1.132 + Perl::Critic::Policy::Subroutines::ProhibitExplicitReturnUndef 1.132 + Perl::Critic::Policy::Subroutines::ProhibitManyArgs 1.132 + Perl::Critic::Policy::Subroutines::ProhibitNestedSubs 1.132 + Perl::Critic::Policy::Subroutines::ProhibitReturnSort 1.132 + Perl::Critic::Policy::Subroutines::ProhibitSubroutinePrototypes 1.132 + Perl::Critic::Policy::Subroutines::ProhibitUnusedPrivateSubroutines 1.132 + Perl::Critic::Policy::Subroutines::ProtectPrivateSubs 1.132 + Perl::Critic::Policy::Subroutines::RequireArgUnpacking 1.132 + Perl::Critic::Policy::Subroutines::RequireFinalReturn 1.132 + Perl::Critic::Policy::TestingAndDebugging::ProhibitNoStrict 1.132 + Perl::Critic::Policy::TestingAndDebugging::ProhibitNoWarnings 1.132 + Perl::Critic::Policy::TestingAndDebugging::ProhibitProlongedStrictureOverride 1.132 + Perl::Critic::Policy::TestingAndDebugging::RequireTestLabels 1.132 + Perl::Critic::Policy::TestingAndDebugging::RequireUseStrict 1.132 + Perl::Critic::Policy::TestingAndDebugging::RequireUseWarnings 1.132 + Perl::Critic::Policy::ValuesAndExpressions::ProhibitCommaSeparatedStatements 1.132 + Perl::Critic::Policy::ValuesAndExpressions::ProhibitComplexVersion 1.132 + Perl::Critic::Policy::ValuesAndExpressions::ProhibitConstantPragma 1.132 + Perl::Critic::Policy::ValuesAndExpressions::ProhibitEmptyQuotes 1.132 + Perl::Critic::Policy::ValuesAndExpressions::ProhibitEscapedCharacters 1.132 + Perl::Critic::Policy::ValuesAndExpressions::ProhibitImplicitNewlines 1.132 + Perl::Critic::Policy::ValuesAndExpressions::ProhibitInterpolationOfLiterals 1.132 + Perl::Critic::Policy::ValuesAndExpressions::ProhibitLeadingZeros 1.132 + Perl::Critic::Policy::ValuesAndExpressions::ProhibitLongChainsOfMethodCalls 1.132 + Perl::Critic::Policy::ValuesAndExpressions::ProhibitMagicNumbers 1.132 + Perl::Critic::Policy::ValuesAndExpressions::ProhibitMismatchedOperators 1.132 + Perl::Critic::Policy::ValuesAndExpressions::ProhibitMixedBooleanOperators 1.132 + Perl::Critic::Policy::ValuesAndExpressions::ProhibitNoisyQuotes 1.132 + Perl::Critic::Policy::ValuesAndExpressions::ProhibitQuotesAsQuotelikeOperatorDelimiters 1.132 + Perl::Critic::Policy::ValuesAndExpressions::ProhibitSpecialLiteralHeredocTerminator 1.132 + Perl::Critic::Policy::ValuesAndExpressions::ProhibitVersionStrings 1.132 + Perl::Critic::Policy::ValuesAndExpressions::RequireConstantVersion 1.132 + Perl::Critic::Policy::ValuesAndExpressions::RequireInterpolationOfMetachars 1.132 + Perl::Critic::Policy::ValuesAndExpressions::RequireNumberSeparators 1.132 + Perl::Critic::Policy::ValuesAndExpressions::RequireQuotedHeredocTerminator 1.132 + Perl::Critic::Policy::ValuesAndExpressions::RequireUpperCaseHeredocTerminator 1.132 + Perl::Critic::Policy::Variables::ProhibitAugmentedAssignmentInDeclaration 1.132 + Perl::Critic::Policy::Variables::ProhibitConditionalDeclarations 1.132 + Perl::Critic::Policy::Variables::ProhibitEvilVariables 1.132 + Perl::Critic::Policy::Variables::ProhibitLocalVars 1.132 + Perl::Critic::Policy::Variables::ProhibitMatchVars 1.132 + Perl::Critic::Policy::Variables::ProhibitPackageVars 1.132 + Perl::Critic::Policy::Variables::ProhibitPerl4PackageNames 1.132 + Perl::Critic::Policy::Variables::ProhibitPunctuationVars 1.132 + Perl::Critic::Policy::Variables::ProhibitReusedNames 1.132 + Perl::Critic::Policy::Variables::ProhibitUnusedVariables 1.132 + Perl::Critic::Policy::Variables::ProtectPrivateVars 1.132 + Perl::Critic::Policy::Variables::RequireInitializationForLocalVars 1.132 + Perl::Critic::Policy::Variables::RequireLexicalLoopIterators 1.132 + Perl::Critic::Policy::Variables::RequireLocalizedPunctuationVars 1.132 + Perl::Critic::Policy::Variables::RequireNegativeIndices 1.132 + Perl::Critic::PolicyConfig 1.132 + Perl::Critic::PolicyFactory 1.132 + Perl::Critic::PolicyListing 1.132 + Perl::Critic::PolicyParameter 1.132 + Perl::Critic::PolicyParameter::Behavior 1.132 + Perl::Critic::PolicyParameter::Behavior::Boolean 1.132 + Perl::Critic::PolicyParameter::Behavior::Enumeration 1.132 + Perl::Critic::PolicyParameter::Behavior::Integer 1.132 + Perl::Critic::PolicyParameter::Behavior::String 1.132 + Perl::Critic::PolicyParameter::Behavior::StringList 1.132 + Perl::Critic::ProfilePrototype 1.132 + Perl::Critic::Statistics 1.132 + Perl::Critic::TestUtils 1.132 + Perl::Critic::Theme 1.132 + Perl::Critic::ThemeListing 1.132 + Perl::Critic::UserProfile 1.132 + Perl::Critic::Utils 1.132 + Perl::Critic::Utils::Constants 1.132 + Perl::Critic::Utils::DataConversion 1.132 + Perl::Critic::Utils::McCabe 1.132 + Perl::Critic::Utils::POD 1.132 + Perl::Critic::Utils::POD::ParseInteriorSequence 1.132 + Perl::Critic::Utils::PPI 1.132 + Perl::Critic::Utils::Perl 1.132 + Perl::Critic::Violation 1.132 + Test::Perl::Critic::Policy 1.132 + requirements: + B::Keywords 1.05 Carp 0 Config::Tiny 2 English 0 Exception::Class 1.23 Exporter 5.63 + Fatal 0 File::Basename 0 File::Find 0 + File::HomeDir 0 File::Path 0 File::Spec 0 File::Spec::Unix 0 File::Temp 0 File::Which 0 Getopt::Long 0 - List::SomeUtils 0.55 + IO::String 0 + IPC::Open2 1 + List::MoreUtils 0.19 List::Util 0 - Module::Build 0.4204 + Module::Build 0.4024 Module::Pluggable 3.1 - PPI 1.277 - PPI::Document 1.277 - PPI::Document::File 1.277 - PPI::Node 1.277 - PPI::Token::Quote::Single 1.277 - PPI::Token::Whitespace 1.277 + PPI 1.224 + PPI::Document 1.224 + PPI::Document::File 1.224 + PPI::Node 1.224 + PPI::Token::Quote::Single 1.224 + PPI::Token::Whitespace 1.224 PPIx::QuoteLike 0 PPIx::Regexp 0.027 - PPIx::Regexp::Util 0.068 - PPIx::Utils::Traversal 0.003 + PPIx::Utilities::Node 1.001 + PPIx::Utilities::Statement 1.001 Perl::Tidy 0 + Pod::Parser 0 Pod::PlainText 0 Pod::Select 0 Pod::Spell 1 Pod::Usage 0 Readonly 2 Scalar::Util 0 - String::Format 1.18 + String::Format 1.13 + Task::Weaken 0 Term::ANSIColor 2.02 Test::Builder 0.92 + Test::Deep 0 + Test::More 0 Text::ParseWords 3 base 0 charnames 0 lib 0 overload 0 - parent 0 - perl 5.010001 + perl 5.006001 strict 0 version 0.77 warnings 0 @@ -6910,6 +6934,17 @@ DISTRIBUTIONS ExtUtils::MakeMaker 0 Test::More 0 Time::Local 0 + Set-Object-1.42 + pathname: R/RU/RURBAN/Set-Object-1.42.tar.gz + provides: + Set::Object 1.42 + Set::Object::TieArray 1.42 + Set::Object::TieHash 1.42 + Set::Object::Weak undef + requirements: + ExtUtils::MakeMaker 0 + Scalar::Util 0 + Test::More 0 Sort-Versions-1.62 pathname: N/NE/NEILB/Sort-Versions-1.62.tar.gz provides: diff --git a/docs/en/rst/api/core/v1/bug.rst b/docs/en/rst/api/core/v1/bug.rst index f13563fd67..b75516eee4 100644 --- a/docs/en/rst/api/core/v1/bug.rst +++ b/docs/en/rst/api/core/v1/bug.rst @@ -1122,6 +1122,136 @@ This method can throw all the same errors as :ref:`rest_single_bug`, plus: * 604 (Summary Required) You did not specify a value for the "summary" argument. + +.. _rest_graph: + +Graph +----- + +Return a graph of bug relationships such as dependencies, regressions, and duplicates. +By default, resolved bugs are not returned but can be if needed. The bug ID provided +will be the root node of the graph. + +**Request** + +To return a graph of dependencies (default) for a given bug. Each bug in the tree will +include basic information about the bug such as status, summary, etc. + +.. code-block:: text + + GET /rest/bug/1156/graph + +To return a simple graph that only includes the bug IDs, then pass ``ids_only=1``. +Note, this will be faster for very large graphs. + +.. code-block:: text + + GET /rest/bug/1156/graph?ids_only=1 + +The default is the dependencies graph. To return the graph for other types, pass the +``relationship={dependencies,regressions,duplicates}`` parameter. + +.. code-block:: text + + GET /rest/bug/1156/graph?relationship=regressions + +============ ======= ================================================================ +name type description +============ ======= ================================================================ +ids_only boolean Do not return simple bug data with each bug ID in the tree. + Default: False +depth int Limit the depth of the graph. + Default: 3, Max: 9 +show_resolved boolean Enable if you want to also see RESOLVED bugs in the graph. + Default: False +relationship string One of "dependencies", "duplicates", or "regressions". + Default: "dependencies" +============ ======= ================================================================ + +**Response** + +The default return object will be an object with two trees based on the type of +relationship selected. For dependencies, it will be ``blocked`` and ``dependson``. +For regressions, it will be be ``regresses`` and ``regressed_by``. And for duplicates, +it will be ``dupe_of`` and ``dupe``. + +.. code-block:: js + + { + "blocked": { + "2": { + "3": { + "bug": { + "alias": null, + "id": 3, + "is_confirmed": 1, + "op_sys": "Unspecified", + "platform": "Unspecified", + "priority": "--", + "resolution": "", + "severity": "normal", + "status": "NEW", + "summary": "Another new test bug", + "target_milestone": "---", + "type": "defect", + "url": "", + "version": "unspecified", + "whiteboard": "" + } + }, + "bug": { + "alias": null, + "id": 2, + "is_confirmed": 1, + "op_sys": "Unspecified", + "platform": "Unspecified", + "priority": "--", + "resolution": "", + "severity": "normal", + "status": "NEW", + "summary": "this is a new test bug", + "target_milestone": "---", + "type": "defect", + "url": "", + "version": "unspecified", + "whiteboard": "" + } + }, + "bug": { + "alias": null, + "id": 1, + "is_confirmed": 1, + "op_sys": "Unspecified", + "platform": "Unspecified", + "priority": "--", + "resolution": "", + "severity": "normal", + "status": "NEW", + "summary": "This is a new test bug", + "target_milestone": "---", + "type": "defect", + "url": "", + "version": "unspecified", + "whiteboard": "" + } + }, + "dependson": {} + } + +The following response, is what will happen if ``ids_only=1`` is passed. + +.. code-block:: js + + { + "blocked": { + "2": { + "3": {} + } + }, + "dependson": {} + } + + .. _rest_possible_duplicates: Possible Duplicates diff --git a/qa/config/generate_test_data.pl b/qa/config/generate_test_data.pl index b6158f43bf..e4a348873a 100644 --- a/qa/config/generate_test_data.pl +++ b/qa/config/generate_test_data.pl @@ -693,6 +693,13 @@ BEGIN CONTROLMAPMANDATORY, 0); }; +# Add QA-Selenium-TEST group also to Another Product +my $another_product = Bugzilla::Product->new({name => 'Another Product'}); +eval { + $sth->execute($created_group->id, $another_product->id, 0, CONTROLMAPSHOWN, + CONTROLMAPNA, 0); +}; + ########################################################################## # Create flag types ########################################################################## diff --git a/qa/t/rest_relationship_trees.t b/qa/t/rest_relationship_trees.t new file mode 100644 index 0000000000..0c79990309 --- /dev/null +++ b/qa/t/rest_relationship_trees.t @@ -0,0 +1,219 @@ +#!/usr/bin/env perl +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# This Source Code Form is "Incompatible With Secondary Licenses", as +# defined by the Mozilla Public License, v. 2.0. +use strict; +use warnings; +use 5.10.1; +use lib qw(lib ../../lib ../../local/lib/perl5); + +use Bugzilla; +use QA::Util qw(get_config); +use QA::Tests qw(create_bug_fields PRIVATE_BUG_USER); + +use Test::Mojo; +use Test::More; + +my $config = get_config(); +my $url = Bugzilla->localconfig->urlbase; +my $private_user_api_key = $config->{PRIVATE_BUG_USER . '_user_api_key'}; +my $unpriv_user_api_key = $config->{unprivileged_user_api_key}; + +# editbugs is needed to fill in dependencies on bug entry +my $editbugs_user_api_key = $config->{editbugs_user_api_key}; + +my $t = Test::Mojo->new(); + +# Allow 1 redirect max +$t->ua->max_redirects(1); + +### Section 1: Dependencies + +# Create first bug + +my $bug_data = create_bug_fields($config); +delete $bug_data->{cc}; # No unprivileged user is not added to the cc list +$bug_data->{summary} = 'This is a public test bug'; +$bug_data->{description} = 'This is a public test bug'; + +$t->post_ok($url + . 'rest/bug' => {'X-Bugzilla-API-Key' => $editbugs_user_api_key} => json => + $bug_data)->status_is(200)->json_has('/id'); + +my $depends_bug1_id = $t->tx->res->json->{id}; + +# Create second bug that depends on the first bug + +$bug_data->{depends_on} = [$depends_bug1_id]; + +$t->post_ok($url + . 'rest/bug' => {'X-Bugzilla-API-Key' => $editbugs_user_api_key} => json => + $bug_data)->status_is(200)->json_has('/id'); + +my $depends_bug2_id = $t->tx->res->json->{id}; + +# Create a third bug that depends on the second bug + +$bug_data->{depends_on} = [$depends_bug2_id]; + +$t->post_ok($url + . 'rest/bug' => {'X-Bugzilla-API-Key' => $editbugs_user_api_key} => json => + $bug_data)->status_is(200)->json_has('/id'); + +my $depends_bug3_id = $t->tx->res->json->{id}; + +# Load the dependency tree + +$t->get_ok($url + . "rest/bug/${depends_bug1_id}/graph?relationship=dependencies" => + {'X-Bugzilla-API-Key' => $editbugs_user_api_key})->status_is(200) + ->json_is("/dependson/$depends_bug2_id/$depends_bug3_id/bug/summary", + 'This is a public test bug')->json_has('/blocked'); + +# Only display bug ids and not load extra bug data. This could be faster +# with really large relationship trees. + +$t->get_ok($url + . "rest/bug/${depends_bug1_id}/graph?relationship=dependencies&ids_only=1" => + {'X-Bugzilla-API-Key' => $editbugs_user_api_key})->status_is(200) + ->json_has("/dependson/$depends_bug2_id/$depends_bug3_id") + ->json_hasnt("/dependson/$depends_bug2_id/$depends_bug3_id/bug") + ->json_has('/blocked'); + +# Update the bug to make private and make sure it is not visible in the tree +# by a user without proper permissions. + +my $update_data = { + summary => 'This is a private test bug', + groups => {add => ['QA-Selenium-TEST']} +}; + +$t->put_ok($url + . "rest/bug/$depends_bug3_id" => + {'X-Bugzilla-API-Key' => $private_user_api_key} => json => $update_data) + ->status_is(200); + +# Redisplay dependency tree and verify that the private bug is missing + +$t->get_ok($url + . "rest/bug/${depends_bug1_id}/graph?relationship=dependencies" => + {'X-Bugzilla-API-Key' => $unpriv_user_api_key})->status_is(200) + ->json_hasnt("/dependson/$depends_bug2_id/$depends_bug3_id"); + +### Section 2: Regressions + +# Create first bug + +$bug_data = create_bug_fields($config); +$bug_data->{summary} = 'This is a public test bug'; +$bug_data->{description} = 'This is a public test bug'; + +$t->post_ok($url + . 'rest/bug' => {'X-Bugzilla-API-Key' => $editbugs_user_api_key} => json => + $bug_data)->status_is(200)->json_has('/id'); + +my $regression_bug1_id = $t->tx->res->json->{id}; + +# Create second bug that regresses the first bug + +$bug_data->{regressed_by} = [$regression_bug1_id]; + +$t->post_ok($url + . 'rest/bug' => {'X-Bugzilla-API-Key' => $editbugs_user_api_key} => json => + $bug_data)->status_is(200)->json_has('/id'); + +my $regression_bug2_id = $t->tx->res->json->{id}; + +# Create a third bug that regresses the second bug + +$bug_data->{regressed_by} = [$regression_bug2_id]; + +$t->post_ok($url + . 'rest/bug' => {'X-Bugzilla-API-Key' => $editbugs_user_api_key} => json => + $bug_data)->status_is(200)->json_has('/id'); + +my $regression_bug3_id = $t->tx->res->json->{id}; + +# Load the regression tree + +$t->get_ok($url + . "rest/bug/${regression_bug1_id}/graph?relationship=regressions" => + {'X-Bugzilla-API-Key' => $editbugs_user_api_key})->status_is(200) + ->json_is("/regressed_by/$regression_bug2_id/$regression_bug3_id/bug/summary", + 'This is a public test bug')->json_has('/regresses'); + +# Only display bug ids and not load extra bug data. This could be faster +# with really large relationship trees. + +$t->get_ok($url + . "rest/bug/${regression_bug1_id}/graph?relationship=regressions&ids_only=1" + => {'X-Bugzilla-API-Key' => $editbugs_user_api_key})->status_is(200) + ->json_has("/regressed_by/$regression_bug2_id/$regression_bug3_id") + ->json_hasnt("/regressed_by/$regression_bug2_id/$regression_bug3_id/bug") + ->json_has('/regresses'); + +### Section 2: Duplicates + +# Create first bug + +$bug_data = create_bug_fields($config); +$bug_data->{summary} = 'This is a public test bug'; +$bug_data->{description} = 'This is a public test bug'; + +$t->post_ok($url + . 'rest/bug' => {'X-Bugzilla-API-Key' => $editbugs_user_api_key} => json => + $bug_data)->status_is(200)->json_has('/id'); + +my $dupe_bug1_id = $t->tx->res->json->{id}; + +# Create second bug that is duplicate of the first bug + +$t->post_ok($url + . 'rest/bug' => {'X-Bugzilla-API-Key' => $editbugs_user_api_key} => json => + $bug_data)->status_is(200)->json_has('/id'); + +my $dupe_bug2_id = $t->tx->res->json->{id}; + +$update_data = {dupe_of => $dupe_bug1_id}; + +$t->put_ok($url + . "rest/bug/$dupe_bug2_id" => + {'X-Bugzilla-API-Key' => $editbugs_user_api_key} => json => $update_data) + ->status_is(200); + +# Create a third bug that is duplicate of the second bug + +$t->post_ok($url + . 'rest/bug' => {'X-Bugzilla-API-Key' => $editbugs_user_api_key} => json => + $bug_data)->status_is(200)->json_has('/id'); + +my $dupe_bug3_id = $t->tx->res->json->{id}; + +$update_data = {dupe_of => $dupe_bug2_id}; + +$t->put_ok($url + . "rest/bug/$dupe_bug3_id" => + {'X-Bugzilla-API-Key' => $editbugs_user_api_key} => json => $update_data) + ->status_is(200); + +# Load the regression tree + +$t->get_ok($url + . "rest/bug/${dupe_bug1_id}/graph?relationship=duplicates&show_resolved=1" => + {'X-Bugzilla-API-Key' => $editbugs_user_api_key})->status_is(200) + ->json_is("/dupe_of/$dupe_bug2_id/$dupe_bug3_id/bug/summary", + 'This is a public test bug')->json_has('/dupe'); + +# Only display bug ids and not load extra bug data. This could be faster +# with really large relationship trees. + +$t->get_ok($url + . "rest/bug/${dupe_bug1_id}/graph?relationship=duplicates&show_resolved=1&ids_only=1" + => {'X-Bugzilla-API-Key' => $editbugs_user_api_key})->status_is(200) + ->json_has("/dupe_of/$dupe_bug2_id/$dupe_bug3_id") + ->json_hasnt("/dupe_of/$dupe_bug2_id/$dupe_bug3_id/bug")->json_has('/dupe'); + +done_testing(); diff --git a/t/report-graph.t b/t/report-graph.t new file mode 100644 index 0000000000..178e605274 --- /dev/null +++ b/t/report-graph.t @@ -0,0 +1,56 @@ +#!/usr/bin/env perl +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# This Source Code Form is "Incompatible With Secondary Licenses", as +# defined by the Mozilla Public License, v. 2.0. +use 5.10.1; +use strict; +use warnings; +use lib qw( . lib local/lib/perl5 ); + +use Test2::V0; +use Test2::Tools::Mock qw(mock); +use Bugzilla::Report::Graph; + +my $DB = mock 'Bugzilla::DB' => ( + add_constructor => [ + fake_new => 'hash' + ] +); + +my $dbh = Bugzilla::DB->fake_new; +my $report = Bugzilla::Report::Graph->new( + dbh => $dbh, + bug_id => 1, + paths => [ + [1, grep { ($_ % 3) == 0 } 1..42], + [map { 2**$_ } 0..10], + [ + 1, 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, + 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, + 89, 97, 101, 103, 107, 109, 113, 127, 131, 137, 139, 149, + 151, 157, 163, 167, 173, 179, 181, 191, 193, 197, 199 + ], + [1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144], + ] +); + +like($report->graph, qr/\b197-/, 'found link from 197'); +like($report->graph, qr/-197\b/, 'found link to 197'); +like($report->graph, qr/\b199\b/, 'found descendent of 197'); + +my $pruned = $report->prune_graph(sub { + my ($ids) = @_; + return [grep { $_ != 197 } @$ids] +}); +is([197, 199], [ @$pruned ], 'verify what was pruned'); + +unlike($report->graph, qr/\b197-/, 'did not find link from 197'); +unlike($report->graph, qr/-197\b/, 'did not find link to 197'); +unlike($report->graph, qr/\b199\b/, 'did not find descendent of 197'); + +done_testing; + +