Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merge Idea + enhancements #9

Open
wants to merge 21 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 56 additions & 4 deletions app/Http/Controllers/Admin/FeedbackController.php
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,33 @@ public function index(Request $request)
]);
}

public function search(Request $request)
{
$search = $request->input('search');
$parent_id = $request->input('parent_id') ?? null;
if (!$request->has('search')) {
return response()->json([]);
}

$query = Post::where('merged_with_post', null);
$query->where('title', 'like', '%' . $search . '%');
if ($parent_id) {
$query->where('id', '!=', $parent_id);
}

$query->orderBy('vote', 'desc');

$response = $query->get();

return response()->json($response);
}

public function show(Post $post)
{
$post->load('creator', 'board', 'status', 'by');
if ($post->merged_with_post) {
$post->merged_with_post = Post::find($post->merged_with_post);
}

$boards = Board::select('id', 'name', 'posts', 'slug')->get();
$statuses = Status::select('id', 'name', 'color')->get();
Expand Down Expand Up @@ -141,17 +165,17 @@ public function update(Request $request, Post $post)
'body' => $request->input('comment') ?? '',
'status_id' => $request->input('status_id'),
]);

if ($request->input('notify') === true) {
$this->notify($post);
}
}

$post->update([
'board_id' => $request->input('board_id'),
'status_id' => $request->input('status_id'),
]);

if ($request->input('notify') === true) {
$this->notify($post);
}

return redirect()->back()->with('success', 'Feedback updated successfully.');
}

Expand Down Expand Up @@ -195,4 +219,32 @@ public function addVote(Post $post, Request $request)

return redirect()->back()->with('success', 'Vote added successfully.');
}

public function merge(Request $request)
{
$request->validate([
'post_id' => 'required|exists:posts,id',
'merge_ids' => 'required|array|exists:posts,id',
'status_id' => 'required|exists:statuses,id',
]);

try {
$post = Post::where('id', $request->post_id);
$post->increment('comments', Post::whereIn('id', $request->merge_ids)->pluck( 'comments' )->sum() );
$post->increment('vote', Post::whereIn('id', $request->merge_ids)->pluck( 'vote' )->sum() );

Post::whereIn('id', $request->merge_ids)
->update([
'merged_with_post' => $request->post_id,
'status_id' => $request->status_id,
'comments' => 0,
'vote' => 0,
]);

return redirect()->back()->with('success', 'Posts merged successfully.');
} catch (\Exception $e) {
return redirect()->back()->with('error', 'Post not found.');

}
}
}
4 changes: 4 additions & 0 deletions app/Http/Controllers/Frontend/BoardController.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ public function show(Request $request, Board $board)
]);
}

if ($request->has('search') && $request->search) {
$postsQuery->where('title', 'like', '%' . $request->search . '%');
}
RatulHasan marked this conversation as resolved.
Show resolved Hide resolved

if ($request->has('sort') && in_array($request->sort, array_keys($sortFields))) {
$orderBy = $sortFields[$request->sort];
}
Expand Down
8 changes: 7 additions & 1 deletion app/Http/Controllers/Frontend/CommentController.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,13 @@ public function index(Request $request, Post $post)
$sort = $request->has('sort') && in_array($request->sort, ['latest', 'oldest']) ? $request->sort : 'oldest';
$orderBy = ($sort === 'latest') ? 'desc' : 'asc';

$comments = $post->comments()->with('user', 'status')->orderBy('created_at', $orderBy)->get();
// Search if there are any merged posts with the post id
$mergedPost = Post::where('merged_with_post', $post->id)->get();
$postIds = $mergedPost->pluck('id')->push($post->id);
$comments = Comment::whereIn('post_id', $postIds)
->with('user', 'status')
->orderBy('created_at', $orderBy)
->get();
Comment on lines +22 to +28
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tip

Codebase Verification

No unit tests were found covering the comment fetching functionality for merged posts. Please add unit tests to ensure this functionality is properly tested.

  • Ensure that the CommentController's logic for fetching comments from both the original and merged posts is covered by unit tests.
