<?php
namespace App\Http\Controllers;
use App\Services\RepositoryService;
use App\Models\PullRequest;
use App\Helpers\DiffRenderer;
use App\GithubConfig;
class PullRequestController extends Controller
{
public function metadata($organizationName, $repositoryName)
{
[$organization, $repository] = RepositoryService::getRepositoryWithOrganization($organizationName, $repositoryName);
$branches = $repository->branches()->get();
$branchNames = $branches->pluck('name');
$assignees = $repository->contributors()->with('githubUser')->get()->map(function ($contributor) {
return $contributor->githubUser;
});
$master_branch = $repository->master_branch;
$default_assignee = GithubConfig::USERID;
return response()->json([
'branches' => $branchNames,
'assignees' => $assignees,
'default_assignee' => $default_assignee,
'master_branch' => $master_branch,
]);
}
}
$q->orderBy('created_at', 'desc')->limit(1);
$q->orderBy('created_at', 'desc')->limit(1);
}]);
}]);
if (!$branch->commits->isEmpty()) {
$branch->last_commit = $branch->commits->first();
$branch->last_commit = $branch->commits->first();
$branch->last_commit->created_at_human = $branch->last_commit->created_at->diffForHumans();
$branch->last_commit->created_at_human = $branch->last_commit->created_at->diffForHumans();
}
$branchesForNotices[] = $branch;
$branchesForNotices[] = $branch;
}
}
'private' => $repoData->private,
'private' => $repoData->private,
'description' => $repoData->description ?? '',
'description' => $repoData->description ?? '',
'last_updated' => now(),
'last_updated' => now(),
'master_branch' => $repoData->default_branch,
]
]
);
);
}
}
'description',
'description',
'pr_count',
'pr_count',
'issue_count',
'issue_count',
'master_branch',
];
];
protected $casts = [
protected $casts = [
<?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('repositories', function (Blueprint $table) {
$table->string('master_branch')->nullable();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('repositories', function (Blueprint $table) {
$table->dropColumn('master_branch');
});
}
};
import RepositoryDashboard from './RepositoryDashboard.svelte';
import RepositoryDashboard from './RepositoryDashboard.svelte';
import ItemOverview from './itemOverview/ItemOverview.svelte';
import ItemOverview from './itemOverview/ItemOverview.svelte';
import Item from './item/Item.svelte';
import Item from './item/Item.svelte';
import NewPullRequest from './item/pr/NewPullRequest.svelte';
import theme from '../theme.js';
import theme from '../theme.js';
const routes = {
const routes = {
'/:organization/:repository/issues': ItemOverview,
'/:organization/:repository/issues': ItemOverview,
'/:organization/:repository/issues/:number': Item,
'/:organization/:repository/issues/:number': Item,
'/:organization/:repository/prs': ItemOverview,
'/:organization/:repository/prs': ItemOverview,
'/:organization/:repository/new/pr/:branch': NewPullRequest,
'/:organization/:repository/prs/:number': Item,
'/:organization/:repository/prs/:number': Item,
};
};
<script>
let { name = 'input', label = "Label", value = $bindable(), placeholder = ' ', onChange } = $props();
function handleInput(event) {
value = event.target.value;
onChange?.({ value });
}
</script>
<div class="input-wrapper">
<label for={name}>{label}</label>
<input {name} {placeholder} bind:value={value} oninput={handleInput}>
</div>
<style lang="scss">
@import "../../scss/components/input";
</style>
<script>
import { onMount } from 'svelte';
import Sidebar from '../../sidebar/Sidebar.svelte';
import SidebarGroup from '../../sidebar/group.svelte';
import Select from '../../Select.svelte';
import Input from '../../Input.svelte';
let { params = {} } = $props();
let head_branch = $state(params.branch);
let base_branch = $state('');
let possibleBranches = $state([]);
let title = $state('');
let assignee = $state();
let possibleAssignees = $state([]);
let loading = $state(false);
onMount(async () => {
// Load branches for the repository to populate the Select components
loading = true;
const res = await fetch(route(`organizations.repositories.pr.metadata`, { organization: params.organization, repository: params.repository }));
const data = await res.json();
// Ensure options are in { value, label } shape expected by <Select>
possibleBranches = (data.branches || []).map((b) => ({ value: b, label: b }));
possibleAssignees = (data.assignees || []).map((a) => ({ value: a.id, label: a.display_name }));
assignee = data.default_assignee;
base_branch = data.master_branch;
loading = false;
});
</script>
<div class="new-pr">
<Sidebar {params} selectedDropdownSection="New PR">
<SidebarGroup title="Branch to merge">
<Select name="head_branch" value={head_branch} selectableItems={possibleBranches} bind:selectedValue={head_branch} />
</SidebarGroup>
<SidebarGroup title="Branch to merge into">
<Select name="base_branch" value={base_branch} selectableItems={possibleBranches} bind:selectedValue={base_branch} />
</SidebarGroup>
<SidebarGroup title="Assignee">
<Select name="assignee" value={assignee} selectableItems={possibleAssignees} bind:selectedValue={assignee} />
</SidebarGroup>
</Sidebar>
<div class="new-pr-main">
<Input name="title" label="Title" bind:value={title} />
</div>
</div>
<style lang="scss">
@import '../../../../scss/components/item/pr/new-pr.scss';
</style>
{:else}
{:else}
{#each branchesForNotice as branch}
{#each branchesForNotice as branch}
<PrNotice item={branch} />
<PrNotice item={branch} {params} />
{/each}
{/each}
<script>
<script>
let { item } = $props();
let { item, params } = $props();
const org = params.organization;
const repo = params.repository;
</script>
</script>
<div class="pr-notice">
<div class="pr-notice">
<div>
<b>{item.name}</b> had recent pushes
<b>{item.name}</b> had recent pushes
{#if item.last_commit}
{#if item.last_commit}
{item.last_commit.created_at_human}
{item.last_commit.created_at_human}
{/if}
{/if}
</div>
</div>
<a href="#/{org}/{repo}/new/pr/{item.name}" class="create-pr button-primary">Create PR</a>
</div>
<style lang="scss">
<style lang="scss">
@import '../../../scss/components/pr-notice';
@import '../../../scss/components/pr-notice';
.input-wrapper {
position: relative;
margin: 1.5rem 0 1rem 0;
label {
position: absolute;
left: 0.75rem;
top: 0.75rem !important;
font-size: 14px !important;
color: var(--text-color-secondary);
background-color: var(--background-color-one);
padding: 0 0.25rem;
transform-origin: left top;
cursor: pointer;
transition: all 0.2s ease-in-out;
}
&:has(input:focus),
&:has(input:not(:placeholder-shown)),
&:has(input[data-filled="true"]),
&:has(select:focus),
&:has(select:not(:invalid)) {
label {
left: 0.5rem;
transform: scale(0.8) translateY(-1.25rem);
}
}
input {
width: 100%;
padding: 0.5rem 1rem;
height: 1.5rem;
border-radius: 0.5rem;
border: 1px solid var(--border-color);
background: var(--background-color-one);
outline: none;
cursor: pointer;
&:focus {
border-color: var(--primary-color);
}
}
}
.new-pr {
height: 100%;
display: flex;
gap: 1rem;
overflow: auto;
}
border-radius: 1rem;
border-radius: 1rem;
border: 2px solid var(--primary-color-dark);
border: 2px solid var(--primary-color-dark);
color: var(--text-color-secondary);
color: var(--text-color-secondary);
display: flex;
justify-content: space-between;
align-items: center;
b {
b {
color: var(--text-color-secondary);
color: var(--text-color-secondary);
}
}
.create-pr {
text-decoration: none;
height: fit-content;
}
}
}
use App\Http\Middleware\IsLoggedIn;
use App\Http\Middleware\IsLoggedIn;
use Illuminate\Support\Facades\Route;
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\ItemController;
use App\Http\Controllers\ItemController;
use App\Http\Controllers\PullRequestController;
use App\Http\Controllers\ItemCommentController;
use App\Http\Controllers\ItemCommentController;
Route::middleware(IsLoggedIn::class)->group(function () {
Route::middleware(IsLoggedIn::class)->group(function () {
Route::get('/org/{organization}/repo/{repository}/branches/pr/notices', [RepositoryController::class, 'getBranchesForPRNotices'])
Route::get('/org/{organization}/repo/{repository}/branches/pr/notices', [RepositoryController::class, 'getBranchesForPRNotices'])
->name('organizations.repositories.branches.pr.notices');
->name('organizations.repositories.branches.pr.notices');
Route::get('/org/{organization}/repo/{repository}/pr/metadata', [PullRequestController::class, 'metadata'])
->name('organizations.repositories.pr.metadata');
});
});
Route::any('incoming_hook', [IncomingWebhookController::class, 'index'])
Route::any('incoming_hook', [IncomingWebhookController::class, 'index'])