35 changed files with 1414 additions and 7 deletions
-
62app/Entities/Projects/Issue.php
-
39app/Entities/Projects/IssueStatus.php
-
35app/Entities/Projects/Priority.php
-
11app/Entities/Projects/Project.php
-
83app/Http/Controllers/Issues/CommentController.php
-
26app/Http/Controllers/Issues/OptionController.php
-
116app/Http/Controllers/Projects/IssueController.php
-
29app/Policies/Projects/IssuePolicy.php
-
1app/Providers/AppServiceProvider.php
-
1app/Providers/AuthServiceProvider.php
-
12composer.lock
-
21database/factories/IssueFactory.php
-
38database/migrations/2019_03_03_210017_create_issues_table.php
-
1resources/lang/de/app.php
-
57resources/lang/de/issue.php
-
1resources/lang/de/project.php
-
1resources/lang/en/app.php
-
58resources/lang/en/issue.php
-
1resources/lang/en/project.php
-
1resources/lang/id/app.php
-
2resources/lang/id/comment.php
-
57resources/lang/id/issue.php
-
1resources/lang/id/project.php
-
32resources/views/projects/issues/create.blade.php
-
54resources/views/projects/issues/edit.blade.php
-
67resources/views/projects/issues/index.blade.php
-
54resources/views/projects/issues/partials/comment-section.blade.php
-
65resources/views/projects/issues/show.blade.php
-
3resources/views/projects/partials/nav-tabs.blade.php
-
23routes/web/projects.php
-
106tests/Feature/Projects/IssueCommentsTest.php
-
219tests/Feature/Projects/ProjectIssuesTest.php
-
99tests/Unit/Models/IssueTest.php
-
11tests/Unit/Models/ProjectTest.php
-
34tests/Unit/Policies/IssuePolicyTest.php
@ -0,0 +1,62 @@ |
|||||
|
<?php |
||||
|
|
||||
|
namespace App\Entities\Projects; |
||||
|
|
||||
|
use App\Entities\Users\User; |
||||
|
use App\Entities\Projects\Comment; |
||||
|
use App\Entities\Projects\Project; |
||||
|
use Illuminate\Database\Eloquent\Model; |
||||
|
|
||||
|
class Issue extends Model |
||||
|
{ |
||||
|
protected $fillable = [ |
||||
|
'project_id', 'title', 'body', 'priority_id', 'pic_id', 'creator_id', |
||||
|
]; |
||||
|
|
||||
|
public function project() |
||||
|
{ |
||||
|
return $this->belongsTo(Project::class); |
||||
|
} |
||||
|
|
||||
|
public function pic() |
||||
|
{ |
||||
|
return $this->belongsTo(User::class)->withDefault(['name' => __('issue.no_pic')]); |
||||
|
} |
||||
|
|
||||
|
public function creator() |
||||
|
{ |
||||
|
return $this->belongsTo(User::class); |
||||
|
} |
||||
|
|
||||
|
public function getPriorityAttribute() |
||||
|
{ |
||||
|
return Priority::getNameById($this->priority_id); |
||||
|
} |
||||
|
|
||||
|
public function getPriorityLabelAttribute() |
||||
|
{ |
||||
|
$classColor = Priority::getColorById($this->priority_id); |
||||
|
|
||||
|
return '<span class="label label-'.$classColor.'">'.$this->priority.'</span>'; |
||||
|
} |
||||
|
|
||||
|
public function getStatusAttribute() |
||||
|
{ |
||||
|
return IssueStatus::getNameById($this->status_id); |
||||
|
} |
||||
|
|
||||
|
public function getStatusLabelAttribute() |
||||
|
{ |
||||
|
return '<span class="badge">'.$this->status.'</span>'; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Issue has many comments relation. |
||||
|
* |
||||
|
* @return \Illuminate\Database\Eloquent\Relations\MorphMany |
||||
|
*/ |
||||
|
public function comments() |
||||
|
{ |
||||
|
return $this->morphMany(Comment::class, 'commentable'); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,39 @@ |
|||||
|
<?php |
||||
|
|
||||
|
namespace App\Entities\Projects; |
||||
|
|
||||
|
use App\Entities\ReferenceAbstract; |
||||
|
|
||||
|
class IssueStatus extends ReferenceAbstract |
||||
|
{ |
||||
|
protected static $lists = [ |
||||
|
0 => 'open', |
||||
|
1 => 'resolved', |
||||
|
2 => 'closed', |
||||
|
3 => 'on_hold', |
||||
|
4 => 'invalid', |
||||
|
]; |
||||
|
|
||||
|
protected static $colors = [ |
||||
|
0 => 'yellow', |
||||
|
1 => 'green', |
||||
|
2 => 'primary', |
||||
|
3 => 'default', |
||||
|
4 => 'warning', |
||||
|
]; |
||||
|
|
||||
|
public static function getNameById($singleId) |
||||
|
{ |
||||
|
return trans('issue.'.static::getById($singleId)); |
||||
|
} |
||||
|
|
||||
|
public static function toArray() |
||||
|
{ |
||||
|
$lists = []; |
||||
|
foreach (static::$lists as $key => $value) { |
||||
|
$lists[$key] = trans('issue.'.$value); |
||||
|
} |
||||
|
|
||||
|
return $lists; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,35 @@ |
|||||
|
<?php |
||||
|
|
||||
|
namespace App\Entities\Projects; |
||||
|
|
||||
|
use App\Entities\ReferenceAbstract; |
||||
|
|
||||
|
class Priority extends ReferenceAbstract |
||||
|
{ |
||||
|
protected static $lists = [ |
||||
|
1 => 'minor', |
||||
|
2 => 'major', |
||||
|
3 => 'critical', |
||||
|
]; |
||||
|
|
||||
|
protected static $colors = [ |
||||
|
1 => 'info', |
||||
|
2 => 'warning', |
||||
|
3 => 'danger', |
||||
|
]; |
||||
|
|
||||
|
public static function getNameById($singleId) |
||||
|
{ |
||||
|
return trans('issue.'.static::getById($singleId)); |
||||
|
} |
||||
|
|
||||
|
public static function toArray() |
||||
|
{ |
||||
|
$lists = []; |
||||
|
foreach (static::$lists as $key => $value) { |
||||
|
$lists[$key] = trans('issue.'.$value); |
||||
|
} |
||||
|
|
||||
|
return $lists; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,83 @@ |
|||||
|
<?php |
||||
|
|
||||
|
namespace App\Http\Controllers\Issues; |
||||
|
|
||||
|
use Illuminate\Http\Request; |
||||
|
use App\Entities\Projects\Issue; |
||||
|
use App\Entities\Projects\Comment; |
||||
|
use App\Http\Controllers\Controller; |
||||
|
|
||||
|
class CommentController extends Controller |
||||
|
{ |
||||
|
/** |
||||
|
* Store a new comment in storage. |
||||
|
* |
||||
|
* @param \Illuminate\Http\Request $request |
||||
|
* @param \App\Entities\Projects\Issue $issue |
||||
|
* @return \Illuminate\Http\RedirectResponse |
||||
|
*/ |
||||
|
public function store(Request $request, Issue $issue) |
||||
|
{ |
||||
|
$this->authorize('comment-on', $issue); |
||||
|
|
||||
|
$newComment = $request->validate([ |
||||
|
'body' => 'required|string|max:255', |
||||
|
]); |
||||
|
|
||||
|
$issue->comments()->create([ |
||||
|
'body' => $newComment['body'], |
||||
|
'creator_id' => auth()->id(), |
||||
|
]); |
||||
|
$issue->touch(); |
||||
|
|
||||
|
flash(__('comment.created'), 'success'); |
||||
|
|
||||
|
return back(); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Update the specified comment. |
||||
|
* |
||||
|
* @param \Illuminate\Http\Request $request |
||||
|
* @param \App\Entities\Projects\Issue $issue |
||||
|
* @param \App\Entities\Projects\Comment $comment |
||||
|
* @return \Illuminate\Http\Response |
||||
|
*/ |
||||
|
public function update(Request $request, Issue $issue, Comment $comment) |
||||
|
{ |
||||
|
$this->authorize('update', $comment); |
||||
|
|
||||
|
$commentData = $request->validate([ |
||||
|
'body' => 'required|string|max:255', |
||||
|
]); |
||||
|
$comment->update($commentData); |
||||
|
flash(__('comment.updated'), 'success'); |
||||
|
|
||||
|
return redirect()->route('projects.issues.show', [$issue->project, $issue]); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Remove the specified comment. |
||||
|
* |
||||
|
* @param \App\Entities\Projects\Issue $issue |
||||
|
* @param \\App\Entities\Projects\Comment $comment |
||||
|
* @return \Illuminate\Routing\Redirector |
||||
|
*/ |
||||
|
public function destroy(Issue $issue, Comment $comment) |
||||
|
{ |
||||
|
$this->authorize('delete', $comment); |
||||
|
|
||||
|
request()->validate([ |
||||
|
'comment_id' => 'required|exists:comments,id', |
||||
|
]); |
||||
|
|
||||
|
if (request('comment_id') == $comment->id && $comment->delete()) { |
||||
|
flash(__('comment.deleted'), 'warning'); |
||||
|
|
||||
|
return redirect()->route('projects.issues.show', [$issue->project, $issue]); |
||||
|
} |
||||
|
flash(__('comment.undeleted'), 'error'); |
||||
|
|
||||
|
return back(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,26 @@ |
|||||
|
<?php |
||||
|
|
||||
|
namespace App\Http\Controllers\Issues; |
||||
|
|
||||
|
use Illuminate\Http\Request; |
||||
|
use App\Entities\Projects\Issue; |
||||
|
use App\Http\Controllers\Controller; |
||||
|
|
||||
|
class OptionController extends Controller |
||||
|
{ |
||||
|
public function update(Request $request, Issue $issue) |
||||
|
{ |
||||
|
$issueData = $request->validate([ |
||||
|
'priority_id' => 'required|in:1,2,3', |
||||
|
'status_id' => 'required|in:0,1,2,3,4', |
||||
|
'pic_id' => 'nullable|exists:users,id', |
||||
|
]); |
||||
|
$issue->priority_id = $issueData['priority_id']; |
||||
|
$issue->status_id = $issueData['status_id']; |
||||
|
$issue->pic_id = $issueData['pic_id']; |
||||
|
$issue->save(); |
||||
|
flash(__('issue.updated'), 'success'); |
||||
|
|
||||
|
return back(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,116 @@ |
|||||
|
<?php |
||||
|
|
||||
|
namespace App\Http\Controllers\Projects; |
||||
|
|
||||
|
use App\Entities\Users\User; |
||||
|
use Illuminate\Http\Request; |
||||
|
use App\Entities\Projects\Issue; |
||||
|
use App\Entities\Projects\Comment; |
||||
|
use App\Entities\Projects\Project; |
||||
|
use App\Entities\Projects\Priority; |
||||
|
use App\Http\Controllers\Controller; |
||||
|
use App\Entities\Projects\IssueStatus; |
||||
|
|
||||
|
class IssueController extends Controller |
||||
|
{ |
||||
|
public function index(Project $project) |
||||
|
{ |
||||
|
$issueQuery = $project->issues() |
||||
|
->orderBy('updated_at', 'desc') |
||||
|
->with(['pic', 'creator']) |
||||
|
->withCount(['comments']); |
||||
|
|
||||
|
if ($priorityId = request('priority_id')) { |
||||
|
$issueQuery->where('priority_id', $priorityId); |
||||
|
} |
||||
|
|
||||
|
if ($statusId = request('status_id')) { |
||||
|
$issueQuery->where('status_id', $priorityId); |
||||
|
} |
||||
|
|
||||
|
$issues = $issueQuery->get(); |
||||
|
|
||||
|
return view('projects.issues.index', compact('project', 'issues')); |
||||
|
} |
||||
|
|
||||
|
public function create(Project $project) |
||||
|
{ |
||||
|
$users = User::pluck('name', 'id'); |
||||
|
$priorities = Priority::toArray(); |
||||
|
|
||||
|
return view('projects.issues.create', compact('project', 'users', 'priorities')); |
||||
|
} |
||||
|
|
||||
|
public function store(Request $request, Project $project) |
||||
|
{ |
||||
|
$issueData = $request->validate([ |
||||
|
'title' => 'required|max:60', |
||||
|
'body' => 'required|max:255', |
||||
|
'priority_id' => 'required|in:1,2,3', |
||||
|
'pic_id' => 'nullable|exists:users,id', |
||||
|
]); |
||||
|
Issue::create([ |
||||
|
'project_id' => $project->id, |
||||
|
'creator_id' => auth()->id(), |
||||
|
'title' => $issueData['title'], |
||||
|
'body' => $issueData['body'], |
||||
|
'priority_id' => $issueData['priority_id'], |
||||
|
'pic_id' => $issueData['pic_id'], |
||||
|
]); |
||||
|
flash(__('issue.created'), 'success'); |
||||
|
|
||||
|
return redirect()->route('projects.issues.index', $project); |
||||
|
} |
||||
|
|
||||
|
public function show(Project $project, Issue $issue) |
||||
|
{ |
||||
|
$editableComment = null; |
||||
|
$priorities = Priority::toArray(); |
||||
|
$statuses = IssueStatus::toArray(); |
||||
|
$users = User::pluck('name', 'id'); |
||||
|
$comments = $issue->comments()->with('creator')->get(); |
||||
|
|
||||
|
if (request('action') == 'comment-edit' && request('comment_id') != null) { |
||||
|
$editableComment = Comment::find(request('comment_id')); |
||||
|
} |
||||
|
|
||||
|
return view('projects.issues.show', compact( |
||||
|
'project', 'issue', 'users', 'statuses', 'priorities', 'comments', |
||||
|
'editableComment' |
||||
|
)); |
||||
|
} |
||||
|
|
||||
|
public function edit(Project $project, Issue $issue) |
||||
|
{ |
||||
|
return view('projects.issues.edit', compact('project', 'issue')); |
||||
|
} |
||||
|
|
||||
|
public function update(Request $request, Project $project, Issue $issue) |
||||
|
{ |
||||
|
$issueData = $request->validate([ |
||||
|
'title' => 'required|max:60', |
||||
|
'body' => 'required|max:255', |
||||
|
]); |
||||
|
$issue->title = $issueData['title']; |
||||
|
$issue->body = $issueData['body']; |
||||
|
$issue->save(); |
||||
|
|
||||
|
flash(__('issue.updated'), 'success'); |
||||
|
|
||||
|
return redirect()->route('projects.issues.show', [$project, $issue]); |
||||
|
} |
||||
|
|
||||
|
public function destroy(Request $request, Project $project, Issue $issue) |
||||
|
{ |
||||
|
$request->validate(['issue_id' => 'required']); |
||||
|
|
||||
|
if ($request->get('issue_id') == $issue->id && $issue->delete()) { |
||||
|
flash(__('issue.deleted'), 'warning'); |
||||
|
|
||||
|
return redirect()->route('projects.issues.index', $project); |
||||
|
} |
||||
|
flash(__('issue.undeleted'), 'danger'); |
||||
|
|
||||
|
return back(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,29 @@ |
|||||
|
<?php |
||||
|
|
||||
|
namespace App\Policies\Projects; |
||||
|
|
||||
|
use App\Entities\Users\User; |
||||
|
use App\Entities\Projects\Issue; |
||||
|
use Illuminate\Auth\Access\HandlesAuthorization; |
||||
|
|
||||
|
class IssuePolicy |
||||
|
{ |
||||
|
use HandlesAuthorization; |
||||
|
|
||||
|
public function create(User $user, Issue $issue) |
||||
|
{ |
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Determine whether the user can add comment to an issue. |
||||
|
* |
||||
|
* @param \App\Entities\Users\User $user |
||||
|
* @param \App\Entities\Projects\Issue $issue |
||||
|
* @return bool |
||||
|
*/ |
||||
|
public function commentOn(User $user, Issue $issue) |
||||
|
{ |
||||
|
return true; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,21 @@ |
|||||
|
<?php |
||||
|
|
||||
|
use App\Entities\Users\User; |
||||
|
use Faker\Generator as Faker; |
||||
|
use App\Entities\Projects\Issue; |
||||
|
use App\Entities\Projects\Project; |
||||
|
|
||||
|
$factory->define(Issue::class, function (Faker $faker) { |
||||
|
return [ |
||||
|
'project_id' => function () { |
||||
|
return factory(Project::class)->create()->id; |
||||
|
}, |
||||
|
'title' => $faker->words(3, true), |
||||
|
'body' => $faker->sentences(3, true), |
||||
|
'creator_id' => function () { |
||||
|
return factory(User::class)->create()->id; |
||||
|
}, |
||||
|
'status_id' => 0, |
||||
|
'priority_id' => 1, |
||||
|
]; |
||||
|
}); |
||||
@ -0,0 +1,38 @@ |
|||||
|
<?php |
||||
|
|
||||
|
use Illuminate\Support\Facades\Schema; |
||||
|
use Illuminate\Database\Schema\Blueprint; |
||||
|
use Illuminate\Database\Migrations\Migration; |
||||
|
|
||||
|
class CreateIssuesTable extends Migration |
||||
|
{ |
||||
|
/** |
||||
|
* Run the migrations. |
||||
|
* |
||||
|
* @return void |
||||
|
*/ |
||||
|
public function up() |
||||
|
{ |
||||
|
Schema::create('issues', function (Blueprint $table) { |
||||
|
$table->increments('id'); |
||||
|
$table->unsignedInteger('project_id'); |
||||
|
$table->string('title', 60); |
||||
|
$table->string('body'); |
||||
|
$table->unsignedInteger('creator_id'); |
||||
|
$table->unsignedTinyInteger('priority_id'); |
||||
|
$table->unsignedInteger('pic_id')->nullable(); |
||||
|
$table->unsignedTinyInteger('status_id')->default(0); |
||||
|
$table->timestamps(); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Reverse the migrations. |
||||
|
* |
||||
|
* @return void |
||||
|
*/ |
||||
|
public function down() |
||||
|
{ |
||||
|
Schema::dropIfExists('issues'); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,57 @@ |
|||||
|
<?php |
||||
|
|
||||
|
return [ |
||||
|
// Labels
|
||||
|
'issue' => 'Issue', |
||||
|
'list' => 'Issue List', |
||||
|
'search' => 'Search Issue', |
||||
|
'search_text' => 'Title ...', |
||||
|
'all' => 'All Issue', |
||||
|
'select' => 'Select Issue', |
||||
|
'detail' => 'Issue Detail', |
||||
|
'not_found' => 'Issue not found.', |
||||
|
'empty' => 'Issue is empty.', |
||||
|
'back_to_show' => 'Back to Issue Detail', |
||||
|
'back_to_index' => 'Back to Issue List', |
||||
|
'no_pic' => 'No issue PIC', |
||||
|
|
||||
|
// Actions
|
||||
|
'create' => 'Create new Issue', |
||||
|
'created' => 'A new Issue has been created.', |
||||
|
'show' => 'View Issue Detail', |
||||
|
'edit' => 'Edit Issue', |
||||
|
'update' => 'Update Issue', |
||||
|
'updated' => 'Issue data has been updated.', |
||||
|
'delete' => 'Delete Issue', |
||||
|
'delete_confirm' => 'Are you sure to delete this Issue?', |
||||
|
'deleted' => 'Issue has been deleted.', |
||||
|
'undeleted' => 'Issue not deleted.', |
||||
|
'undeleteable' => 'Issue data cannot be deleted.', |
||||
|
'assign_pic' => 'Assign PIC', |
||||
|
'select_pic' => 'Select a PIC', |
||||
|
'pic_assigned' => 'Issue PIC has been assigned.', |
||||
|
'pic_removed' => 'Issue PIC has been removed.', |
||||
|
|
||||
|
// Attributes
|
||||
|
'title' => 'Issue Title', |
||||
|
'body' => 'Issue Description', |
||||
|
|
||||
|
// Relations
|
||||
|
'project' => 'Issue Project', |
||||
|
'pic' => 'Issue PIC', |
||||
|
'creator' => 'Issue Creator', |
||||
|
|
||||
|
// Priority
|
||||
|
'minor' => 'Minor', |
||||
|
'major' => 'Major', |
||||
|
'critical' => 'Critical', |
||||
|
'all_priority' => 'All Priority', |
||||
|
|
||||
|
// Statuses
|
||||
|
'open' => 'Open', |
||||
|
'resolved' => 'Resolved', |
||||
|
'closed' => 'Closed', |
||||
|
'on_hold' => 'On Hold', |
||||
|
'invalid' => 'Invalid', |
||||
|
'all_status' => 'All Status', |
||||
|
]; |
||||
@ -0,0 +1,58 @@ |
|||||
|
<?php |
||||
|
|
||||
|
return [ |
||||
|
// Labels
|
||||
|
'issue' => 'Issue', |
||||
|
'list' => 'Issue List', |
||||
|
'search' => 'Search Issue', |
||||
|
'search_text' => 'Title ...', |
||||
|
'all' => 'All Issue', |
||||
|
'select' => 'Select Issue', |
||||
|
'detail' => 'Issue Detail', |
||||
|
'not_found' => 'Issue not found.', |
||||
|
'empty' => 'Issue is empty.', |
||||
|
'back_to_show' => 'Back to Issue Detail', |
||||
|
'back_to_index' => 'Back to Issue List', |
||||
|
'no_pic' => 'No issue PIC', |
||||
|
|
||||
|
// Actions
|
||||
|
'create' => 'Create new Issue', |
||||
|
'created' => 'A new Issue has been created.', |
||||
|
'show' => 'View Issue Detail', |
||||
|
'edit' => 'Edit Issue', |
||||
|
'update' => 'Update Issue', |
||||
|
'updated' => 'Issue data has been updated.', |
||||
|
'delete' => 'Delete Issue', |
||||
|
'delete_confirm' => 'Are you sure to delete this Issue?', |
||||
|
'deleted' => 'Issue has been deleted.', |
||||
|
'undeleted' => 'Issue not deleted.', |
||||
|
'undeleteable' => 'Issue data cannot be deleted.', |
||||
|
'assign_pic' => 'Assign PIC', |
||||
|
'select_pic' => 'Select a PIC', |
||||
|
'pic_assigned' => 'Issue PIC has been assigned.', |
||||
|
'pic_removed' => 'Issue PIC has been removed.', |
||||
|
|
||||
|
// Attributes
|
||||
|
'title' => 'Issue Title', |
||||
|
'body' => 'Issue Description', |
||||
|
|
||||
|
// Relations
|
||||
|
'project' => 'Issue Project', |
||||
|
'pic' => 'Issue PIC', |
||||
|
'creator' => 'Issue Creator', |
||||
|
|
||||
|
// Priority
|
||||
|
'priority' => 'Priority', |
||||
|
'minor' => 'Minor', |
||||
|
'major' => 'Major', |
||||
|
'critical' => 'Critical', |
||||
|
'all_priority' => 'All Priority', |
||||
|
|
||||
|
// Statuses
|
||||
|
'open' => 'Open', |
||||
|
'resolved' => 'Resolved', |
||||
|
'closed' => 'Closed', |
||||
|
'on_hold' => 'On Hold', |
||||
|
'invalid' => 'Invalid', |
||||
|
'all_status' => 'All Status', |
||||
|
]; |
||||
@ -0,0 +1,57 @@ |
|||||
|
<?php |
||||
|
|
||||
|
return [ |
||||
|
// Labels
|
||||
|
'issue' => 'Issue', |
||||
|
'list' => 'Daftar Issue', |
||||
|
'search' => 'Cari Issue', |
||||
|
'search_text' => 'Nama ...', |
||||
|
'all' => 'Semua Issue', |
||||
|
'select' => 'Pilih Issue', |
||||
|
'detail' => 'Detail Issue', |
||||
|
'not_found' => 'Issue tidak ditemukan.', |
||||
|
'empty' => 'Belum ada Issue', |
||||
|
'back_to_show' => 'Kembali ke detail Issue', |
||||
|
'back_to_index' => 'Kembali ke daftar Issue', |
||||
|
'no_pic' => 'Belum ada PIC', |
||||
|
|
||||
|
// Actions
|
||||
|
'create' => 'Input Issue Baru', |
||||
|
'created' => 'Input Issue baru telah berhasil.', |
||||
|
'show' => 'Lihat Detail Issue', |
||||
|
'edit' => 'Edit Issue', |
||||
|
'update' => 'Update Issue', |
||||
|
'updated' => 'Update data Issue telah berhasil.', |
||||
|
'delete' => 'Hapus Issue', |
||||
|
'delete_confirm' => 'Anda yakin akan menghapus Issue ini?', |
||||
|
'deleted' => 'Hapus data Issue telah berhasil.', |
||||
|
'undeleted' => 'Data Issue gagal dihapus.', |
||||
|
'undeleteable' => 'Data Issue tidak dapat dihapus.', |
||||
|
'assign_pic' => 'Tugaskan PIC', |
||||
|
'select_pic' => 'Pilih PIC', |
||||
|
'pic_assigned' => 'PIC telah ditugaskan.', |
||||
|
'pic_removed' => 'PIC telah dihapus.', |
||||
|
|
||||
|
// Attributes
|
||||
|
'title' => 'Judul Issue', |
||||
|
'body' => 'Deskripsi Issue', |
||||
|
|
||||
|
// Relations
|
||||
|
'project' => 'Project Issue', |
||||
|
'pic' => 'PIC Issue', |
||||
|
'creator' => 'Pembuat Issue', |
||||
|
|
||||
|
// Priority
|
||||
|
'minor' => 'Minor', |
||||
|
'major' => 'Major', |
||||
|
'critical' => 'Critical', |
||||
|
'all_priority' => 'Semua Priority', |
||||
|
|
||||
|
// Statuses
|
||||
|
'open' => 'Open', |
||||
|
'resolved' => 'Selesai', |
||||
|
'closed' => 'Ditutup', |
||||
|
'on_hold' => 'Ditunda', |
||||
|
'invalid' => 'Tidak Valid', |
||||
|
'all_status' => 'Semua Status', |
||||
|
]; |
||||
@ -0,0 +1,32 @@ |
|||||
|
@extends('layouts.project') |
||||
|
|
||||
|
@section('subtitle', __('issue.create')) |
||||
|
|
||||
|
@section('action-buttons') |
||||
|
@can('create', new App\Entities\Projects\Issue) |
||||
|
{!! html_link_to_route('projects.issues.create', __('issue.create'), $project, ['class' => 'btn btn-success', 'icon' => 'plus']) !!} |
||||
|
@endcan |
||||
|
@endsection |
||||
|
|
||||
|
@section('content-project') |
||||
|
|
||||
|
<div class="row"> |
||||
|
<div class="col-sm-6 col-sm-offset-2"> |
||||
|
{{ Form::open(['route' => ['projects.issues.store', $project]]) }} |
||||
|
<div class="panel panel-default"> |
||||
|
<div class="panel-heading"><h3 class="panel-title">{{ __('issue.create') }}</h3></div> |
||||
|
<div class="panel-body"> |
||||
|
{!! FormField::text('title', ['label' => __('issue.title')]) !!} |
||||
|
{!! FormField::textarea('body', ['label' => __('issue.body')]) !!} |
||||
|
{!! FormField::radios('priority_id', $priorities, ['label' => __('issue.priority'), 'placeholder' => false]) !!} |
||||
|
{!! FormField::select('pic_id', $users, ['label' => __('issue.pic')]) !!} |
||||
|
</div> |
||||
|
<div class="panel-footer"> |
||||
|
{{ Form::submit(__('issue.create'), ['class' => 'btn btn-success']) }} |
||||
|
{{ link_to_route('projects.issues.index', __('app.cancel'), $project, ['class' => 'btn btn-default']) }} |
||||
|
</div> |
||||
|
</div> |
||||
|
{{ Form::close() }} |
||||
|
</div> |
||||
|
</div> |
||||
|
@endsection |
||||
@ -0,0 +1,54 @@ |
|||||
|
@extends('layouts.project') |
||||
|
|
||||
|
@section('subtitle', __('issue.update')) |
||||
|
|
||||
|
@section('action-buttons') |
||||
|
@can('create', new App\Entities\Projects\Issue) |
||||
|
{!! html_link_to_route('projects.issues.create', __('issue.create'), $project, ['class' => 'btn btn-success', 'icon' => 'plus']) !!} |
||||
|
@endcan |
||||
|
@endsection |
||||
|
|
||||
|
@section('content-project') |
||||
|
<div class="row"> |
||||
|
<div class="col-sm-6 col-sm-offset-2"> |
||||
|
@if (request('action') == 'delete' && $issue) |
||||
|
<div class="panel panel-default"> |
||||
|
<div class="panel-heading"><h3 class="panel-title">{{ __('issue.delete') }}</h3></div> |
||||
|
<div class="panel-body"> |
||||
|
<label class="control-label text-primary">{{ __('issue.title') }}</label> |
||||
|
<p>{{ $issue->title }}</p> |
||||
|
<label class="control-label text-primary">{{ __('issue.body') }}</label> |
||||
|
<p>{{ $issue->body }}</p> |
||||
|
{!! $errors->first('issue_id', '<span class="form-error small">:message</span>') !!} |
||||
|
</div> |
||||
|
<hr style="margin:0"> |
||||
|
<div class="panel-body text-danger">{{ __('issue.delete_confirm') }}</div> |
||||
|
<div class="panel-footer"> |
||||
|
{!! FormField::delete( |
||||
|
['route' => ['projects.issues.destroy', $project, $issue]], |
||||
|
__('app.delete_confirm_button'), |
||||
|
['id' => 'delete-issue-'.$issue->id, 'class' => 'btn btn-danger'], |
||||
|
['issue_id' => $issue->id] |
||||
|
) !!} |
||||
|
{{ link_to_route('projects.issues.edit', __('app.cancel'), [$project, $issue], ['class' => 'btn btn-default']) }} |
||||
|
</div> |
||||
|
</div> |
||||
|
@else |
||||
|
{{ Form::model($issue, ['route' => ['projects.issues.update', $project, $issue], 'method' => 'patch']) }} |
||||
|
<div class="panel panel-default"> |
||||
|
<div class="panel-heading"><h3 class="panel-title">{{ __('issue.update') }}</h3></div> |
||||
|
<div class="panel-body"> |
||||
|
{!! FormField::text('title', ['label' => __('issue.title')]) !!} |
||||
|
{!! FormField::textarea('body', ['label' => __('issue.body')]) !!} |
||||
|
</div> |
||||
|
<div class="panel-footer"> |
||||
|
{{ Form::submit(__('issue.update'), ['class' => 'btn btn-success']) }} |
||||
|
{{ link_to_route('projects.issues.show', __('app.cancel'), [$project, $issue], ['class' => 'btn btn-default']) }} |
||||
|
{{ link_to_route('projects.issues.edit', __('app.delete'), [$project, $issue, 'action' => 'delete'], ['id' => 'delete-issue-'.$issue->id, 'class' => 'btn btn-danger pull-right']) }} |
||||
|
</div> |
||||
|
</div> |
||||
|
{{ Form::close() }} |
||||
|
@endif |
||||
|
</div> |
||||
|
</div> |
||||
|
@endsection |
||||
@ -0,0 +1,67 @@ |
|||||
|
@inject('priorities', 'App\Entities\Projects\Priority') |
||||
|
@inject('issueStatuses', 'App\Entities\Projects\IssueStatus') |
||||
|
@extends('layouts.project') |
||||
|
|
||||
|
@section('subtitle', __('project.issues')) |
||||
|
|
||||
|
@section('action-buttons') |
||||
|
{{ Form::open(['method' => 'get', 'class' => 'form-inline', 'style' => 'display:inline']) }} |
||||
|
{!! FormField::select('priority_id', $priorities::toArray(), ['label' => false, 'placeholder' => __('issue.all_priority'), 'value' => request('priority_id')]) !!} |
||||
|
{!! FormField::select('status_id', $issueStatuses::toArray(), ['label' => false, 'placeholder' => __('issue.all_status'), 'value' => request('status_id')]) !!} |
||||
|
{{ Form::submit(__('app.filter'), ['class' => 'btn btn-info']) }} |
||||
|
@if (request(['priority_id', 'status_id'])) |
||||
|
{{ link_to_route('projects.issues.index', __('app.reset'), $project, ['class' => 'btn btn-default']) }} |
||||
|
@endif |
||||
|
{{ Form::close() }} |
||||
|
@can('create', new App\Entities\Projects\Issue) |
||||
|
{!! html_link_to_route('projects.issues.create', __('issue.create'), $project, ['class' => 'btn btn-success', 'icon' => 'plus']) !!} |
||||
|
@endcan |
||||
|
@endsection |
||||
|
|
||||
|
@section('content-project') |
||||
|
<div id="project-issues" class="panel panel-default table-responsive"> |
||||
|
<div class="panel-heading"> |
||||
|
<h3 class="panel-title">{{ __('project.issues') }}</h3> |
||||
|
</div> |
||||
|
<table class="table table-condensed table-striped"> |
||||
|
<thead> |
||||
|
<th>{{ __('app.table_no') }}</th> |
||||
|
<th>{{ __('issue.title') }}</th> |
||||
|
<th>{{ __('issue.priority') }}</th> |
||||
|
<th>{{ __('app.status') }}</th> |
||||
|
<th class="text-center">{{ __('comment.comment') }}</th> |
||||
|
<th>{{ __('issue.pic') }}</th> |
||||
|
<th>{{ __('issue.creator') }}</th> |
||||
|
<th>{{ __('app.last_update') }}</th> |
||||
|
<th class="text-center">{{ __('app.action') }}</th> |
||||
|
</thead> |
||||
|
<tbody> |
||||
|
@forelse($issues as $key => $issue) |
||||
|
@php |
||||
|
$no = 1 + $key; |
||||
|
@endphp |
||||
|
<tr id="{{ $issue->id }}"> |
||||
|
<td>{{ $no }}</td> |
||||
|
<td>{{ $issue->title }}</td> |
||||
|
<td>{!! $issue->priority_label !!}</td> |
||||
|
<td>{!! $issue->status_label !!}</td> |
||||
|
<td class="text-center">{{ $issue->comments_count }}</td> |
||||
|
<td>{{ $issue->pic->name }}</td> |
||||
|
<td>{{ $issue->creator->name }}</td> |
||||
|
<td>{{ $issue->updated_at->diffForHumans() }}</td> |
||||
|
<td class="text-center"> |
||||
|
{{ link_to_route( |
||||
|
'projects.issues.show', |
||||
|
__('app.show'), |
||||
|
[$project, $issue], |
||||
|
['title' => __('issue.show')] |
||||
|
) }} |
||||
|
</td> |
||||
|
</tr> |
||||
|
@empty |
||||
|
<tr><td colspan="9">{{ __('issue.not_found') }}</td></tr> |
||||
|
@endforelse |
||||
|
</tbody> |
||||
|
</table> |
||||
|
</div> |
||||
|
@endsection |
||||
@ -0,0 +1,54 @@ |
|||||
|
@foreach($comments as $comment) |
||||
|
<div class="alert alert-warning"> |
||||
|
<legend style="font-size: 14px;margin-bottom: 10px;"> |
||||
|
<span class="label label-default pull-right">{{ $comment->time_display }}</span> |
||||
|
<strong>{{ $comment->creator->name }}</strong> |
||||
|
</legend> |
||||
|
<div class="pull-right"> |
||||
|
@can('update', $comment) |
||||
|
{{ link_to_route('projects.issues.show', __('app.edit'), [$project, $issue, 'action' => 'comment-edit', 'comment_id' => $comment->id], ['id' => 'edit-comment-'.$comment->id, 'class' => 'small', 'title' => __('comment.edit')]) }} |
||||
|
@endcan |
||||
|
@can('delete', $comment) |
||||
|
{!! FormField::delete( |
||||
|
['route' => ['issues.comments.destroy', $issue, $comment], 'class' => ''], |
||||
|
'×', |
||||
|
['class' => 'btn-link', 'id' => 'delete-comment-'.$comment->id], |
||||
|
['comment_id' => $comment->id] |
||||
|
) !!} |
||||
|
@endcan |
||||
|
</div> |
||||
|
{!! nl2br($comment->body) !!} |
||||
|
</div> |
||||
|
@endforeach |
||||
|
|
||||
|
@can('comment-on', $issue) |
||||
|
{{ Form::open(['route' => ['issues.comments.store', $issue]]) }} |
||||
|
{!! FormField::textarea('body', ['required' => true, 'label' => false, 'placeholder' => __('comment.create_text')]) !!} |
||||
|
{{ Form::submit(__('comment.create'), ['class' => 'btn btn-success pull-right']) }} |
||||
|
{{ Form::close() }} |
||||
|
<div class="clearfix"></div><br> |
||||
|
@endcan |
||||
|
|
||||
|
@if (Request::get('action') == 'comment-edit' && $editableComment) |
||||
|
<div id="commentModal" class="modal" role="dialog"> |
||||
|
<div class="modal-dialog"> |
||||
|
<!-- Modal content--> |
||||
|
<div class="modal-content"> |
||||
|
<div class="modal-header"> |
||||
|
{{ link_to_route('projects.issues.show', '×', [$issue->project, $issue], ['class' => 'close']) }} |
||||
|
<h4 class="modal-title">{{ __('comment.edit') }}</h4> |
||||
|
</div> |
||||
|
{!! Form::model($editableComment, ['route' => ['issues.comments.update', $issue, $editableComment],'method' => 'patch']) !!} |
||||
|
<div class="modal-body"> |
||||
|
{!! FormField::textarea('body', ['label' => __('comment.body')]) !!} |
||||
|
{{ Form::hidden('page', request('page')) }} |
||||
|
</div> |
||||
|
<div class="modal-footer"> |
||||
|
{!! Form::submit(__('comment.update'), ['class' => 'btn btn-success']) !!} |
||||
|
{{ link_to_route('projects.issues.show', __('app.cancel'), [$project->issue, $issue] + request(['page']), ['class' => 'btn btn-default']) }} |
||||
|
</div> |
||||
|
{!! Form::close() !!} |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
@endif |
||||
@ -0,0 +1,65 @@ |
|||||
|
@extends('layouts.project') |
||||
|
|
||||
|
@section('subtitle', __('issue.detail')) |
||||
|
|
||||
|
@section('action-buttons') |
||||
|
@can('create', new App\Entities\Projects\Issue) |
||||
|
{!! html_link_to_route('projects.issues.create', __('issue.create'), $project, ['class' => 'btn btn-success', 'icon' => 'plus']) !!} |
||||
|
@endcan |
||||
|
@endsection |
||||
|
|
||||
|
@section('content-project') |
||||
|
<div class="row"> |
||||
|
<div class="col-md-6"> |
||||
|
<div class="panel panel-default"> |
||||
|
<div class="panel-heading"> |
||||
|
<h3 class="panel-title"> |
||||
|
<div class="pull-right">{!! $issue->status_label !!}</div> |
||||
|
{{ __('issue.detail') }} |
||||
|
</h3> |
||||
|
</div> |
||||
|
<table class="table table-condensed"> |
||||
|
<tbody> |
||||
|
<tr><th class="col-md-4">{{ __('issue.title') }}</th><td class="col-md-8">{{ $issue->title }}</td></tr> |
||||
|
<tr><th>{{ __('issue.body') }}</th><td>{{ $issue->body }}</td></tr> |
||||
|
<tr><th>{{ __('issue.priority') }}</th><td>{!! $issue->priority_label !!}</td></tr> |
||||
|
<tr><th>{{ __('issue.pic') }}</th><td>{{ $issue->pic->name }}</td></tr> |
||||
|
<tr><th>{{ __('app.created_by') }}</th><td>{{ $issue->creator->name }}</td></tr> |
||||
|
</tbody> |
||||
|
</table> |
||||
|
<div class="panel-footer"> |
||||
|
{{ link_to_route('projects.issues.edit', __('issue.edit'), [$project, $issue], ['id' => 'edit-issue-'.$issue->id, 'class' => 'btn btn-warning']) }} |
||||
|
{{ link_to_route('projects.issues.index', __('issue.back_to_index'), [$project], ['class' => 'btn btn-default pull-right']) }} |
||||
|
</div> |
||||
|
</div> |
||||
|
<hr> |
||||
|
@include('projects.issues.partials.comment-section') |
||||
|
</div> |
||||
|
<div class="col-md-6"> |
||||
|
{{ Form::model($issue, ['route' => ['issues.options.update', $issue], 'method' => 'patch']) }} |
||||
|
<div class="panel panel-default"> |
||||
|
<div class="panel-heading"><h3 class="panel-title">{{ __('app.action') }}</h3></div> |
||||
|
<div class="panel-body"> |
||||
|
{!! FormField::radios('priority_id', $priorities, ['label' => __('issue.priority')]) !!} |
||||
|
{!! FormField::radios('status_id', $statuses, ['label' => __('app.status')]) !!} |
||||
|
{!! FormField::select('pic_id', $users, ['label' => __('issue.assign_pic'), 'placeholder' => __('issue.select_pic')]) !!} |
||||
|
</div> |
||||
|
<div class="panel-footer"> |
||||
|
{{ Form::submit(__('issue.update'), ['class' => 'btn btn-success']) }} |
||||
|
</div> |
||||
|
</div> |
||||
|
{{ Form::close() }} |
||||
|
</div> |
||||
|
</div> |
||||
|
@endsection |
||||
|
|
||||
|
@section('script') |
||||
|
<script> |
||||
|
(function () { |
||||
|
$('#commentModal').modal({ |
||||
|
show: true, |
||||
|
backdrop: 'static', |
||||
|
}); |
||||
|
})(); |
||||
|
</script> |
||||
|
@endsection |
||||
@ -0,0 +1,106 @@ |
|||||
|
<?php |
||||
|
|
||||
|
namespace Tests\Feature\Projects; |
||||
|
|
||||
|
use Tests\TestCase; |
||||
|
use App\Entities\Projects\Issue; |
||||
|
use App\Entities\Projects\Comment; |
||||
|
use Illuminate\Foundation\Testing\RefreshDatabase; |
||||
|
|
||||
|
class IssueCommentsTest extends TestCase |
||||
|
{ |
||||
|
use RefreshDatabase; |
||||
|
|
||||
|
/** @test */ |
||||
|
public function user_can_view_issue_comments() |
||||
|
{ |
||||
|
$this->adminUserSigningIn(); |
||||
|
$issue = factory(Issue::class)->create(); |
||||
|
$comment = factory(Comment::class)->create([ |
||||
|
'commentable_type' => 'issues', |
||||
|
'commentable_id' => $issue->id, |
||||
|
'body' => 'This is issue comment.', |
||||
|
]); |
||||
|
|
||||
|
$this->visitRoute('projects.issues.show', [$issue->project, $issue]); |
||||
|
|
||||
|
$this->seeText('This is issue comment.'); |
||||
|
} |
||||
|
|
||||
|
/** @test */ |
||||
|
public function admin_can_add_comment_to_an_issue() |
||||
|
{ |
||||
|
$admin = $this->adminUserSigningIn(); |
||||
|
$issue = factory(Issue::class)->create(); |
||||
|
|
||||
|
$this->visitRoute('projects.issues.show', [$issue->project, $issue]); |
||||
|
|
||||
|
$this->submitForm(__('comment.create'), [ |
||||
|
'body' => 'First comment.', |
||||
|
]); |
||||
|
|
||||
|
$this->seePageIs(route('projects.issues.show', [$issue->project, $issue])); |
||||
|
$this->see(__('comment.created')); |
||||
|
|
||||
|
$this->seeInDatabase('comments', [ |
||||
|
'commentable_type' => 'issues', |
||||
|
'commentable_id' => $issue->id, |
||||
|
'body' => 'First comment.', |
||||
|
'creator_id' => $admin->id, |
||||
|
]); |
||||
|
} |
||||
|
|
||||
|
/** @test */ |
||||
|
public function user_can_edit_an_issue_comment() |
||||
|
{ |
||||
|
$this->adminUserSigningIn(); |
||||
|
$issue = factory(Issue::class)->create(); |
||||
|
$comment = factory(Comment::class)->create([ |
||||
|
'commentable_type' => 'issues', |
||||
|
'commentable_id' => $issue->id, |
||||
|
'body' => 'This is issue comment.', |
||||
|
]); |
||||
|
|
||||
|
$this->visitRoute('projects.issues.show', [$issue->project, $issue]); |
||||
|
$this->seeElement('a', ['id' => 'edit-comment-'.$comment->id]); |
||||
|
$this->click('edit-comment-'.$comment->id); |
||||
|
$this->seeRouteIs('projects.issues.show', [$issue->project, $issue, 'action' => 'comment-edit', 'comment_id' => $comment->id]); |
||||
|
|
||||
|
$this->submitForm(__('comment.update'), [ |
||||
|
'body' => 'Edited comment.', |
||||
|
]); |
||||
|
|
||||
|
$this->seePageIs(route('projects.issues.show', [$issue->project, $issue])); |
||||
|
$this->see(__('comment.updated')); |
||||
|
|
||||
|
$this->seeInDatabase('comments', [ |
||||
|
'id' => $comment->id, |
||||
|
'commentable_type' => 'issues', |
||||
|
'commentable_id' => $issue->id, |
||||
|
'body' => 'Edited comment.', |
||||
|
]); |
||||
|
} |
||||
|
|
||||
|
/** @test */ |
||||
|
public function user_can_delete_an_issue_comment() |
||||
|
{ |
||||
|
$this->adminUserSigningIn(); |
||||
|
$issue = factory(Issue::class)->create(); |
||||
|
$comment = factory(Comment::class)->create([ |
||||
|
'commentable_type' => 'issues', |
||||
|
'commentable_id' => $issue->id, |
||||
|
'body' => 'This is issue comment.', |
||||
|
]); |
||||
|
|
||||
|
$this->visitRoute('projects.issues.show', [$issue->project, $issue]); |
||||
|
$this->seeElement('button', ['id' => 'delete-comment-'.$comment->id]); |
||||
|
$this->press('delete-comment-'.$comment->id); |
||||
|
|
||||
|
$this->seePageIs(route('projects.issues.show', [$issue->project, $issue])); |
||||
|
$this->see(__('comment.deleted')); |
||||
|
|
||||
|
$this->dontSeeInDatabase('comments', [ |
||||
|
'id' => $comment->id, |
||||
|
]); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,219 @@ |
|||||
|
<?php |
||||
|
|
||||
|
namespace Tests\Feature\Projects; |
||||
|
|
||||
|
use Tests\TestCase; |
||||
|
use App\Entities\Projects\Issue; |
||||
|
use App\Entities\Projects\Project; |
||||
|
use Illuminate\Foundation\Testing\RefreshDatabase; |
||||
|
|
||||
|
class ProjectIssuesTest extends TestCase |
||||
|
{ |
||||
|
use RefreshDatabase; |
||||
|
|
||||
|
/** @test */ |
||||
|
public function user_can_view_project_issues() |
||||
|
{ |
||||
|
$this->adminUserSigningIn(); |
||||
|
$project = factory(Project::class)->create(); |
||||
|
$issue = factory(Issue::class)->create([ |
||||
|
'project_id' => $project->id, |
||||
|
'title' => 'The issue title.', |
||||
|
'body' => 'This is a project issue body.', |
||||
|
]); |
||||
|
|
||||
|
$this->visitRoute('projects.issues.index', $project); |
||||
|
$this->seeRouteIs('projects.issues.index', $project); |
||||
|
|
||||
|
$this->seeText('The issue title.'); |
||||
|
} |
||||
|
|
||||
|
/** @test */ |
||||
|
public function admin_can_add_issue_to_a_project() |
||||
|
{ |
||||
|
$admin = $this->adminUserSigningIn(); |
||||
|
$project = factory(Project::class)->create(); |
||||
|
|
||||
|
$this->visitRoute('projects.issues.create', $project); |
||||
|
|
||||
|
$this->submitForm(__('issue.create'), [ |
||||
|
'title' => 'First Issue.', |
||||
|
'body' => 'First Issue description.', |
||||
|
'priority_id' => 1, |
||||
|
'pic_id' => $admin->id, |
||||
|
]); |
||||
|
|
||||
|
$this->seePageIs(route('projects.issues.index', $project)); |
||||
|
$this->see(__('issue.created')); |
||||
|
|
||||
|
$this->seeInDatabase('issues', [ |
||||
|
'project_id' => $project->id, |
||||
|
'title' => 'First Issue.', |
||||
|
'body' => 'First Issue description.', |
||||
|
'priority_id' => 1, |
||||
|
'pic_id' => $admin->id, |
||||
|
'creator_id' => $admin->id, |
||||
|
]); |
||||
|
} |
||||
|
|
||||
|
/** @test */ |
||||
|
public function user_can_view_an_issue_detail() |
||||
|
{ |
||||
|
$this->adminUserSigningIn(); |
||||
|
$project = factory(Project::class)->create(); |
||||
|
$issue = factory(Issue::class)->create([ |
||||
|
'project_id' => $project->id, |
||||
|
'title' => 'The issue title.', |
||||
|
'body' => 'This is a project issue body.', |
||||
|
]); |
||||
|
|
||||
|
$this->visitRoute('projects.issues.show', [$project, $issue]); |
||||
|
$this->seeText($issue->title); |
||||
|
$this->seeText($issue->body); |
||||
|
} |
||||
|
|
||||
|
/** @test */ |
||||
|
public function user_can_edit_issue() |
||||
|
{ |
||||
|
$this->adminUserSigningIn(); |
||||
|
$project = factory(Project::class)->create(); |
||||
|
$issue = factory(Issue::class)->create([ |
||||
|
'project_id' => $project->id, |
||||
|
'title' => 'The issue title.', |
||||
|
'body' => 'This is a project issue body.', |
||||
|
]); |
||||
|
|
||||
|
$this->visitRoute('projects.issues.show', [$project, $issue]); |
||||
|
$this->seeElement('a', ['id' => 'edit-issue-'.$issue->id]); |
||||
|
$this->click('edit-issue-'.$issue->id); |
||||
|
$this->seeRouteIs('projects.issues.edit', [$project, $issue]); |
||||
|
|
||||
|
$this->submitForm(__('issue.update'), [ |
||||
|
'title' => 'First Issue.', |
||||
|
'body' => 'This is a project issue body.', |
||||
|
]); |
||||
|
|
||||
|
$this->seePageIs(route('projects.issues.show', [$project, $issue])); |
||||
|
$this->see(__('issue.updated')); |
||||
|
|
||||
|
$this->seeInDatabase('issues', [ |
||||
|
'id' => $issue->id, |
||||
|
'project_id' => $project->id, |
||||
|
'title' => 'First Issue.', |
||||
|
'body' => 'This is a project issue body.', |
||||
|
]); |
||||
|
} |
||||
|
|
||||
|
/** @test */ |
||||
|
public function user_can_delete_issue() |
||||
|
{ |
||||
|
$this->adminUserSigningIn(); |
||||
|
$project = factory(Project::class)->create(); |
||||
|
$issue = factory(Issue::class)->create([ |
||||
|
'project_id' => $project->id, |
||||
|
]); |
||||
|
|
||||
|
$this->visitRoute('projects.issues.edit', [$project, $issue]); |
||||
|
$this->seeElement('a', ['id' => 'delete-issue-'.$issue->id]); |
||||
|
|
||||
|
$this->click('delete-issue-'.$issue->id); |
||||
|
|
||||
|
$this->seePageIs(route('projects.issues.edit', [$project, $issue, 'action' => 'delete'])); |
||||
|
$this->seeElement('button', ['id' => 'delete-issue-'.$issue->id]); |
||||
|
|
||||
|
$this->press('delete-issue-'.$issue->id); |
||||
|
|
||||
|
$this->seePageIs(route('projects.issues.index', $project)); |
||||
|
$this->seeText(__('issue.deleted')); |
||||
|
|
||||
|
$this->dontSeeInDatabase('issues', [ |
||||
|
'id' => $issue->id, |
||||
|
]); |
||||
|
} |
||||
|
|
||||
|
/** @test */ |
||||
|
public function user_can_assign_someone_to_an_issue_as_pic() |
||||
|
{ |
||||
|
$this->adminUserSigningIn(); |
||||
|
$worker = $this->createUser('worker'); |
||||
|
$issue = factory(Issue::class)->create(); |
||||
|
|
||||
|
$this->visitRoute('projects.issues.show', [$issue->project, $issue]); |
||||
|
$this->submitForm(__('issue.update'), [ |
||||
|
'pic_id' => $worker->id, |
||||
|
]); |
||||
|
$this->seeRouteIs('projects.issues.show', [$issue->project, $issue]); |
||||
|
$this->seeText(__('issue.updated')); |
||||
|
|
||||
|
$this->seeInDatabase('issues', [ |
||||
|
'id' => $issue->id, |
||||
|
'pic_id' => $worker->id, |
||||
|
]); |
||||
|
} |
||||
|
|
||||
|
/** @test */ |
||||
|
public function user_can_remove_pic_assignment() |
||||
|
{ |
||||
|
$this->adminUserSigningIn(); |
||||
|
$worker = $this->createUser('worker'); |
||||
|
$issue = factory(Issue::class)->create(['pic_id' => $worker->id]); |
||||
|
|
||||
|
$this->visitRoute('projects.issues.show', [$issue->project, $issue]); |
||||
|
$this->submitForm(__('issue.update'), [ |
||||
|
'pic_id' => null, |
||||
|
]); |
||||
|
$this->seeRouteIs('projects.issues.show', [$issue->project, $issue]); |
||||
|
$this->seeText(__('issue.updated')); |
||||
|
|
||||
|
$this->seeInDatabase('issues', [ |
||||
|
'id' => $issue->id, |
||||
|
'pic_id' => null, |
||||
|
]); |
||||
|
} |
||||
|
|
||||
|
/** @test */ |
||||
|
public function user_can_change_issue_status() |
||||
|
{ |
||||
|
$this->adminUserSigningIn(); |
||||
|
$worker = $this->createUser('worker'); |
||||
|
$issue = factory(Issue::class)->create(); |
||||
|
|
||||
|
$this->visitRoute('projects.issues.show', [$issue->project, $issue]); |
||||
|
$this->submitForm(__('issue.update'), [ |
||||
|
'status_id' => 2, // resolved
|
||||
|
'pic_id' => $worker->id, |
||||
|
]); |
||||
|
$this->seeRouteIs('projects.issues.show', [$issue->project, $issue]); |
||||
|
$this->seeText(__('issue.updated')); |
||||
|
|
||||
|
$this->seeInDatabase('issues', [ |
||||
|
'id' => $issue->id, |
||||
|
'pic_id' => $worker->id, |
||||
|
'status_id' => 2, // resolved
|
||||
|
]); |
||||
|
} |
||||
|
|
||||
|
/** @test */ |
||||
|
public function user_can_change_issue_priority() |
||||
|
{ |
||||
|
$this->adminUserSigningIn(); |
||||
|
$worker = $this->createUser('worker'); |
||||
|
$issue = factory(Issue::class)->create(); |
||||
|
|
||||
|
$this->visitRoute('projects.issues.show', [$issue->project, $issue]); |
||||
|
$this->submitForm(__('issue.update'), [ |
||||
|
'priority_id' => 2, // major
|
||||
|
'status_id' => 2, // resolved
|
||||
|
'pic_id' => $worker->id, |
||||
|
]); |
||||
|
$this->seeRouteIs('projects.issues.show', [$issue->project, $issue]); |
||||
|
$this->seeText(__('issue.updated')); |
||||
|
|
||||
|
$this->seeInDatabase('issues', [ |
||||
|
'id' => $issue->id, |
||||
|
'pic_id' => $worker->id, |
||||
|
'priority_id' => 2, // major
|
||||
|
'status_id' => 2, // resolved
|
||||
|
]); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,99 @@ |
|||||
|
<?php |
||||
|
|
||||
|
namespace Tests\Unit\Models; |
||||
|
|
||||
|
use Tests\TestCase; |
||||
|
use App\Entities\Users\User; |
||||
|
use App\Entities\Projects\Issue; |
||||
|
use App\Entities\Projects\Comment; |
||||
|
use App\Entities\Projects\Project; |
||||
|
use Illuminate\Support\Collection; |
||||
|
use App\Entities\Projects\Priority; |
||||
|
use Illuminate\Foundation\Testing\RefreshDatabase; |
||||
|
|
||||
|
class IssueTest extends TestCase |
||||
|
{ |
||||
|
use RefreshDatabase; |
||||
|
|
||||
|
/** @test */ |
||||
|
public function an_issue_has_belongs_to_project_relation() |
||||
|
{ |
||||
|
$issue = factory(Issue::class)->make(); |
||||
|
|
||||
|
$this->assertInstanceOf(Project::class, $issue->project); |
||||
|
$this->assertEquals($issue->project_id, $issue->project->id); |
||||
|
} |
||||
|
|
||||
|
/** @test */ |
||||
|
public function an_issue_has_belongs_to_pic_relation() |
||||
|
{ |
||||
|
$pic = $this->createUser('worker'); |
||||
|
$issue = factory(Issue::class)->make(['pic_id' => $pic->id]); |
||||
|
|
||||
|
$this->assertInstanceOf(User::class, $issue->pic); |
||||
|
$this->assertEquals($issue->pic_id, $issue->pic->id); |
||||
|
} |
||||
|
|
||||
|
/** @test */ |
||||
|
public function issue_pic_name_has_default_value() |
||||
|
{ |
||||
|
$issue = factory(Issue::class)->make(['pic_id' => null]); |
||||
|
|
||||
|
$this->assertEquals(__('issue.no_pic'), $issue->pic->name); |
||||
|
} |
||||
|
|
||||
|
/** @test */ |
||||
|
public function an_issue_has_belongs_to_creator_relation() |
||||
|
{ |
||||
|
$issue = factory(Issue::class)->make(); |
||||
|
|
||||
|
$this->assertInstanceOf(User::class, $issue->creator); |
||||
|
$this->assertEquals($issue->creator_id, $issue->creator->id); |
||||
|
} |
||||
|
|
||||
|
/** @test */ |
||||
|
public function an_issue_has_status_attribute() |
||||
|
{ |
||||
|
$issue = factory(Issue::class)->make(); |
||||
|
|
||||
|
$this->assertEquals(__('issue.open'), $issue->status); |
||||
|
} |
||||
|
|
||||
|
/** @test */ |
||||
|
public function an_issue_has_status_label_attribute() |
||||
|
{ |
||||
|
$issue = factory(Issue::class)->make(); |
||||
|
|
||||
|
$this->assertEquals('<span class="badge">'.$issue->status.'</span>', $issue->status_label); |
||||
|
} |
||||
|
|
||||
|
/** @test */ |
||||
|
public function an_issue_has_priority_attribute() |
||||
|
{ |
||||
|
$issue = factory(Issue::class)->make(); |
||||
|
|
||||
|
$this->assertEquals(__('issue.minor'), $issue->priority); |
||||
|
} |
||||
|
|
||||
|
/** @test */ |
||||
|
public function an_issue_has_priority_label_attribute() |
||||
|
{ |
||||
|
$issue = factory(Issue::class)->make(); |
||||
|
$colorClass = Priority::getColorById($issue->priority_id); |
||||
|
|
||||
|
$this->assertEquals('<span class="label label-'.$colorClass.'">'.$issue->priority.'</span>', $issue->priority_label); |
||||
|
} |
||||
|
|
||||
|
/** @test */ |
||||
|
public function an_issue_has_many_comments_relation() |
||||
|
{ |
||||
|
$issue = factory(Issue::class)->create(); |
||||
|
$comment = factory(Comment::class)->create([ |
||||
|
'commentable_type' => 'issues', |
||||
|
'commentable_id' => $issue->id, |
||||
|
]); |
||||
|
|
||||
|
$this->assertInstanceOf(Collection::class, $issue->comments); |
||||
|
$this->assertInstanceOf(Comment::class, $issue->comments->first()); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,34 @@ |
|||||
|
<?php |
||||
|
|
||||
|
namespace Tests\Unit\Policies; |
||||
|
|
||||
|
use Tests\TestCase; |
||||
|
use App\Entities\Projects\Issue; |
||||
|
use Illuminate\Foundation\Testing\RefreshDatabase; |
||||
|
|
||||
|
/** |
||||
|
* Issue Policy Test. |
||||
|
* |
||||
|
* @author Nafies Luthfi <nafiesl@gmail.com> |
||||
|
*/ |
||||
|
class IssuePolicyTest extends TestCase |
||||
|
{ |
||||
|
use RefreshDatabase; |
||||
|
|
||||
|
/** @test */ |
||||
|
public function admin_can_create_issue() |
||||
|
{ |
||||
|
$admin = $this->createUser('admin'); |
||||
|
|
||||
|
$this->assertTrue($admin->can('create', new Issue())); |
||||
|
} |
||||
|
|
||||
|
/** @test */ |
||||
|
public function admin_can_add_comment_to_an_issue() |
||||
|
{ |
||||
|
$admin = $this->createUser('admin'); |
||||
|
$issue = factory(Issue::class)->create(); |
||||
|
|
||||
|
$this->assertTrue($admin->can('comment-on', $issue)); |
||||
|
} |
||||
|
} |
||||
Write
Preview
Loading…
Cancel
Save
Reference in new issue