Analysis chain

The logic to fetch comments from both the original and merged posts is correctly implemented, ensuring that all relevant comments are displayed. Verify that this functionality is covered by unit tests.

Scripts Executed

The following scripts were executed for the analysis:


Script:

#!/bin/bash
# Description: Verify that the comment fetching functionality for merged posts is covered by unit tests.

# Test: Search for unit tests covering comment fetching for merged posts. Expect: At least one test case.
rg --type php $'test.*comments.*merged'

Length of output: 40



Script:

#!/bin/bash
# Description: Verify that the comment fetching functionality for merged posts is covered by unit tests.

# Search for test files that might be testing the CommentController
test_files=$(fd --type f --name '*Test.php')

# Check each test file for references to CommentController and merged posts
for file in $test_files; do
  echo "Checking $file for CommentController and merged posts..."
  rg -H 'CommentController' "$file" | rg 'merged'
done

Length of output: 298



Script:

#!/bin/bash
# Description: Verify that the comment fetching functionality for merged posts is covered by unit tests.

# Search for test files that might be testing the CommentController
test_files=$(fd --type f --glob '*Test.php')

# Check each test file for references to CommentController and merged posts
for file in $test_files; do
  echo "Checking $file for CommentController and merged posts..."
  rg -H 'CommentController' "$file" | rg 'merged'
done

Length of output: 3003


// Group comments by parent_id
$groupedComments = $comments->groupBy('parent_id');
Expand Down
3 changes: 3 additions & 0 deletions app/Http/Controllers/Frontend/PostController.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ public function show(Board $board, $post)
$post->load('creator');

$post->body = Formatting::transformBody($post->body);
if ($post->merged_with_post) {
$post->merged_with_post = Post::with('board')->find($post->merged_with_post);
}
Comment on lines +21 to +23
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tip

Codebase Verification

The search results indicate that there are no unit tests specifically covering the merged_with_post functionality in the PostController. The references found are primarily in migration files and other controllers, but no test files were identified.

To ensure the robustness of the new functionality, it is crucial to add unit tests that cover the merged_with_post logic in the PostController.

  • Location to add tests: Ensure that the PostController's show method, which handles the merged_with_post logic, is covered by unit tests.
Analysis chain

The logic to load additional data for posts merged with other posts is correctly implemented. Ensure that this functionality is covered by unit tests.

Scripts Executed

The following scripts were executed for the analysis:


Script:

#!/bin/bash
# Description: Verify that the merged post loading functionality is covered by unit tests.

# Test: Search for unit tests covering merged post loading. Expect: At least one test case.
rg --type php $'test.*merged_with_post'

Length of output: 40



Script:

#!/bin/bash
# Description: Verify that the merged post loading functionality is covered by unit tests.

# Test: Search for any unit tests related to PostController.
rg --type php 'PostController'

# Test: Search for any unit tests that might cover merged_with_post functionality indirectly.
rg --type php 'merged_with_post'

Length of output: 1559


$data = [
'post' => $post,
Expand Down
7 changes: 4 additions & 3 deletions app/Models/Post.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ class Post extends Model
'eta',
'impact',
'effort',
'created_by'
'created_by',
'merged_with_post',
];

protected static function boot()
Expand Down Expand Up @@ -63,7 +64,7 @@ public function board()

public function status()
{
return $this->hasOne(Status::class, 'id', 'status_id');
return $this->hasOne(Status::class, 'id', 'status_id')->withDefault();
}

public function votes()
Expand Down Expand Up @@ -98,7 +99,7 @@ public function scopeWithVote($query)
'has_voted' => Vote::selectRaw('count(*)')
->whereColumn('post_id', 'posts.id')
->where('user_id', $userId)
->take(1)
->take(1),
]);
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('posts', function (Blueprint $table) {
$table->foreignId('merged_with_post')->nullable()->constrained('posts')->onDelete('set null');
});
}

