package Git::IssueManager::Issue; #ABSTRACT: class representing an Issue use Moose; use File::Basename; =head1 DESCRIPTION B represents an issue within the Git::IssueManager module. Issues can be added, removed, modified and listed. Make sure that you understand all the attributes before adding issues to your repository. =cut =attr subject The subject/ title of the issue At most 50 chars allowed. =cut has 'subject' => (is => 'rw', isa => 'Str', required => 1, trigger => sub { my ($self, $new, $old) = @_; die("subject exceeds 50 chars") unless length($new) < 51; } ); =attr priority The priority of the issue. Possible values are: =over =item I - the most highest level of priority =item I =item I =item I =back The default value is B. =cut has 'priority' => (is => 'rw', isa => 'Str', default => 'low', trigger => sub { my ($self, $new, $old) = @_; die("unknown value (" . $new . ")") unless lc($new) eq "urgent" || lc($new) eq "high" || lc($new) eq "medium" || lc($new) eq "low"; }); =attr severity The severity of the issue. Possible values are: =over =item I =item I =item I =item I =back The default value is B =cut has 'severity' => (is => 'rw', isa => 'Str', default => 'low',trigger => sub { my ($self, $new, $old) = @_; die("unknown value (" . $new . ")") unless lc($new) eq "critical" || lc($new) eq "high" || lc($new) eq "medium" || lc($new) eq "low"; }); =attr type The type of the issue. Possible values are: =over =item I - a problem within the code, preventing the correct working of the software =item I - a security related problem within the code, preventing the correct working of the software =item I - an enhancement to an already existing feature =item I - a completly new feature =item I - a simple task, which should be done (please use rarely) =item I - an epic , normally consisting out of multiple issues =back The default values is B. =cut has 'type' => (is => 'rw', isa => 'Str', default => 'bug', trigger => sub { my ($self, $new, $old) = @_; die("unknown value (" . $new . ")") unless lc($new) eq "bug" || lc($new) eq "security-bug" || lc($new) eq "improvement" || lc($new) eq "feature" || lc($new) eq "task" || lc($$new) eq "epic"; }); =attr status The status of the issue. Possible values are: =over =item I - nothing has been done yet =item I - the issue has been assigned to a developer =item I - somebody is working on the issue =item I - the issue is closed =back The default value is B. =cut has 'status' => (is => 'rw', isa => 'Str', default => 'open', trigger => sub { my ($self, $new, $old) = @_; die("unknown value (" . $new . ")") unless lc($new) eq "open" || lc($new) eq "assigned" || lc($new) eq "inprogress" || lc($new) eq "closed"; }); =attr substatus A substatus to the actual status. Possible values are: =over =item I - there is no substatus =item I - the bug was fixed =item I - the issue has been closed but it will never be fixed =back The default value is B. =cut has 'substatus' => (is => 'rw', isa => 'Str', default => 'none', trigger => sub { my ($self, $new, $old) = @_; die("unknown value (" . $new . ")") unless lc($new) eq "none" || lc($new) eq "fixed" || lc($new) eq "wontfix"; }); =attr comment A comment to the current status of the issue. Only Plain Text is allowed. Default value is the empty string. =cut has 'comment' => (is => 'rw', isa=>'Str', default => ""); =attr description The full description of the issue. Only Plain Text and Markdown are allowed. B The default value is the empty string. =cut has 'description' => (is => 'rw', isa => 'Str', default => ""); =attr tags An arrayref of tags/ keywords for better identifying the issue. Maximum length of one tag is B<20> characters. Maximum number of tags is B<10>. =cut has 'tags' => (is => 'rw', isa => 'ArrayRef[Str]', default => sub {return[];}); =attr attachments An arrayref of files attached to this issue, for example documentation or text files presenting error messages, screenshots, etc. =cut has 'attachements' => (is => 'rw', isa=> 'ArrayRef[Str]', default => sub{return [];}); =attr author The author of the issue, can be the name or an anomynized nickname =cut has 'author' => (is=> 'rw', isa => 'Str', default => ""); =attr author_email The authors email for sending status changes of the issue =cut has 'author_email' => (is => 'rw', isa => 'Str', default => ""); =attr worker The persons name working on solving the issue =cut has 'worker' => (is => 'rw', isa => 'Str', default => ""); =attr worker_email The email address of the person working on this issue =cut has 'worker_email' => ( is => 'rw', isa => 'Str', default =>""); =attr creation_date A datetime object representing the date/time the issue was created =cut has 'creation_date' => (is => 'rw', isa=>'DateTime',default => sub{return DateTime->now();}); =attr closed_date A datetime object representing the date/time the issue was closed, only valid if status is closed =cut has 'closed_date' => (is => 'rw', isa=>'DateTime',default => sub{return DateTime->now();}); =attr last_change_date A datetime object representing the date/time the issue was last modified =cut has 'last_change_date' => (is => 'rw', isa=>'DateTime',default => sub{return DateTime->now();}); =attr id id of the issue =cut has 'id' => (is => 'rw', isa => 'Str', default => ""); =attr estimated_time The estimated time for solving this issue in B Default value is B<0>, meaning no estimate set. =cut has 'estimated_time' => (is => 'rw', isa => 'Num', default => 0, trigger => sub { my ($self, $new, $old) = @_; die("unknown value (" . $new . ")") unless $new >=0 ; }); =attr working_time The current time in B already spent on this issue The default value is B<0>. =cut has 'working_time' => (is => 'rw', isa=>'Num', default => 0, trigger => sub { my ($self, $new, $old) = @_; die("unknown value (" . $new . ")") unless $new >= 0; }); =attr close_commit the hash of the commit which closed this issue =cut has 'close_commit' => (is => 'rw', isa=>'Str', default =>""); =method addTag add another tag to the issue. B $issue->addTag("File"); =over =item B<1. Parameter:> Tag to add to the issue =back =cut sub addTag { my $self = shift; my $tag = shift; die("no tag given") unless defined($tag); die("too many tags") unless @{$self->tags}<11; die("tag exceeds 20 chars") unless length($tag) < 21; push (@{$self->tags}, $tag); } =method delTag del a tag from the issue B $issue->delTag("File"); =over =item B<1. Parameter> Tag to remove from issue =back =cut sub delTag { my $self = shift; my $tag = shift; die("no tag given") unless defined($tag); my $i = 0; for my $t (@{$self->tags}) { if ($t eq $tag) { last; } $i++; } splice(@{$self->tags},$i,1); } =method addAttachment Add another attachment to the issue. B $issue->addAttachement("/tmp/test.txt"); =over =item B<1. Parameter> path to the attachment to add =back Make sure the attachment exist at the given path and stays there until the issue has been added. =cut sub addAttachement { my $self = shift; my $attachment = shift; die("no attachment given") unless defined($attachment); die("file does not exist") unless -e $attachment; push (@{$self->attachements}, $attachment); } =method delAttachement Remove an attachment from the issue. B $issue->delAttachement("/tmp/test"); =over =item B<1. Parameter> Attachment path to remove from issue =back =cut sub delAttachment { my $self = shift; my $attachment = shift; die("no attachment given") unless defined($attachment); my $i = 0; for my $a (@{$self->attachments}) { if ($a eq $attachment) { last; } $i++; } splice(@{$self->attachments},$i,1); } =method _createAttachmentTree - internal method, do not call directly creates a git repository tree object from the attachment array and return the hash of the object =over =item B<1. Parameter> reference to a Git::RepositoryHL object =back =cut sub _createAttachmentTree { my $self = shift; my $repository = shift; my @tree; for my $a (@{$self->attachments}) { my $hash = $repository->createFileObjectFromFile($a); my $t = { ref => $hash, path => fileparse($a), mode => "100644", type => "blob" }; push(@tree, $t); } return $repository->createTree(\@tree); } =method createIssue Creates the issue inside the given git repository and commits these changes to the issues branch =over =item B<1. Parameter> reference to a Git::RepositoryHL object =back =cut sub createIssue { my $self = shift; my $repository = shift; die("No Git::LowLevel object given") unless ref($repository) eq "Git::LowLevel"; my $ref = $repository->getReference('refs/heads/issues'); my $root = $ref->getTree(); my $issueTree = $root->newTree(); my $path = $self->subject; $path=~s/\s/_/g; $issueTree->path($self->subject); my $subject = $issueTree->newBlob(); $subject->path("subject"); $subject->_content($self->subject); $issueTree->add($subject); my $priority = $issueTree->newBlob(); $priority->path("priority"); $priority->_content(lc($self->priority)); $issueTree->add($priority); my $severity = $issueTree->newBlob(); $severity->path("severity"); $severity->_content(lc($self->severity)); $issueTree->add($severity); my $type = $issueTree->newBlob(); $type->path("type"); $type->_content(lc($self->type)); $issueTree->add($type); my $last_changed = $issueTree->newBlob(); $last_changed->path("last_change_date"); $last_changed->_content($self->last_change_date->epoch()); $issueTree->add($last_changed); my $creation_date = $issueTree->newBlob(); $creation_date->path("creation_date"); $creation_date->_content($self->creation_date->epoch()); $issueTree->add($creation_date); if ($self->status eq "closed") { my $closed_date = $issueTree->newBlob(); $closed_date->path("closed_date"); $closed_date->_content($self->closed_date->epoch()); $issueTree->add($closed_date); } if (defined($self->substatus) && length($self->substatus) > 0) { my $substatus = $issueTree->newBlob(); $substatus->path("substatus"); $substatus->_content($self->substatus); $issueTree->add($substatus); } if (defined($self->comment) && length($self->comment)> 0) { my $comment = $issueTree->newBlob(); $comment->path("comment"); $comment->_content($self->comment); $issueTree->add($comment); } if (defined($self->close_commit) && length($self->close_commit)> 0 && $self->status eq "closed") { my $close_commit = $issueTree->newBlob(); $close_commit->path("close_commit"); $close_commit->_content($self->close_commit); $issueTree->add($close_commit); } if (defined($self->description) && length($self->description)> 0) { my $description = $issueTree->newBlob(); $description->path("description"); $description->_content($self->description); $issueTree->add($description); } if (defined($self->worker) && length($self->worker)> 0) { my $worker = $issueTree->newBlob(); $worker->path("worker"); $worker->_content($self->worker . "<" . $self->worker_email . ">"); $issueTree->add($worker); } my $estimated = $issueTree->newBlob(); $estimated->path("estimated"); $estimated->_content($self->estimated_time); $issueTree->add($estimated); my $working_time = $issueTree->newBlob(); $working_time->path("working"); $working_time->_content($self->working_time); $issueTree->add($working_time); if (defined($self->tags) && @{$self->tags}> 0) { my $tags = $issueTree->newBlob(); $tags->path("tags"); $tags->_content(join "\n", @{$self->tags}); $issueTree->add($tags); } return $issueTree; } 1;