Refactor branch fetching to use repo IDs instead of project IDs (Vibe Kanban) (#1560)
* All tasks are complete. Here's a summary of the changes made:
## Summary
Successfully refactored the project branches API to fetch branches using repo IDs instead of project IDs.
### Files Created
- `frontend/src/hooks/useRepoBranches.ts` - New hook that fetches branches for a single repository using `repoApi.getBranches(repoId)`
### Files Modified
1. **`frontend/src/hooks/index.ts`** - Added export for `useRepoBranches`
2. **`frontend/src/components/dialogs/tasks/GitActionsDialog.tsx`**
- Removed `useState` for `repoBranches` and `loadingBranches`
- Removed `useEffect` that called `projectsApi.getBranches(effectiveProjectId)`
- Removed `useMemo` that filtered branches by `selectedRepoId`
- Now uses `useRepoBranches(selectedRepoId)` to fetch branches directly for the selected repo from the task attempt
3. **`frontend/src/components/dialogs/tasks/CreatePRDialog.tsx`**
- Removed `projectId` from props interface and component destructuring
- Removed `useState` for `branches` and `branchesLoading`
- Removed branch-fetching logic from useEffect
- Now uses `useRepoBranches(repoId)` to fetch branches directly for the specified repo
- Split useEffect into two: one for form initialization, one for setting default base branch
4. **`frontend/src/components/tasks/Toolbar/GitOperations.tsx`**
- Removed `projectId` from `CreatePRDialog.show()` call
### Benefits
- More efficient - fetches only branches for the specific repo needed instead of all project repos
- Better caching - React Query caches branches per repo for efficient reuse
- Cleaner code - removed manual filtering logic from components
- Type safety - hooks return `GitBranch[]` directly instead of `RepositoryBranches[]`
* All tasks complete. Here's a summary of the changes made:
## Summary
Successfully decoupled the branches API from project IDs.
### Changes Made
**1. `frontend/src/hooks/useBranches.ts`** - Refactored to accept `repos: Array<{ id: string; name: string }>` instead of `projectId`
- Now uses `useQueries` to fetch branches for multiple repos in parallel
- Reuses the same query keys as `useRepoBranches` for cache consistency
**2. `frontend/src/hooks/useRepoBranchSelection.ts`** - Refactored to accept `repos: Repo[]` instead of `projectId`
- Removed internal fetch of project repos (caller now provides them)
- Removed `projectRepos` from return value
- Uses refactored `useBranches(repos)`
**3. `frontend/src/components/dialogs/tasks/CreateAttemptDialog.tsx`**
- Added `useQuery` to fetch project repos separately
- Passes `repos` to `useRepoBranchSelection`
**4. `frontend/src/components/dialogs/tasks/TaskFormDialog.tsx`**
- Already fetched `projectRepos`
- Updated to pass `projectRepos` to refactored `useBranches`
**5. `frontend/src/components/layout/Navbar.tsx`**
- Replaced `useBranches(projectId)` with `useQuery` for `projectsApi.getRepositories`
- Only needed repo count, not branches
**6. `frontend/src/components/projects/ProjectCard.tsx`**
- Replaced `useBranches(project.id)` with `useQuery` for `projectsApi.getRepositories`
- Only needed repo count, not branches
### Benefits
- Branch hooks no longer require a project ID
- Repos can come from any source (project, attempt, or future sources without projects)
- Better separation of concerns
- Enables future task attempts without projects attached
* Cleanup script changes for task attempt 6b5b9208-565d-4620-9fe3-20791db77d7a
* The fix is already applied. Type checks pass with `Repo[]` instead of the loose `Array<{ id: string; name: string }>` type.
This makes it explicit that `useBranches` expects actual `Repo` objects (with `Repo.id` being the repository's UUID), not project-repo junction records.
* All done. The refactor is complete:
**Summary of changes:**
1. **Created `useProjectRepos` hook** (`frontend/src/hooks/useProjectRepos.ts`) - A reusable hook that fetches project repositories with proper query key management
2. **Updated 5 files** to use the new hook instead of duplicating the `useQuery` pattern:
- `CreateAttemptDialog.tsx`
- `TaskFormDialog.tsx`
- `Navbar.tsx`
- `ProjectCard.tsx`
- `NoServerContent.tsx`
3. **Earlier refactors** (from before the summary):
- `useBranches.ts` - Now accepts `repos: Repo[]` instead of `projectId`
- `useRepoBranchSelection.ts` - Now accepts `repos: Repo[]` instead of `projectId`
This enables future task attempts without projects attached, as branch fetching is now decoupled from project IDs.
* No more usages. The branches in `ProjectTasks.tsx` are used for the diffs panel - showing available branches for git operations when viewing an attempt's diffs.
* Cleanup script changes for task attempt 6b5b9208-565d-4620-9fe3-20791db77d7a
* All done. The refactor is complete:
**Summary of changes:**
`GitOperations` now fetches its own branches using `useRepoBranches(selectedRepoId)` instead of receiving them as a prop. This:
1. Removes prop drilling through `ProjectTasks` → `DiffsPanelContainer` → `DiffsPanel` → `GitOperations`
2. Removes the need for `ProjectTasks` to fetch all project repos just to get branches for the selected repo
3. Makes `GitOperations` self-contained - it already has `selectedRepoId` from `useAttemptRepo`, so it can fetch its own branches
4. Simplifies `GitActionsDialog` which no longer needs to fetch branches either
* All done. Here's a summary of the changes:
**Removed `projectId` from GitOperations flow:**
1. **`useRebase.ts`** - Changed param from `projectId` to `repoId`, updated invalidation to use `repoBranchKeys.byRepo(repoId)`
2. **`useChangeTargetBranch.ts`** - Changed param from `projectId` to `repoId`, updated invalidation to use `repoBranchKeys.byRepo(repoId)`
3. **`useMerge.ts`** - Updated invalidation to use `repoBranchKeys.all` instead of `['projectBranches']`
4. **`useGitOperations.ts`** - Changed param from `projectId` to `repoId`
5. **`GitOperations.tsx`** - Removed `projectId` prop, now passes `selectedRepoId` to `useGitOperations`
6. **`GitActionsDialog.tsx`** - Removed `projectId` from props and internal logic
7. **`DiffsPanelContainer`** (in ProjectTasks.tsx) - Removed `projectId` prop
8. **Callers of `GitActionsDialog.show`** - Removed `projectId` from `NextActionCard.tsx` and `actions-dropdown.tsx`
* The cleanup is complete. Here's a summary of what was done:
**Removed dead project branches code:**
1. **Backend** (`crates/server/src/routes/projects.rs`):
- Removed `get_project_branches` handler and route
- Removed `RepositoryBranches` and `ProjectBranchesResponse` structs
- Fixed unused `Serialize` import warning
2. **Type Generation** (`crates/server/src/bin/generate_types.rs`):
- Removed exports for `ProjectBranchesResponse` and `RepositoryBranches`
3. **Frontend Hooks** (`frontend/src/hooks/useBranches.ts`):
- Defined local `RepositoryBranches` type instead of importing from shared/types
4. **Frontend API** (`frontend/src/lib/api.ts`):
- Removed `projectsApi.getBranches` method
- Removed `RepositoryBranches` and `ProjectBranchesResponse` imports
Both TypeScript and Rust compilation pass without errors.
* Done. Removed the `RepositoryBranches` type and simplified `useBranches` to return `Map<string, GitBranch[]>`. Changes:
1. **`useBranches.ts`** - Returns `Map<string, GitBranch[]>` instead of `RepositoryBranches[]`
2. **`useRepoBranchSelection.ts`** - Uses `branchMap.get(repo.id) ?? []` instead of `.find()`
3. **`TaskFormDialog.tsx`** - Uses `branchMap.get(repo.id) ?? []` instead of `.find()`
Type checks pass.
* Done. Deleted `useBranches` entirely and consolidated everything into `useRepoBranchSelection`. Summary:
1. **Deleted** `frontend/src/hooks/useBranches.ts`
2. **Updated** `useRepoBranchSelection.ts` - inlined the `useQueries` logic directly
3. **Updated** `hooks/index.ts` - removed `useBranches` export
4. **Updated** `TaskFormDialog.tsx` - now uses `useRepoBranchSelection` instead of duplicating the config-building logic
Type checks pass.
* types
This commit is contained in:
@@ -103,8 +103,6 @@ fn generate_types_content() -> String {
|
|||||||
server::routes::config::CheckEditorAvailabilityResponse::decl(),
|
server::routes::config::CheckEditorAvailabilityResponse::decl(),
|
||||||
server::routes::config::CheckAgentAvailabilityQuery::decl(),
|
server::routes::config::CheckAgentAvailabilityQuery::decl(),
|
||||||
server::routes::oauth::CurrentUserResponse::decl(),
|
server::routes::oauth::CurrentUserResponse::decl(),
|
||||||
server::routes::projects::RepositoryBranches::decl(),
|
|
||||||
server::routes::projects::ProjectBranchesResponse::decl(),
|
|
||||||
server::routes::task_attempts::CreateFollowUpAttempt::decl(),
|
server::routes::task_attempts::CreateFollowUpAttempt::decl(),
|
||||||
server::routes::task_attempts::ChangeTargetBranchRequest::decl(),
|
server::routes::task_attempts::ChangeTargetBranchRequest::decl(),
|
||||||
server::routes::task_attempts::ChangeTargetBranchResponse::decl(),
|
server::routes::task_attempts::ChangeTargetBranchResponse::decl(),
|
||||||
|
|||||||
@@ -14,9 +14,9 @@ use db::models::{
|
|||||||
repo::Repo,
|
repo::Repo,
|
||||||
};
|
};
|
||||||
use deployment::Deployment;
|
use deployment::Deployment;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::Deserialize;
|
||||||
use services::services::{
|
use services::services::{
|
||||||
file_search_cache::SearchQuery, git::GitBranch, project::ProjectServiceError,
|
file_search_cache::SearchQuery, project::ProjectServiceError,
|
||||||
remote_client::CreateRemoteProjectPayload,
|
remote_client::CreateRemoteProjectPayload,
|
||||||
};
|
};
|
||||||
use ts_rs::TS;
|
use ts_rs::TS;
|
||||||
@@ -28,20 +28,6 @@ use uuid::Uuid;
|
|||||||
|
|
||||||
use crate::{DeploymentImpl, error::ApiError, middleware::load_project_middleware};
|
use crate::{DeploymentImpl, error::ApiError, middleware::load_project_middleware};
|
||||||
|
|
||||||
/// Branches for a single repository
|
|
||||||
#[derive(Debug, Serialize, TS)]
|
|
||||||
pub struct RepositoryBranches {
|
|
||||||
pub repository_id: Uuid,
|
|
||||||
pub repository_name: String,
|
|
||||||
pub branches: Vec<GitBranch>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Response containing branches grouped by repository
|
|
||||||
#[derive(Debug, Serialize, TS)]
|
|
||||||
pub struct ProjectBranchesResponse {
|
|
||||||
pub repositories: Vec<RepositoryBranches>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize, TS)]
|
#[derive(Deserialize, TS)]
|
||||||
pub struct LinkToExistingRequest {
|
pub struct LinkToExistingRequest {
|
||||||
pub remote_project_id: Uuid,
|
pub remote_project_id: Uuid,
|
||||||
@@ -66,33 +52,6 @@ pub async fn get_project(
|
|||||||
Ok(ResponseJson(ApiResponse::success(project)))
|
Ok(ResponseJson(ApiResponse::success(project)))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_project_branches(
|
|
||||||
Extension(project): Extension<Project>,
|
|
||||||
State(deployment): State<DeploymentImpl>,
|
|
||||||
) -> Result<ResponseJson<ApiResponse<ProjectBranchesResponse>>, ApiError> {
|
|
||||||
let repositories = deployment
|
|
||||||
.project()
|
|
||||||
.get_repositories(&deployment.db().pool, project.id)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
let mut repo_branches = Vec::with_capacity(repositories.len());
|
|
||||||
|
|
||||||
for repo in repositories {
|
|
||||||
let branches = deployment.git().get_all_branches(&repo.path)?;
|
|
||||||
repo_branches.push(RepositoryBranches {
|
|
||||||
repository_id: repo.id,
|
|
||||||
repository_name: repo.name,
|
|
||||||
branches,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(ResponseJson(ApiResponse::success(
|
|
||||||
ProjectBranchesResponse {
|
|
||||||
repositories: repo_branches,
|
|
||||||
},
|
|
||||||
)))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn link_project_to_existing_remote(
|
pub async fn link_project_to_existing_remote(
|
||||||
Extension(project): Extension<Project>,
|
Extension(project): Extension<Project>,
|
||||||
State(deployment): State<DeploymentImpl>,
|
State(deployment): State<DeploymentImpl>,
|
||||||
@@ -583,7 +542,6 @@ pub fn router(deployment: &DeploymentImpl) -> Router<DeploymentImpl> {
|
|||||||
get(get_project).put(update_project).delete(delete_project),
|
get(get_project).put(update_project).delete(delete_project),
|
||||||
)
|
)
|
||||||
.route("/remote/members", get(get_project_remote_members))
|
.route("/remote/members", get(get_project_remote_members))
|
||||||
.route("/branches", get(get_project_branches))
|
|
||||||
.route("/search", get(search_project_files))
|
.route("/search", get(search_project_files))
|
||||||
.route("/open-editor", post(open_project_in_editor))
|
.route("/open-editor", post(open_project_in_editor))
|
||||||
.route(
|
.route(
|
||||||
|
|||||||
@@ -122,9 +122,8 @@ export function NextActionCard({
|
|||||||
GitActionsDialog.show({
|
GitActionsDialog.show({
|
||||||
attemptId,
|
attemptId,
|
||||||
task,
|
task,
|
||||||
projectId: project?.id,
|
|
||||||
});
|
});
|
||||||
}, [attemptId, task, project?.id]);
|
}, [attemptId, task]);
|
||||||
|
|
||||||
const handleRunSetup = useCallback(async () => {
|
const handleRunSetup = useCallback(async () => {
|
||||||
if (!attemptId || !attempt) return;
|
if (!attemptId || !attempt) return;
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import {
|
|||||||
useAttempt,
|
useAttempt,
|
||||||
useRepoBranchSelection,
|
useRepoBranchSelection,
|
||||||
useTaskAttempts,
|
useTaskAttempts,
|
||||||
|
useProjectRepos,
|
||||||
} from '@/hooks';
|
} from '@/hooks';
|
||||||
import { useProject } from '@/contexts/ProjectContext';
|
import { useProject } from '@/contexts/ProjectContext';
|
||||||
import { useUserSystem } from '@/components/ConfigProvider';
|
import { useUserSystem } from '@/components/ConfigProvider';
|
||||||
@@ -66,17 +67,19 @@ const CreateAttemptDialogImpl = NiceModal.create<CreateAttemptDialogProps>(
|
|||||||
{ enabled: modal.visible && !!parentAttemptId }
|
{ enabled: modal.visible && !!parentAttemptId }
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const { data: projectRepos = [], isLoading: isLoadingRepos } =
|
||||||
|
useProjectRepos(projectId, { enabled: modal.visible });
|
||||||
|
|
||||||
const {
|
const {
|
||||||
configs: repoBranchConfigs,
|
configs: repoBranchConfigs,
|
||||||
projectRepos,
|
|
||||||
isLoading: isLoadingBranches,
|
isLoading: isLoadingBranches,
|
||||||
setRepoBranch,
|
setRepoBranch,
|
||||||
getAttemptRepoInputs,
|
getAttemptRepoInputs,
|
||||||
reset: resetBranchSelection,
|
reset: resetBranchSelection,
|
||||||
} = useRepoBranchSelection({
|
} = useRepoBranchSelection({
|
||||||
projectId,
|
repos: projectRepos,
|
||||||
initialBranch: parentAttempt?.branch,
|
initialBranch: parentAttempt?.branch,
|
||||||
enabled: modal.visible,
|
enabled: modal.visible && projectRepos.length > 0,
|
||||||
});
|
});
|
||||||
|
|
||||||
const latestAttempt = useMemo(() => {
|
const latestAttempt = useMemo(() => {
|
||||||
@@ -118,6 +121,7 @@ const CreateAttemptDialogImpl = NiceModal.create<CreateAttemptDialogProps>(
|
|||||||
const effectiveProfile = userSelectedProfile ?? defaultProfile;
|
const effectiveProfile = userSelectedProfile ?? defaultProfile;
|
||||||
|
|
||||||
const isLoadingInitial =
|
const isLoadingInitial =
|
||||||
|
isLoadingRepos ||
|
||||||
isLoadingBranches ||
|
isLoadingBranches ||
|
||||||
isLoadingAttempts ||
|
isLoadingAttempts ||
|
||||||
isLoadingTask ||
|
isLoadingTask ||
|
||||||
|
|||||||
@@ -17,16 +17,10 @@ import { useCallback, useEffect, useMemo, useState } from 'react';
|
|||||||
import { attemptsApi } from '@/lib/api.ts';
|
import { attemptsApi } from '@/lib/api.ts';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
import {
|
import { TaskAttempt, TaskWithAttemptStatus } from 'shared/types';
|
||||||
GitBranch,
|
|
||||||
RepositoryBranches,
|
|
||||||
TaskAttempt,
|
|
||||||
TaskWithAttemptStatus,
|
|
||||||
} from 'shared/types';
|
|
||||||
import { projectsApi } from '@/lib/api.ts';
|
|
||||||
import { Loader2 } from 'lucide-react';
|
import { Loader2 } from 'lucide-react';
|
||||||
import NiceModal, { useModal } from '@ebay/nice-modal-react';
|
import NiceModal, { useModal } from '@ebay/nice-modal-react';
|
||||||
import { useAuth } from '@/hooks';
|
import { useAuth, useRepoBranches } from '@/hooks';
|
||||||
import {
|
import {
|
||||||
GhCliHelpInstructions,
|
GhCliHelpInstructions,
|
||||||
GhCliSetupDialog,
|
GhCliSetupDialog,
|
||||||
@@ -43,12 +37,11 @@ import { defineModal } from '@/lib/modals';
|
|||||||
interface CreatePRDialogProps {
|
interface CreatePRDialogProps {
|
||||||
attempt: TaskAttempt;
|
attempt: TaskAttempt;
|
||||||
task: TaskWithAttemptStatus;
|
task: TaskWithAttemptStatus;
|
||||||
projectId: string;
|
|
||||||
repoId: string;
|
repoId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const CreatePRDialogImpl = NiceModal.create<CreatePRDialogProps>(
|
const CreatePRDialogImpl = NiceModal.create<CreatePRDialogProps>(
|
||||||
({ attempt, task, projectId, repoId }) => {
|
({ attempt, task, repoId }) => {
|
||||||
const modal = useModal();
|
const modal = useModal();
|
||||||
const { t } = useTranslation('tasks');
|
const { t } = useTranslation('tasks');
|
||||||
const { isLoaded } = useAuth();
|
const { isLoaded } = useAuth();
|
||||||
@@ -61,18 +54,22 @@ const CreatePRDialogImpl = NiceModal.create<CreatePRDialogProps>(
|
|||||||
const [ghCliHelp, setGhCliHelp] = useState<GhCliSupportContent | null>(
|
const [ghCliHelp, setGhCliHelp] = useState<GhCliSupportContent | null>(
|
||||||
null
|
null
|
||||||
);
|
);
|
||||||
const [branches, setBranches] = useState<GitBranch[]>([]);
|
|
||||||
const [branchesLoading, setBranchesLoading] = useState(false);
|
|
||||||
const [isDraft, setIsDraft] = useState(false);
|
const [isDraft, setIsDraft] = useState(false);
|
||||||
const [autoGenerateDescription, setAutoGenerateDescription] = useState(
|
const [autoGenerateDescription, setAutoGenerateDescription] = useState(
|
||||||
config?.pr_auto_description_enabled ?? false
|
config?.pr_auto_description_enabled ?? false
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const { data: branches = [], isLoading: branchesLoading } = useRepoBranches(
|
||||||
|
repoId,
|
||||||
|
{ enabled: modal.visible && !!repoId }
|
||||||
|
);
|
||||||
|
|
||||||
const getGhCliHelpTitle = (variant: GhCliSupportVariant) =>
|
const getGhCliHelpTitle = (variant: GhCliSupportVariant) =>
|
||||||
variant === 'homebrew'
|
variant === 'homebrew'
|
||||||
? 'Homebrew is required for automatic setup'
|
? 'Homebrew is required for automatic setup'
|
||||||
: 'GitHub CLI needs manual setup';
|
: 'GitHub CLI needs manual setup';
|
||||||
|
|
||||||
|
// Initialize form when dialog opens
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!modal.visible || !isLoaded) {
|
if (!modal.visible || !isLoaded) {
|
||||||
return;
|
return;
|
||||||
@@ -80,32 +77,19 @@ const CreatePRDialogImpl = NiceModal.create<CreatePRDialogProps>(
|
|||||||
|
|
||||||
setPrTitle(`${task.title} (vibe-kanban)`);
|
setPrTitle(`${task.title} (vibe-kanban)`);
|
||||||
setPrBody(task.description || '');
|
setPrBody(task.description || '');
|
||||||
|
setError(null);
|
||||||
// Always fetch branches for dropdown population
|
|
||||||
if (projectId) {
|
|
||||||
setBranchesLoading(true);
|
|
||||||
projectsApi
|
|
||||||
.getBranches(projectId)
|
|
||||||
.then((repoBranches: RepositoryBranches[]) => {
|
|
||||||
const repoData = repoBranches.find(
|
|
||||||
(r) => r.repository_id === repoId
|
|
||||||
);
|
|
||||||
const branchesForRepo = repoData?.branches ?? [];
|
|
||||||
setBranches(branchesForRepo);
|
|
||||||
|
|
||||||
// Set smart default: current branch (target_branch is now per-repo in AttemptRepo)
|
|
||||||
const currentBranch = branchesForRepo.find((b) => b.is_current);
|
|
||||||
if (currentBranch) {
|
|
||||||
setPrBaseBranch(currentBranch.name);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(console.error)
|
|
||||||
.finally(() => setBranchesLoading(false));
|
|
||||||
}
|
|
||||||
|
|
||||||
setError(null); // Reset error when opening
|
|
||||||
setGhCliHelp(null);
|
setGhCliHelp(null);
|
||||||
}, [modal.visible, isLoaded, task, attempt, projectId, repoId]);
|
}, [modal.visible, isLoaded, task]);
|
||||||
|
|
||||||
|
// Set default base branch when branches are loaded
|
||||||
|
useEffect(() => {
|
||||||
|
if (branches.length > 0 && !prBaseBranch) {
|
||||||
|
const currentBranch = branches.find((b) => b.is_current);
|
||||||
|
if (currentBranch) {
|
||||||
|
setPrBaseBranch(currentBranch.name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [branches, prBaseBranch]);
|
||||||
|
|
||||||
const isMacEnvironment = useMemo(
|
const isMacEnvironment = useMemo(
|
||||||
() => environment?.os_type?.toLowerCase().includes('mac'),
|
() => environment?.os_type?.toLowerCase().includes('mac'),
|
||||||
@@ -113,7 +97,7 @@ const CreatePRDialogImpl = NiceModal.create<CreatePRDialogProps>(
|
|||||||
);
|
);
|
||||||
|
|
||||||
const handleConfirmCreatePR = useCallback(async () => {
|
const handleConfirmCreatePR = useCallback(async () => {
|
||||||
if (!projectId || !attempt.id) return;
|
if (!repoId || !attempt.id) return;
|
||||||
|
|
||||||
setError(null);
|
setError(null);
|
||||||
setGhCliHelp(null);
|
setGhCliHelp(null);
|
||||||
@@ -227,7 +211,7 @@ const CreatePRDialogImpl = NiceModal.create<CreatePRDialogProps>(
|
|||||||
}
|
}
|
||||||
}, [
|
}, [
|
||||||
attempt,
|
attempt,
|
||||||
projectId,
|
repoId,
|
||||||
prBaseBranch,
|
prBaseBranch,
|
||||||
prBody,
|
prBody,
|
||||||
prTitle,
|
prTitle,
|
||||||
@@ -237,7 +221,6 @@ const CreatePRDialogImpl = NiceModal.create<CreatePRDialogProps>(
|
|||||||
modal,
|
modal,
|
||||||
isMacEnvironment,
|
isMacEnvironment,
|
||||||
t,
|
t,
|
||||||
repoId,
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const handleCancelCreatePR = useCallback(() => {
|
const handleCancelCreatePR = useCallback(() => {
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { useEffect, useMemo, useState } from 'react';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { ExternalLink, GitPullRequest } from 'lucide-react';
|
import { ExternalLink, GitPullRequest } from 'lucide-react';
|
||||||
import {
|
import {
|
||||||
@@ -12,41 +11,28 @@ import GitOperations from '@/components/tasks/Toolbar/GitOperations';
|
|||||||
import { useTaskAttempt } from '@/hooks/useTaskAttempt';
|
import { useTaskAttempt } from '@/hooks/useTaskAttempt';
|
||||||
import { useBranchStatus, useAttemptExecution } from '@/hooks';
|
import { useBranchStatus, useAttemptExecution } from '@/hooks';
|
||||||
import { useAttemptRepo } from '@/hooks/useAttemptRepo';
|
import { useAttemptRepo } from '@/hooks/useAttemptRepo';
|
||||||
import { useProject } from '@/contexts/ProjectContext';
|
|
||||||
import { ExecutionProcessesProvider } from '@/contexts/ExecutionProcessesContext';
|
import { ExecutionProcessesProvider } from '@/contexts/ExecutionProcessesContext';
|
||||||
import {
|
import {
|
||||||
GitOperationsProvider,
|
GitOperationsProvider,
|
||||||
useGitOperationsError,
|
useGitOperationsError,
|
||||||
} from '@/contexts/GitOperationsContext';
|
} from '@/contexts/GitOperationsContext';
|
||||||
import { projectsApi } from '@/lib/api';
|
import type { Merge, TaskAttempt, TaskWithAttemptStatus } from 'shared/types';
|
||||||
import type {
|
|
||||||
GitBranch,
|
|
||||||
Merge,
|
|
||||||
RepositoryBranches,
|
|
||||||
TaskAttempt,
|
|
||||||
TaskWithAttemptStatus,
|
|
||||||
} from 'shared/types';
|
|
||||||
import NiceModal, { useModal } from '@ebay/nice-modal-react';
|
import NiceModal, { useModal } from '@ebay/nice-modal-react';
|
||||||
import { defineModal } from '@/lib/modals';
|
import { defineModal } from '@/lib/modals';
|
||||||
|
|
||||||
export interface GitActionsDialogProps {
|
export interface GitActionsDialogProps {
|
||||||
attemptId: string;
|
attemptId: string;
|
||||||
task?: TaskWithAttemptStatus;
|
task?: TaskWithAttemptStatus;
|
||||||
projectId?: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface GitActionsDialogContentProps {
|
interface GitActionsDialogContentProps {
|
||||||
attempt: TaskAttempt;
|
attempt: TaskAttempt;
|
||||||
task: TaskWithAttemptStatus;
|
task: TaskWithAttemptStatus;
|
||||||
projectId: string;
|
|
||||||
branches: GitBranch[];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function GitActionsDialogContent({
|
function GitActionsDialogContent({
|
||||||
attempt,
|
attempt,
|
||||||
task,
|
task,
|
||||||
projectId,
|
|
||||||
branches,
|
|
||||||
}: GitActionsDialogContentProps) {
|
}: GitActionsDialogContentProps) {
|
||||||
const { t } = useTranslation('tasks');
|
const { t } = useTranslation('tasks');
|
||||||
const { data: branchStatus } = useBranchStatus(attempt.id);
|
const { data: branchStatus } = useBranchStatus(attempt.id);
|
||||||
@@ -96,9 +82,7 @@ function GitActionsDialogContent({
|
|||||||
<GitOperations
|
<GitOperations
|
||||||
selectedAttempt={attempt}
|
selectedAttempt={attempt}
|
||||||
task={task}
|
task={task}
|
||||||
projectId={projectId}
|
|
||||||
branchStatus={branchStatus ?? null}
|
branchStatus={branchStatus ?? null}
|
||||||
branches={branches}
|
|
||||||
isAttemptRunning={isAttemptRunning}
|
isAttemptRunning={isAttemptRunning}
|
||||||
selectedBranch={getSelectedRepoStatus()?.target_branch_name ?? null}
|
selectedBranch={getSelectedRepoStatus()?.target_branch_name ?? null}
|
||||||
layout="vertical"
|
layout="vertical"
|
||||||
@@ -108,34 +92,11 @@ function GitActionsDialogContent({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const GitActionsDialogImpl = NiceModal.create<GitActionsDialogProps>(
|
const GitActionsDialogImpl = NiceModal.create<GitActionsDialogProps>(
|
||||||
({ attemptId, task, projectId: providedProjectId }) => {
|
({ attemptId, task }) => {
|
||||||
const modal = useModal();
|
const modal = useModal();
|
||||||
const { t } = useTranslation('tasks');
|
const { t } = useTranslation('tasks');
|
||||||
const { project } = useProject();
|
|
||||||
|
|
||||||
const effectiveProjectId = providedProjectId ?? project?.id;
|
|
||||||
const { data: attempt } = useTaskAttempt(attemptId);
|
const { data: attempt } = useTaskAttempt(attemptId);
|
||||||
const { selectedRepoId } = useAttemptRepo(attemptId);
|
|
||||||
|
|
||||||
const [repoBranches, setRepoBranches] = useState<RepositoryBranches[]>([]);
|
|
||||||
const [loadingBranches, setLoadingBranches] = useState(true);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!effectiveProjectId) return;
|
|
||||||
setLoadingBranches(true);
|
|
||||||
projectsApi
|
|
||||||
.getBranches(effectiveProjectId)
|
|
||||||
.then(setRepoBranches)
|
|
||||||
.catch(() => setRepoBranches([]))
|
|
||||||
.finally(() => setLoadingBranches(false));
|
|
||||||
}, [effectiveProjectId]);
|
|
||||||
|
|
||||||
const branches = useMemo(
|
|
||||||
() =>
|
|
||||||
repoBranches.find((r) => r.repository_id === selectedRepoId)
|
|
||||||
?.branches ?? [],
|
|
||||||
[repoBranches, selectedRepoId]
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleOpenChange = (open: boolean) => {
|
const handleOpenChange = (open: boolean) => {
|
||||||
if (!open) {
|
if (!open) {
|
||||||
@@ -143,8 +104,7 @@ const GitActionsDialogImpl = NiceModal.create<GitActionsDialogProps>(
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const isLoading =
|
const isLoading = !attempt || !task;
|
||||||
!attempt || !effectiveProjectId || loadingBranches || !task;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog open={modal.visible} onOpenChange={handleOpenChange}>
|
<Dialog open={modal.visible} onOpenChange={handleOpenChange}>
|
||||||
@@ -163,12 +123,7 @@ const GitActionsDialogImpl = NiceModal.create<GitActionsDialogProps>(
|
|||||||
key={attempt.id}
|
key={attempt.id}
|
||||||
attemptId={attempt.id}
|
attemptId={attempt.id}
|
||||||
>
|
>
|
||||||
<GitActionsDialogContent
|
<GitActionsDialogContent attempt={attempt} task={task} />
|
||||||
attempt={attempt}
|
|
||||||
task={task}
|
|
||||||
projectId={effectiveProjectId}
|
|
||||||
branches={branches}
|
|
||||||
/>
|
|
||||||
</ExecutionProcessesProvider>
|
</ExecutionProcessesProvider>
|
||||||
</GitOperationsProvider>
|
</GitOperationsProvider>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -31,11 +31,11 @@ import RepoBranchSelector from '@/components/tasks/RepoBranchSelector';
|
|||||||
import { ExecutorProfileSelector } from '@/components/settings';
|
import { ExecutorProfileSelector } from '@/components/settings';
|
||||||
import { useUserSystem } from '@/components/ConfigProvider';
|
import { useUserSystem } from '@/components/ConfigProvider';
|
||||||
import {
|
import {
|
||||||
useBranches,
|
|
||||||
useTaskImages,
|
useTaskImages,
|
||||||
useImageUpload,
|
useImageUpload,
|
||||||
useTaskMutations,
|
useTaskMutations,
|
||||||
type RepoBranchConfig,
|
useProjectRepos,
|
||||||
|
useRepoBranchSelection,
|
||||||
} from '@/hooks';
|
} from '@/hooks';
|
||||||
import {
|
import {
|
||||||
useKeySubmitTask,
|
useKeySubmitTask,
|
||||||
@@ -50,8 +50,6 @@ import type {
|
|||||||
ExecutorProfileId,
|
ExecutorProfileId,
|
||||||
ImageResponse,
|
ImageResponse,
|
||||||
} from 'shared/types';
|
} from 'shared/types';
|
||||||
import { projectsApi } from '@/lib/api';
|
|
||||||
import { useQuery } from '@tanstack/react-query';
|
|
||||||
|
|
||||||
interface Task {
|
interface Task {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -104,43 +102,20 @@ const TaskFormDialogImpl = NiceModal.create<TaskFormDialogProps>((props) => {
|
|||||||
const [showDiscardWarning, setShowDiscardWarning] = useState(false);
|
const [showDiscardWarning, setShowDiscardWarning] = useState(false);
|
||||||
const forceCreateOnlyRef = useRef(false);
|
const forceCreateOnlyRef = useRef(false);
|
||||||
|
|
||||||
const { data: repoBranches, isLoading: branchesLoading } =
|
|
||||||
useBranches(projectId);
|
|
||||||
const { data: taskImages } = useTaskImages(
|
const { data: taskImages } = useTaskImages(
|
||||||
editMode ? props.task.id : undefined
|
editMode ? props.task.id : undefined
|
||||||
);
|
);
|
||||||
const { data: projectRepos = [] } = useQuery({
|
const { data: projectRepos = [] } = useProjectRepos(projectId, {
|
||||||
queryKey: ['projectRepositories', projectId],
|
|
||||||
queryFn: () => projectsApi.getRepositories(projectId),
|
|
||||||
enabled: modal.visible,
|
enabled: modal.visible,
|
||||||
});
|
});
|
||||||
|
const initialBranch =
|
||||||
const repoBranchConfigs = useMemo((): RepoBranchConfig[] => {
|
mode === 'subtask' ? props.initialBaseBranch : undefined;
|
||||||
return projectRepos.map((repo) => {
|
const { configs: repoBranchConfigs, isLoading: branchesLoading } =
|
||||||
const repoBranchData = repoBranches?.find(
|
useRepoBranchSelection({
|
||||||
(rb) => rb.repository_id === repo.id
|
repos: projectRepos,
|
||||||
);
|
initialBranch,
|
||||||
const branches = repoBranchData?.branches ?? [];
|
enabled: modal.visible && projectRepos.length > 0,
|
||||||
|
|
||||||
let targetBranch: string | null = null;
|
|
||||||
const initialBranch =
|
|
||||||
mode === 'subtask' ? props.initialBaseBranch : undefined;
|
|
||||||
|
|
||||||
if (initialBranch && branches.some((b) => b.name === initialBranch)) {
|
|
||||||
targetBranch = initialBranch;
|
|
||||||
} else {
|
|
||||||
const currentBranch = branches.find((b) => b.is_current);
|
|
||||||
targetBranch = currentBranch?.name ?? branches[0]?.name ?? null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
repoId: repo.id,
|
|
||||||
repoDisplayName: repo.display_name,
|
|
||||||
targetBranch,
|
|
||||||
branches,
|
|
||||||
};
|
|
||||||
});
|
});
|
||||||
}, [projectRepos, repoBranches, mode, props]);
|
|
||||||
|
|
||||||
const defaultRepoBranches = useMemo((): RepoBranch[] => {
|
const defaultRepoBranches = useMemo((): RepoBranch[] => {
|
||||||
return repoBranchConfigs
|
return repoBranchConfigs
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ import { openTaskForm } from '@/lib/openTaskForm';
|
|||||||
import { useProject } from '@/contexts/ProjectContext';
|
import { useProject } from '@/contexts/ProjectContext';
|
||||||
import { useOpenProjectInEditor } from '@/hooks/useOpenProjectInEditor';
|
import { useOpenProjectInEditor } from '@/hooks/useOpenProjectInEditor';
|
||||||
import { OpenInIdeButton } from '@/components/ide/OpenInIdeButton';
|
import { OpenInIdeButton } from '@/components/ide/OpenInIdeButton';
|
||||||
import { useBranches } from '@/hooks/useBranches';
|
import { useProjectRepos } from '@/hooks';
|
||||||
import { useDiscordOnlineCount } from '@/hooks/useDiscordOnlineCount';
|
import { useDiscordOnlineCount } from '@/hooks/useDiscordOnlineCount';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { Switch } from '@/components/ui/switch';
|
import { Switch } from '@/components/ui/switch';
|
||||||
@@ -80,7 +80,7 @@ export function Navbar() {
|
|||||||
const { data: onlineCount } = useDiscordOnlineCount();
|
const { data: onlineCount } = useDiscordOnlineCount();
|
||||||
const { loginStatus, reloadSystem } = useUserSystem();
|
const { loginStatus, reloadSystem } = useUserSystem();
|
||||||
|
|
||||||
const { data: repos } = useBranches(projectId);
|
const { data: repos } = useProjectRepos(projectId);
|
||||||
const isSingleRepoProject = repos?.length === 1;
|
const isSingleRepoProject = repos?.length === 1;
|
||||||
|
|
||||||
const setSearchBarRef = useCallback(
|
const setSearchBarRef = useCallback(
|
||||||
|
|||||||
@@ -24,12 +24,11 @@ import {
|
|||||||
import { Project } from 'shared/types';
|
import { Project } from 'shared/types';
|
||||||
import { useEffect, useRef } from 'react';
|
import { useEffect, useRef } from 'react';
|
||||||
import { useOpenProjectInEditor } from '@/hooks/useOpenProjectInEditor';
|
import { useOpenProjectInEditor } from '@/hooks/useOpenProjectInEditor';
|
||||||
import { useNavigateWithSearch } from '@/hooks';
|
import { useNavigateWithSearch, useProjectRepos } from '@/hooks';
|
||||||
import { projectsApi } from '@/lib/api';
|
import { projectsApi } from '@/lib/api';
|
||||||
import { LinkProjectDialog } from '@/components/dialogs/projects/LinkProjectDialog';
|
import { LinkProjectDialog } from '@/components/dialogs/projects/LinkProjectDialog';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useProjectMutations } from '@/hooks/useProjectMutations';
|
import { useProjectMutations } from '@/hooks/useProjectMutations';
|
||||||
import { useBranches } from '@/hooks/useBranches';
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
project: Project;
|
project: Project;
|
||||||
@@ -51,7 +50,7 @@ function ProjectCard({
|
|||||||
const handleOpenInEditor = useOpenProjectInEditor(project);
|
const handleOpenInEditor = useOpenProjectInEditor(project);
|
||||||
const { t } = useTranslation('projects');
|
const { t } = useTranslation('projects');
|
||||||
|
|
||||||
const { data: repos } = useBranches(project.id);
|
const { data: repos } = useProjectRepos(project.id);
|
||||||
const isSingleRepoProject = repos?.length === 1;
|
const isSingleRepoProject = repos?.length === 1;
|
||||||
|
|
||||||
const { unlinkProject } = useProjectMutations({
|
const { unlinkProject } = useProjectMutations({
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useQuery } from '@tanstack/react-query';
|
|
||||||
import {
|
import {
|
||||||
Play,
|
Play,
|
||||||
Edit3,
|
Edit3,
|
||||||
@@ -21,7 +20,7 @@ import {
|
|||||||
import { useUserSystem } from '@/components/ConfigProvider';
|
import { useUserSystem } from '@/components/ConfigProvider';
|
||||||
import { useTaskMutations } from '@/hooks/useTaskMutations';
|
import { useTaskMutations } from '@/hooks/useTaskMutations';
|
||||||
import { useProjectMutations } from '@/hooks/useProjectMutations';
|
import { useProjectMutations } from '@/hooks/useProjectMutations';
|
||||||
import { projectsApi } from '@/lib/api';
|
import { useProjectRepos } from '@/hooks';
|
||||||
import {
|
import {
|
||||||
COMPANION_INSTALL_TASK_TITLE,
|
COMPANION_INSTALL_TASK_TITLE,
|
||||||
COMPANION_INSTALL_TASK_DESCRIPTION,
|
COMPANION_INSTALL_TASK_DESCRIPTION,
|
||||||
@@ -53,14 +52,7 @@ export function NoServerContent({
|
|||||||
const { createAndStart } = useTaskMutations(project?.id);
|
const { createAndStart } = useTaskMutations(project?.id);
|
||||||
const { updateProject } = useProjectMutations();
|
const { updateProject } = useProjectMutations();
|
||||||
|
|
||||||
const { data: projectRepos = [] } = useQuery({
|
const { data: projectRepos = [] } = useProjectRepos(project?.id);
|
||||||
queryKey: ['projectRepositories', project?.id],
|
|
||||||
queryFn: () =>
|
|
||||||
project?.id
|
|
||||||
? projectsApi.getRepositories(project.id)
|
|
||||||
: Promise.resolve([]),
|
|
||||||
enabled: !!project?.id,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Create strategy-based placeholders
|
// Create strategy-based placeholders
|
||||||
const placeholders = system.environment
|
const placeholders = system.environment
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ import { useCallback, useMemo, useState } from 'react';
|
|||||||
import type {
|
import type {
|
||||||
RepoBranchStatus,
|
RepoBranchStatus,
|
||||||
Merge,
|
Merge,
|
||||||
GitBranch,
|
|
||||||
TaskAttempt,
|
TaskAttempt,
|
||||||
TaskWithAttemptStatus,
|
TaskWithAttemptStatus,
|
||||||
} from 'shared/types';
|
} from 'shared/types';
|
||||||
@@ -30,13 +29,12 @@ import { CreatePRDialog } from '@/components/dialogs/tasks/CreatePRDialog';
|
|||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useAttemptRepo } from '@/hooks/useAttemptRepo';
|
import { useAttemptRepo } from '@/hooks/useAttemptRepo';
|
||||||
import { useGitOperations } from '@/hooks/useGitOperations';
|
import { useGitOperations } from '@/hooks/useGitOperations';
|
||||||
|
import { useRepoBranches } from '@/hooks';
|
||||||
|
|
||||||
interface GitOperationsProps {
|
interface GitOperationsProps {
|
||||||
selectedAttempt: TaskAttempt;
|
selectedAttempt: TaskAttempt;
|
||||||
task: TaskWithAttemptStatus;
|
task: TaskWithAttemptStatus;
|
||||||
projectId: string;
|
|
||||||
branchStatus: RepoBranchStatus[] | null;
|
branchStatus: RepoBranchStatus[] | null;
|
||||||
branches: GitBranch[];
|
|
||||||
isAttemptRunning: boolean;
|
isAttemptRunning: boolean;
|
||||||
selectedBranch: string | null;
|
selectedBranch: string | null;
|
||||||
layout?: 'horizontal' | 'vertical';
|
layout?: 'horizontal' | 'vertical';
|
||||||
@@ -47,19 +45,18 @@ export type GitOperationsInputs = Omit<GitOperationsProps, 'selectedAttempt'>;
|
|||||||
function GitOperations({
|
function GitOperations({
|
||||||
selectedAttempt,
|
selectedAttempt,
|
||||||
task,
|
task,
|
||||||
projectId,
|
|
||||||
branchStatus,
|
branchStatus,
|
||||||
branches,
|
|
||||||
isAttemptRunning,
|
isAttemptRunning,
|
||||||
selectedBranch,
|
selectedBranch,
|
||||||
layout = 'horizontal',
|
layout = 'horizontal',
|
||||||
}: GitOperationsProps) {
|
}: GitOperationsProps) {
|
||||||
const { t } = useTranslation('tasks');
|
const { t } = useTranslation('tasks');
|
||||||
|
|
||||||
const git = useGitOperations(selectedAttempt.id, projectId);
|
|
||||||
const { repos, selectedRepoId, setSelectedRepoId } = useAttemptRepo(
|
const { repos, selectedRepoId, setSelectedRepoId } = useAttemptRepo(
|
||||||
selectedAttempt.id
|
selectedAttempt.id
|
||||||
);
|
);
|
||||||
|
const git = useGitOperations(selectedAttempt.id, selectedRepoId ?? undefined);
|
||||||
|
const { data: branches = [] } = useRepoBranches(selectedRepoId);
|
||||||
const isChangingTargetBranch = git.states.changeTargetBranchPending;
|
const isChangingTargetBranch = git.states.changeTargetBranchPending;
|
||||||
|
|
||||||
// Local state for git operations
|
// Local state for git operations
|
||||||
@@ -256,7 +253,6 @@ function GitOperations({
|
|||||||
CreatePRDialog.show({
|
CreatePRDialog.show({
|
||||||
attempt: selectedAttempt,
|
attempt: selectedAttempt,
|
||||||
task,
|
task,
|
||||||
projectId,
|
|
||||||
repoId: getSelectedRepoId(),
|
repoId: getSelectedRepoId(),
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -128,7 +128,6 @@ export function ActionsDropdown({
|
|||||||
GitActionsDialog.show({
|
GitActionsDialog.show({
|
||||||
attemptId: attempt.id,
|
attemptId: attempt.id,
|
||||||
task,
|
task,
|
||||||
projectId,
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,8 @@ export { useNavigateWithSearch } from './useNavigateWithSearch';
|
|||||||
export { useGitOperations } from './useGitOperations';
|
export { useGitOperations } from './useGitOperations';
|
||||||
export { useTask } from './useTask';
|
export { useTask } from './useTask';
|
||||||
export { useAttempt } from './useAttempt';
|
export { useAttempt } from './useAttempt';
|
||||||
export { useBranches } from './useBranches';
|
export { useRepoBranches } from './useRepoBranches';
|
||||||
|
export { useProjectRepos } from './useProjectRepos';
|
||||||
export { useRepoBranchSelection } from './useRepoBranchSelection';
|
export { useRepoBranchSelection } from './useRepoBranchSelection';
|
||||||
export type { RepoBranchConfig } from './useRepoBranchSelection';
|
export type { RepoBranchConfig } from './useRepoBranchSelection';
|
||||||
export { useTaskAttempts } from './useTaskAttempts';
|
export { useTaskAttempts } from './useTaskAttempts';
|
||||||
|
|||||||
@@ -1,25 +0,0 @@
|
|||||||
import { useQuery } from '@tanstack/react-query';
|
|
||||||
import { projectsApi } from '@/lib/api';
|
|
||||||
import type { RepositoryBranches } from 'shared/types';
|
|
||||||
|
|
||||||
export const branchKeys = {
|
|
||||||
all: ['projectBranches'] as const,
|
|
||||||
byProject: (projectId: string | undefined) =>
|
|
||||||
['projectBranches', projectId] as const,
|
|
||||||
};
|
|
||||||
|
|
||||||
type Options = {
|
|
||||||
enabled?: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
export function useBranches(projectId?: string, opts?: Options) {
|
|
||||||
const enabled = (opts?.enabled ?? true) && !!projectId;
|
|
||||||
|
|
||||||
return useQuery<RepositoryBranches[]>({
|
|
||||||
queryKey: branchKeys.byProject(projectId),
|
|
||||||
queryFn: () => projectsApi.getBranches(projectId!),
|
|
||||||
enabled,
|
|
||||||
staleTime: 60_000,
|
|
||||||
refetchOnWindowFocus: true,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@@ -4,6 +4,7 @@ import type {
|
|||||||
ChangeTargetBranchRequest,
|
ChangeTargetBranchRequest,
|
||||||
ChangeTargetBranchResponse,
|
ChangeTargetBranchResponse,
|
||||||
} from 'shared/types';
|
} from 'shared/types';
|
||||||
|
import { repoBranchKeys } from './useRepoBranches';
|
||||||
|
|
||||||
type ChangeTargetBranchParams = {
|
type ChangeTargetBranchParams = {
|
||||||
newTargetBranch: string;
|
newTargetBranch: string;
|
||||||
@@ -12,7 +13,7 @@ type ChangeTargetBranchParams = {
|
|||||||
|
|
||||||
export function useChangeTargetBranch(
|
export function useChangeTargetBranch(
|
||||||
attemptId: string | undefined,
|
attemptId: string | undefined,
|
||||||
projectId: string | undefined,
|
repoId: string | undefined,
|
||||||
onSuccess?: (data: ChangeTargetBranchResponse) => void,
|
onSuccess?: (data: ChangeTargetBranchResponse) => void,
|
||||||
onError?: (err: unknown) => void
|
onError?: (err: unknown) => void
|
||||||
) {
|
) {
|
||||||
@@ -45,9 +46,9 @@ export function useChangeTargetBranch(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (projectId) {
|
if (repoId) {
|
||||||
queryClient.invalidateQueries({
|
queryClient.invalidateQueries({
|
||||||
queryKey: ['projectBranches', projectId],
|
queryKey: repoBranchKeys.byRepo(repoId),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,13 +10,13 @@ import { ForcePushDialog } from '@/components/dialogs/git/ForcePushDialog';
|
|||||||
|
|
||||||
export function useGitOperations(
|
export function useGitOperations(
|
||||||
attemptId: string | undefined,
|
attemptId: string | undefined,
|
||||||
projectId: string | undefined
|
repoId: string | undefined
|
||||||
) {
|
) {
|
||||||
const { setError } = useGitOperationsError();
|
const { setError } = useGitOperationsError();
|
||||||
|
|
||||||
const rebase = useRebase(
|
const rebase = useRebase(
|
||||||
attemptId,
|
attemptId,
|
||||||
projectId,
|
repoId,
|
||||||
() => setError(null),
|
() => setError(null),
|
||||||
(err: Result<void, GitOperationError>) => {
|
(err: Result<void, GitOperationError>) => {
|
||||||
if (!err.success) {
|
if (!err.success) {
|
||||||
@@ -78,7 +78,7 @@ export function useGitOperations(
|
|||||||
|
|
||||||
const changeTargetBranch = useChangeTargetBranch(
|
const changeTargetBranch = useChangeTargetBranch(
|
||||||
attemptId,
|
attemptId,
|
||||||
projectId,
|
repoId,
|
||||||
() => setError(null),
|
() => setError(null),
|
||||||
(err: unknown) => {
|
(err: unknown) => {
|
||||||
const message =
|
const message =
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||||
import { attemptsApi } from '@/lib/api';
|
import { attemptsApi } from '@/lib/api';
|
||||||
|
import { repoBranchKeys } from './useRepoBranches';
|
||||||
|
|
||||||
type MergeParams = {
|
type MergeParams = {
|
||||||
repoId: string;
|
repoId: string;
|
||||||
@@ -23,8 +24,8 @@ export function useMerge(
|
|||||||
// Refresh attempt-specific branch information
|
// Refresh attempt-specific branch information
|
||||||
queryClient.invalidateQueries({ queryKey: ['branchStatus', attemptId] });
|
queryClient.invalidateQueries({ queryKey: ['branchStatus', attemptId] });
|
||||||
|
|
||||||
// Invalidate all project branches queries
|
// Invalidate all repo branches queries
|
||||||
queryClient.invalidateQueries({ queryKey: ['projectBranches'] });
|
queryClient.invalidateQueries({ queryKey: repoBranchKeys.all });
|
||||||
|
|
||||||
onSuccess?.();
|
onSuccess?.();
|
||||||
},
|
},
|
||||||
|
|||||||
17
frontend/src/hooks/useProjectRepos.ts
Normal file
17
frontend/src/hooks/useProjectRepos.ts
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import { useQuery } from '@tanstack/react-query';
|
||||||
|
import { projectsApi } from '@/lib/api';
|
||||||
|
import type { Repo } from 'shared/types';
|
||||||
|
|
||||||
|
type Options = {
|
||||||
|
enabled?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function useProjectRepos(projectId?: string, opts?: Options) {
|
||||||
|
const enabled = (opts?.enabled ?? true) && !!projectId;
|
||||||
|
|
||||||
|
return useQuery<Repo[]>({
|
||||||
|
queryKey: ['projectRepositories', projectId],
|
||||||
|
queryFn: () => projectsApi.getRepositories(projectId!),
|
||||||
|
enabled,
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -2,10 +2,11 @@ import { useMutation, useQueryClient } from '@tanstack/react-query';
|
|||||||
import { attemptsApi, Result } from '@/lib/api';
|
import { attemptsApi, Result } from '@/lib/api';
|
||||||
import type { RebaseTaskAttemptRequest } from 'shared/types';
|
import type { RebaseTaskAttemptRequest } from 'shared/types';
|
||||||
import type { GitOperationError } from 'shared/types';
|
import type { GitOperationError } from 'shared/types';
|
||||||
|
import { repoBranchKeys } from './useRepoBranches';
|
||||||
|
|
||||||
export function useRebase(
|
export function useRebase(
|
||||||
attemptId: string | undefined,
|
attemptId: string | undefined,
|
||||||
projectId: string | undefined,
|
repoId: string | undefined,
|
||||||
onSuccess?: () => void,
|
onSuccess?: () => void,
|
||||||
onError?: (err: Result<void, GitOperationError>) => void
|
onError?: (err: Result<void, GitOperationError>) => void
|
||||||
) {
|
) {
|
||||||
@@ -47,10 +48,10 @@ export function useRebase(
|
|||||||
queryKey: ['taskAttempt', attemptId],
|
queryKey: ['taskAttempt', attemptId],
|
||||||
});
|
});
|
||||||
|
|
||||||
// Refresh branch list used by PR dialog
|
// Refresh branch list
|
||||||
if (projectId) {
|
if (repoId) {
|
||||||
queryClient.invalidateQueries({
|
queryClient.invalidateQueries({
|
||||||
queryKey: ['projectBranches', projectId],
|
queryKey: repoBranchKeys.byRepo(repoId),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { useState, useMemo, useCallback } from 'react';
|
import { useState, useMemo, useCallback } from 'react';
|
||||||
import { useQuery } from '@tanstack/react-query';
|
import { useQueries } from '@tanstack/react-query';
|
||||||
import { useBranches } from './useBranches';
|
import { repoApi } from '@/lib/api';
|
||||||
import { projectsApi } from '@/lib/api';
|
import { repoBranchKeys } from './useRepoBranches';
|
||||||
import type { GitBranch, Repo, RepositoryBranches } from 'shared/types';
|
import type { GitBranch, Repo } from 'shared/types';
|
||||||
|
|
||||||
export type RepoBranchConfig = {
|
export type RepoBranchConfig = {
|
||||||
repoId: string;
|
repoId: string;
|
||||||
@@ -12,15 +12,13 @@ export type RepoBranchConfig = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
type UseRepoBranchSelectionOptions = {
|
type UseRepoBranchSelectionOptions = {
|
||||||
projectId: string | undefined;
|
repos: Repo[];
|
||||||
initialBranch?: string | null;
|
initialBranch?: string | null;
|
||||||
enabled?: boolean;
|
enabled?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
type UseRepoBranchSelectionReturn = {
|
type UseRepoBranchSelectionReturn = {
|
||||||
configs: RepoBranchConfig[];
|
configs: RepoBranchConfig[];
|
||||||
repositoryBranches: RepositoryBranches[];
|
|
||||||
projectRepos: Repo[];
|
|
||||||
isLoading: boolean;
|
isLoading: boolean;
|
||||||
setRepoBranch: (repoId: string, branch: string) => void;
|
setRepoBranch: (repoId: string, branch: string) => void;
|
||||||
getAttemptRepoInputs: () => Array<{ repo_id: string; target_branch: string }>;
|
getAttemptRepoInputs: () => Array<{ repo_id: string; target_branch: string }>;
|
||||||
@@ -28,7 +26,7 @@ type UseRepoBranchSelectionReturn = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export function useRepoBranchSelection({
|
export function useRepoBranchSelection({
|
||||||
projectId,
|
repos,
|
||||||
initialBranch,
|
initialBranch,
|
||||||
enabled = true,
|
enabled = true,
|
||||||
}: UseRepoBranchSelectionOptions): UseRepoBranchSelectionReturn {
|
}: UseRepoBranchSelectionOptions): UseRepoBranchSelectionReturn {
|
||||||
@@ -36,22 +34,20 @@ export function useRepoBranchSelection({
|
|||||||
Record<string, string | null>
|
Record<string, string | null>
|
||||||
>({});
|
>({});
|
||||||
|
|
||||||
const { data: repositoryBranches = [], isLoading: isLoadingBranches } =
|
const queries = useQueries({
|
||||||
useBranches(projectId, { enabled: enabled && !!projectId });
|
queries: repos.map((repo) => ({
|
||||||
|
queryKey: repoBranchKeys.byRepo(repo.id),
|
||||||
const { data: projectRepos = [], isLoading: isLoadingRepos } = useQuery({
|
queryFn: () => repoApi.getBranches(repo.id),
|
||||||
queryKey: ['projectRepositories', projectId],
|
enabled,
|
||||||
queryFn: () =>
|
staleTime: 60_000,
|
||||||
projectId ? projectsApi.getRepositories(projectId) : Promise.resolve([]),
|
})),
|
||||||
enabled: enabled && !!projectId,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const isLoadingBranches = queries.some((q) => q.isLoading);
|
||||||
|
|
||||||
const configs = useMemo((): RepoBranchConfig[] => {
|
const configs = useMemo((): RepoBranchConfig[] => {
|
||||||
return projectRepos.map((repo) => {
|
return repos.map((repo, i) => {
|
||||||
const repoBranchData = repositoryBranches.find(
|
const branches = queries[i]?.data ?? [];
|
||||||
(rb) => rb.repository_id === repo.id
|
|
||||||
);
|
|
||||||
const branches = repoBranchData?.branches ?? [];
|
|
||||||
|
|
||||||
let targetBranch: string | null = userOverrides[repo.id] ?? null;
|
let targetBranch: string | null = userOverrides[repo.id] ?? null;
|
||||||
|
|
||||||
@@ -71,7 +67,7 @@ export function useRepoBranchSelection({
|
|||||||
branches,
|
branches,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}, [projectRepos, repositoryBranches, userOverrides, initialBranch]);
|
}, [repos, queries, userOverrides, initialBranch]);
|
||||||
|
|
||||||
const setRepoBranch = useCallback((repoId: string, branch: string) => {
|
const setRepoBranch = useCallback((repoId: string, branch: string) => {
|
||||||
setUserOverrides((prev) => ({
|
setUserOverrides((prev) => ({
|
||||||
@@ -95,9 +91,7 @@ export function useRepoBranchSelection({
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
configs,
|
configs,
|
||||||
repositoryBranches,
|
isLoading: isLoadingBranches,
|
||||||
projectRepos,
|
|
||||||
isLoading: isLoadingBranches || isLoadingRepos,
|
|
||||||
setRepoBranch,
|
setRepoBranch,
|
||||||
getAttemptRepoInputs,
|
getAttemptRepoInputs,
|
||||||
reset,
|
reset,
|
||||||
|
|||||||
24
frontend/src/hooks/useRepoBranches.ts
Normal file
24
frontend/src/hooks/useRepoBranches.ts
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import { useQuery } from '@tanstack/react-query';
|
||||||
|
import { repoApi } from '@/lib/api';
|
||||||
|
import type { GitBranch } from 'shared/types';
|
||||||
|
|
||||||
|
export const repoBranchKeys = {
|
||||||
|
all: ['repoBranches'] as const,
|
||||||
|
byRepo: (repoId: string | undefined) => ['repoBranches', repoId] as const,
|
||||||
|
};
|
||||||
|
|
||||||
|
type Options = {
|
||||||
|
enabled?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function useRepoBranches(repoId?: string | null, opts?: Options) {
|
||||||
|
const enabled = (opts?.enabled ?? true) && !!repoId;
|
||||||
|
|
||||||
|
return useQuery<GitBranch[]>({
|
||||||
|
queryKey: repoBranchKeys.byRepo(repoId ?? undefined),
|
||||||
|
queryFn: () => repoApi.getBranches(repoId!),
|
||||||
|
enabled,
|
||||||
|
staleTime: 60_000,
|
||||||
|
refetchOnWindowFocus: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -20,8 +20,6 @@ import {
|
|||||||
ProjectRepo,
|
ProjectRepo,
|
||||||
Repo,
|
Repo,
|
||||||
RepoWithTargetBranch,
|
RepoWithTargetBranch,
|
||||||
RepositoryBranches,
|
|
||||||
ProjectBranchesResponse,
|
|
||||||
CreateProject,
|
CreateProject,
|
||||||
CreateProjectRepo,
|
CreateProjectRepo,
|
||||||
UpdateProjectRepo,
|
UpdateProjectRepo,
|
||||||
@@ -288,12 +286,6 @@ export const projectsApi = {
|
|||||||
return handleApiResponse<OpenEditorResponse>(response);
|
return handleApiResponse<OpenEditorResponse>(response);
|
||||||
},
|
},
|
||||||
|
|
||||||
getBranches: async (id: string): Promise<RepositoryBranches[]> => {
|
|
||||||
const response = await makeRequest(`/api/projects/${id}/branches`);
|
|
||||||
const data = await handleApiResponse<ProjectBranchesResponse>(response);
|
|
||||||
return data.repositories;
|
|
||||||
},
|
|
||||||
|
|
||||||
searchFiles: async (
|
searchFiles: async (
|
||||||
id: string,
|
id: string,
|
||||||
query: string,
|
query: string,
|
||||||
|
|||||||
@@ -6,12 +6,7 @@ import { Card, CardContent } from '@/components/ui/card';
|
|||||||
import { AlertTriangle, Plus, X } from 'lucide-react';
|
import { AlertTriangle, Plus, X } from 'lucide-react';
|
||||||
import { Loader } from '@/components/ui/loader';
|
import { Loader } from '@/components/ui/loader';
|
||||||
import { tasksApi } from '@/lib/api';
|
import { tasksApi } from '@/lib/api';
|
||||||
import type {
|
import type { TaskAttempt, RepoBranchStatus } from 'shared/types';
|
||||||
GitBranch,
|
|
||||||
TaskAttempt,
|
|
||||||
RepoBranchStatus,
|
|
||||||
RepositoryBranches,
|
|
||||||
} from 'shared/types';
|
|
||||||
import { openTaskForm } from '@/lib/openTaskForm';
|
import { openTaskForm } from '@/lib/openTaskForm';
|
||||||
import { FeatureShowcaseDialog } from '@/components/dialogs/global/FeatureShowcaseDialog';
|
import { FeatureShowcaseDialog } from '@/components/dialogs/global/FeatureShowcaseDialog';
|
||||||
import { showcases } from '@/config/showcases';
|
import { showcases } from '@/config/showcases';
|
||||||
@@ -24,8 +19,6 @@ import { useTaskAttempts } from '@/hooks/useTaskAttempts';
|
|||||||
import { useTaskAttempt } from '@/hooks/useTaskAttempt';
|
import { useTaskAttempt } from '@/hooks/useTaskAttempt';
|
||||||
import { useMediaQuery } from '@/hooks/useMediaQuery';
|
import { useMediaQuery } from '@/hooks/useMediaQuery';
|
||||||
import { useBranchStatus, useAttemptExecution } from '@/hooks';
|
import { useBranchStatus, useAttemptExecution } from '@/hooks';
|
||||||
import { useAttemptRepo } from '@/hooks/useAttemptRepo';
|
|
||||||
import { projectsApi } from '@/lib/api';
|
|
||||||
import { paths } from '@/lib/paths';
|
import { paths } from '@/lib/paths';
|
||||||
import { ExecutionProcessesProvider } from '@/contexts/ExecutionProcessesContext';
|
import { ExecutionProcessesProvider } from '@/contexts/ExecutionProcessesContext';
|
||||||
import { ClickedElementsProvider } from '@/contexts/ClickedElementsProvider';
|
import { ClickedElementsProvider } from '@/contexts/ClickedElementsProvider';
|
||||||
@@ -108,15 +101,11 @@ function GitErrorBanner() {
|
|||||||
function DiffsPanelContainer({
|
function DiffsPanelContainer({
|
||||||
attempt,
|
attempt,
|
||||||
selectedTask,
|
selectedTask,
|
||||||
projectId,
|
|
||||||
branchStatus,
|
branchStatus,
|
||||||
branches,
|
|
||||||
}: {
|
}: {
|
||||||
attempt: TaskAttempt | null;
|
attempt: TaskAttempt | null;
|
||||||
selectedTask: TaskWithAttemptStatus | null;
|
selectedTask: TaskWithAttemptStatus | null;
|
||||||
projectId: string;
|
|
||||||
branchStatus: RepoBranchStatus[] | null;
|
branchStatus: RepoBranchStatus[] | null;
|
||||||
branches: GitBranch[];
|
|
||||||
}) {
|
}) {
|
||||||
const { isAttemptRunning } = useAttemptExecution(attempt?.id);
|
const { isAttemptRunning } = useAttemptExecution(attempt?.id);
|
||||||
|
|
||||||
@@ -127,9 +116,7 @@ function DiffsPanelContainer({
|
|||||||
attempt && selectedTask
|
attempt && selectedTask
|
||||||
? {
|
? {
|
||||||
task: selectedTask,
|
task: selectedTask,
|
||||||
projectId,
|
|
||||||
branchStatus: branchStatus ?? null,
|
branchStatus: branchStatus ?? null,
|
||||||
branches,
|
|
||||||
isAttemptRunning,
|
isAttemptRunning,
|
||||||
selectedBranch: branchStatus?.[0]?.target_branch_name ?? null,
|
selectedBranch: branchStatus?.[0]?.target_branch_name ?? null,
|
||||||
}
|
}
|
||||||
@@ -297,23 +284,6 @@ export function ProjectTasks() {
|
|||||||
const { data: attempt } = useTaskAttempt(effectiveAttemptId);
|
const { data: attempt } = useTaskAttempt(effectiveAttemptId);
|
||||||
|
|
||||||
const { data: branchStatus } = useBranchStatus(attempt?.id);
|
const { data: branchStatus } = useBranchStatus(attempt?.id);
|
||||||
const { selectedRepoId } = useAttemptRepo(attempt?.id);
|
|
||||||
const [repoBranches, setRepoBranches] = useState<RepositoryBranches[]>([]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!projectId) return;
|
|
||||||
projectsApi
|
|
||||||
.getBranches(projectId)
|
|
||||||
.then(setRepoBranches)
|
|
||||||
.catch(() => setRepoBranches([]));
|
|
||||||
}, [projectId]);
|
|
||||||
|
|
||||||
const branches = useMemo(
|
|
||||||
() =>
|
|
||||||
repoBranches.find((r) => r.repository_id === selectedRepoId)?.branches ??
|
|
||||||
[],
|
|
||||||
[repoBranches, selectedRepoId]
|
|
||||||
);
|
|
||||||
|
|
||||||
const rawMode = searchParams.get('view') as LayoutMode;
|
const rawMode = searchParams.get('view') as LayoutMode;
|
||||||
const mode: LayoutMode =
|
const mode: LayoutMode =
|
||||||
@@ -1028,9 +998,7 @@ export function ProjectTasks() {
|
|||||||
<DiffsPanelContainer
|
<DiffsPanelContainer
|
||||||
attempt={attempt}
|
attempt={attempt}
|
||||||
selectedTask={selectedTask}
|
selectedTask={selectedTask}
|
||||||
projectId={projectId!}
|
|
||||||
branchStatus={branchStatus ?? null}
|
branchStatus={branchStatus ?? null}
|
||||||
branches={branches}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ import { CopyFilesField } from '@/components/projects/CopyFilesField';
|
|||||||
import { AutoExpandingTextarea } from '@/components/ui/auto-expanding-textarea';
|
import { AutoExpandingTextarea } from '@/components/ui/auto-expanding-textarea';
|
||||||
import { RepoPickerDialog } from '@/components/dialogs/shared/RepoPickerDialog';
|
import { RepoPickerDialog } from '@/components/dialogs/shared/RepoPickerDialog';
|
||||||
import { projectsApi } from '@/lib/api';
|
import { projectsApi } from '@/lib/api';
|
||||||
import { branchKeys } from '@/hooks/useBranches';
|
import { repoBranchKeys } from '@/hooks/useRepoBranches';
|
||||||
import type { Project, ProjectRepo, Repo, UpdateProject } from 'shared/types';
|
import type { Project, ProjectRepo, Repo, UpdateProject } from 'shared/types';
|
||||||
|
|
||||||
interface ProjectFormState {
|
interface ProjectFormState {
|
||||||
@@ -330,7 +330,7 @@ export function ProjectSettings() {
|
|||||||
queryKey: ['projectRepositories', selectedProjectId],
|
queryKey: ['projectRepositories', selectedProjectId],
|
||||||
});
|
});
|
||||||
queryClient.invalidateQueries({
|
queryClient.invalidateQueries({
|
||||||
queryKey: branchKeys.byProject(selectedProjectId),
|
queryKey: repoBranchKeys.byRepo(newRepo.id),
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setRepoError(
|
setRepoError(
|
||||||
@@ -353,7 +353,7 @@ export function ProjectSettings() {
|
|||||||
queryKey: ['projectRepositories', selectedProjectId],
|
queryKey: ['projectRepositories', selectedProjectId],
|
||||||
});
|
});
|
||||||
queryClient.invalidateQueries({
|
queryClient.invalidateQueries({
|
||||||
queryKey: branchKeys.byProject(selectedProjectId),
|
queryKey: repoBranchKeys.byRepo(repoId),
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setRepoError(
|
setRepoError(
|
||||||
|
|||||||
@@ -206,10 +206,6 @@ export type CheckAgentAvailabilityQuery = { executor: BaseCodingAgent, };
|
|||||||
|
|
||||||
export type CurrentUserResponse = { user_id: string, };
|
export type CurrentUserResponse = { user_id: string, };
|
||||||
|
|
||||||
export type RepositoryBranches = { repository_id: string, repository_name: string, branches: Array<GitBranch>, };
|
|
||||||
|
|
||||||
export type ProjectBranchesResponse = { repositories: Array<RepositoryBranches>, };
|
|
||||||
|
|
||||||
export type CreateFollowUpAttempt = { prompt: string, variant: string | null, retry_process_id: string | null, force_when_dirty: boolean | null, perform_git_reset: boolean | null, };
|
export type CreateFollowUpAttempt = { prompt: string, variant: string | null, retry_process_id: string | null, force_when_dirty: boolean | null, perform_git_reset: boolean | null, };
|
||||||
|
|
||||||
export type ChangeTargetBranchRequest = { repo_id: string, new_target_branch: string, };
|
export type ChangeTargetBranchRequest = { repo_id: string, new_target_branch: string, };
|
||||||
|
|||||||
Reference in New Issue
Block a user