/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('posts', function (Blueprint $table) {
$table->dropConstrainedForeignId('merged_with_post');
});
}
};
56 changes: 56 additions & 0 deletions resources/js/Components/ActionMenu.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { Fragment } from 'react'
import { Menu, Transition } from '@headlessui/react'
import { EllipsisVerticalIcon } from '@heroicons/react/20/solid'
import classNames from 'classnames';

type ActionMenuProps = {
menuItems: {
label: string;
onClick: () => void;
}[];
menuName?: string;
};

export default function ActionMenu({ menuItems, menuName = 'Actions' }: ActionMenuProps) {
return (
<Menu as="div" className="relative inline-block text-left">
<div>
<Menu.Button className="flex items-center text-gray-400 hover:text-gray-600 focus:outline-none dark:bg-gray-800 dark:text-gray-300 dark:hover:text-gray-200">
<span className="sr-only">Open options</span>
<span>{menuName}</span>
<EllipsisVerticalIcon className="h-5 w-5" aria-hidden="true" />
</Menu.Button>
</div>

<Transition
as={Fragment}
enter="transition ease-out duration-100"
enterFrom="transform opacity-0 scale-95"
enterTo="transform opacity-100 scale-100"
leave="transition ease-in duration-75"
leaveFrom="transform opacity-100 scale-100"
leaveTo="transform opacity-0 scale-95"
>
<Menu.Items className="absolute right-0 z-10 mt-2 w-56 origin-top-right rounded-md bg-white shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none dark:bg-slate-800 dark:ring-gray-700">
<div className="py-1">
{menuItems.map((item) => (
<Menu.Item key={item.label}>
{({ active }) => (
<button
onClick={item.onClick}
className={classNames(
active ? 'bg-gray-100 text-gray-900 dark:bg-gray-700 dark:text-gray-300' : 'text-gray-700 dark:text-gray-300',
'block px-4 py-2 text-sm w-full text-left dark:hover:bg-slate-700'
)}
>
{item.label}
</button>
)}
</Menu.Item>
))}
</div>
</Menu.Items>
</Transition>
</Menu>
)
}
43 changes: 23 additions & 20 deletions resources/js/Components/Comments.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useEffect, useState } from 'react';
import React, { useCallback, useEffect, useState } from 'react';
import { Link, usePage } from '@inertiajs/react';

import CommentBox from './CommentBox';
Expand All @@ -16,29 +16,32 @@ const Comments: React.FC<CommentsProps> = ({ post }) => {
const [sort, setSort] = useState<'latest' | 'oldest'>('oldest');
const [isFetching, setIsFetching] = useState(false);

const fetchComments = async () => {
setIsFetching(true);
const fetchComments = useCallback( async () => {
setIsFetching(true);

try {
const response = await fetch(
route('post.comments.index', {
post: post.slug,
sort: sort,
})
);
if (post.merged_with_post){
return;
}
try {
const response = await fetch(
route('post.comments.index', {
post: post.slug,
sort: sort,
})
);

const data = await response.json();
setIsFetching(false);
setComments(data);
} catch (error) {
console.error('Error fetching comments:', error);
setIsFetching(false);
}
};
const data = await response.json();
setIsFetching(false);
setComments(data);
} catch (error) {
console.error('Error fetching comments:', error);
setIsFetching(false);
}
}, [sort, post]);

useEffect(() => {
fetchComments();
}, [sort]);
}, [sort, post]);

const appendToComments = (comment: CommentType) => {
setComments([...comments, comment]);
Expand Down Expand Up @@ -70,7 +73,7 @@ const Comments: React.FC<CommentsProps> = ({ post }) => {
<div className="flex items-center">
<div className="text-sm mr-2 dark:text-gray-400">Sort By</div>
<select
className="px-2 min-w-28 text-sm py-1.5 rounded border border-gray-200"
className="px-2 min-w-28 text-sm py-1.5 rounded border border-gray-200 dark:border-gray-700 dark:bg-slate-800 dark:text-gray-300"
value={sort}
onChange={(e) => setSort(e.target.value as 'latest' | 'oldest')}
>
Expand Down
16 changes: 8 additions & 8 deletions resources/js/Components/UserSearchDropdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,13 +56,13 @@ const UserSearchDropdown = ({
{user === null && (
<Combobox value={selectedUser} onChange={handleSelect}>
<Combobox.Input
className="w-full px-3 py-2 text-sm border border-gray-300 rounded-md shadow-sm focus:ring-indigo-500 focus:border-indigo-500"
className="w-full px-3 py-2 text-sm border border-gray-300 rounded-md shadow-sm focus:ring-indigo-500 focus:border-indigo-500 dark:bg-gray-800 dark:text-gray-300 dark:border-gray-700"
placeholder="Search for a user by name or email"
onKeyUp={handleSearch}
/>
<Combobox.Options className="absolute mt-1 max-h-60 w-full overflow-auto rounded-md bg-white py-1 text-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm">
<Combobox.Options className="absolute mt-1 max-h-60 w-full overflow-auto rounded-md bg-white py-1 text-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm dark:bg-gray-800 dark:text-gray-300 dark:ring-gray-700">
{users.length === 0 && query !== '' ? (
<div className="relative cursor-pointer select-none py-2 px-4 text-gray-700">
<div className="relative cursor-pointer select-none py-2 px-4 text-gray-700 dark:text-gray-300 dark:bg-gray-800">
<button
type="button"
className="inline-flex items-center"
Expand All @@ -78,7 +78,7 @@ const UserSearchDropdown = ({
key={user.id}
value={user}
className={({ active }) =>
`relative cursor-pointer select-none py-2 pl-10 pr-4 ${
`relative cursor-pointer select-none py-2 pl-10 pr-4 dark:bg-gray-800 dark:text-gray-300 ${
active ? 'bg-indigo-100 text-gray-700' : 'text-gray-900'
}`
}
Expand All @@ -89,11 +89,11 @@ const UserSearchDropdown = ({
<img
src={user.avatar}
alt={user.name}
className="h-6 w-6 flex-shrink-0 rounded-full"
className="h-6 w-6 flex-shrink-0 rounded-full dark:border-gray-700"
/>
<span
className={classNames(
'ml-3 truncate',
'ml-3 truncate dark:text-gray-300',
selected && 'font-semibold'
)}
>
Expand All @@ -103,7 +103,7 @@ const UserSearchDropdown = ({

{selected ? (
<span
className={`absolute inset-y-0 left-0 flex items-center pl-3 ${
className={`absolute inset-y-0 left-0 flex items-center pl-3 dark:text-gray-300 dark:bg-gray-800${
active ? 'text-white' : 'text-indigo-600'
}`}
>
Expand All @@ -120,7 +120,7 @@ const UserSearchDropdown = ({
)}

{user && (
<div className="mt-2 flex items-center justify-between bg-indigo-100 px-2 py-2 rounded border border-indigo-200">
<div className="mt-2 flex items-center justify-between bg-indigo-100 px-2 py-2 rounded border border-indigo-200 dark:bg-gray-800 dark:border-gray-700">
<div className="flex">
<img
src={user.avatar}
Expand Down
4 changes: 2 additions & 2 deletions resources/js/Pages/Admin/Feedbacks/Create.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -81,8 +81,8 @@ const CreateModal = ({
<form onSubmit={handleSubmit}>
<ModalHeader>Create Feedback</ModalHeader>
<ModalBody>
<div className="mb-4 border bg-slate-50 p-3 rounded">
<div className="block text-sm font-medium leading-6 text-gray-900 mb-2">
<div className="mb-4 border bg-slate-50 p-3 rounded dark:bg-slate-800 dark:border-gray-700 dark:text-gray-300">
<div className="block text-sm font-medium leading-6 text-gray-900 mb-2 dark:text-gray-200">
Post on behalf of a user (optional)
</div>
<UserSearchDropdown
Expand Down
Loading