mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-08-22 16:02:24 +00:00
feat: support search for gh repo when pagination needed (#6982)
* frontend changes gh app search repo * feat: support search for gh repo when pagination needed * backend * ee repo ref
This commit is contained in:
@@ -1 +1 @@
|
||||
a81fe447c13c5dd59eb60594123f36984ccb7058
|
||||
a2c83c51c9b501e3660fe82416a73e487356158d
|
||||
@@ -603,6 +603,13 @@ paths:
|
||||
operationId: getGlobalConnectedRepositories
|
||||
tags:
|
||||
- Git Sync
|
||||
parameters:
|
||||
- name: search
|
||||
in: query
|
||||
description: Search repositories by name
|
||||
required: false
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
"200":
|
||||
description: connected repositories
|
||||
@@ -19537,10 +19544,18 @@ components:
|
||||
required:
|
||||
- name
|
||||
- url
|
||||
total_count:
|
||||
type: number
|
||||
description: Total number of repositories available for this installation
|
||||
per_page:
|
||||
type: number
|
||||
description: Number of repositories loaded per page
|
||||
required:
|
||||
- installation_id
|
||||
- account_id
|
||||
- repositories
|
||||
- total_count
|
||||
- per_page
|
||||
|
||||
WorkspaceGithubInstallation:
|
||||
type: object
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
loadGithubInstallations,
|
||||
startInstallationCheck,
|
||||
stopInstallationCheck,
|
||||
getRepositories,
|
||||
addInstallationToWorkspace,
|
||||
deleteInstallation,
|
||||
exportInstallation,
|
||||
@@ -18,6 +17,7 @@
|
||||
handleInstallClick,
|
||||
type GitHubAppState
|
||||
} from '$lib/githubApp'
|
||||
import RepositorySelector from './RepositorySelector.svelte'
|
||||
|
||||
interface Props {
|
||||
resourceType: string
|
||||
@@ -215,17 +215,21 @@
|
||||
</select>
|
||||
</div>
|
||||
{#if githubState.selectedGHAppAccountId}
|
||||
<div class="flex flex-col gap-1 flex-1">
|
||||
<p class="text-sm font-semibold text-secondary">Repository</p>
|
||||
<div class="flex flex-row gap-2">
|
||||
<select bind:value={githubState.selectedGHAppRepository}>
|
||||
<option value="" disabled selected>Select Repository</option>
|
||||
{#each getRepositories(githubState, githubState.selectedGHAppAccountId) as repository (repository.url)}
|
||||
<option value={repository.url}>{repository.name}</option>
|
||||
{/each}
|
||||
</select>
|
||||
{@const selectedInstallation = githubState.workspaceGithubInstallations.find(
|
||||
(inst) => inst.account_id === githubState.selectedGHAppAccountId
|
||||
)}
|
||||
{#if selectedInstallation}
|
||||
<div class="flex flex-col gap-1 flex-1">
|
||||
<p class="text-sm font-semibold text-secondary">Repository</p>
|
||||
<RepositorySelector
|
||||
bind:selectedRepository={githubState.selectedGHAppRepository}
|
||||
accountId={githubState.selectedGHAppAccountId}
|
||||
initialRepositories={selectedInstallation.repositories}
|
||||
totalCount={selectedInstallation.total_count}
|
||||
perPage={selectedInstallation.per_page}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
<div class="pt-[26px]">
|
||||
<Button
|
||||
|
||||
@@ -0,0 +1,136 @@
|
||||
<script lang="ts">
|
||||
import { GitSyncService } from '$lib/gen'
|
||||
import Select from './select/Select.svelte'
|
||||
import { debounce } from '$lib/utils'
|
||||
|
||||
interface Repository {
|
||||
name: string
|
||||
url: string
|
||||
}
|
||||
|
||||
interface Props {
|
||||
disabled?: boolean
|
||||
selectedRepository?: string | undefined
|
||||
accountId: string
|
||||
initialRepositories: Repository[]
|
||||
totalCount: number
|
||||
perPage: number
|
||||
containerClass?: string
|
||||
minWidth?: string
|
||||
onError?: (error: Error) => void
|
||||
}
|
||||
|
||||
let {
|
||||
disabled = false,
|
||||
selectedRepository = $bindable(),
|
||||
accountId,
|
||||
initialRepositories,
|
||||
totalCount,
|
||||
perPage,
|
||||
containerClass = 'flex-1',
|
||||
minWidth = '160px',
|
||||
onError
|
||||
}: Props = $props()
|
||||
|
||||
let isFetching = $state(false)
|
||||
let selectFilterText = $state('')
|
||||
let lastSearchQuery = $state('')
|
||||
|
||||
// Use a derived value that always reflects current repos
|
||||
let availableRepos = $derived(() => {
|
||||
// If we have search results from backend, use those
|
||||
// Otherwise use initial repositories
|
||||
return searchResults.length > 0 ? searchResults : initialRepositories
|
||||
})
|
||||
|
||||
// Track search results from backend
|
||||
let searchResults = $state<Repository[]>([])
|
||||
|
||||
// Only enable search mode if total count exceeds per_page limit
|
||||
const searchMode = $derived(totalCount > perPage)
|
||||
|
||||
// Debounced search function
|
||||
const debouncedSearch = debounce(async (query: string) => {
|
||||
await searchRepositories(query)
|
||||
}, 500)
|
||||
|
||||
// Watch for filter text changes and trigger backend search
|
||||
$effect(() => {
|
||||
if (searchMode && selectFilterText !== undefined && selectFilterText !== lastSearchQuery) {
|
||||
if (selectFilterText.length >= 1) {
|
||||
// Only search backend if we have more repos than currently loaded
|
||||
if (totalCount > initialRepositories.length) {
|
||||
debouncedSearch.debounced(selectFilterText)
|
||||
}
|
||||
} else if (selectFilterText.length === 0) {
|
||||
lastSearchQuery = ''
|
||||
searchResults = []
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
async function searchRepositories(query: string) {
|
||||
if (!query) {
|
||||
searchResults = []
|
||||
lastSearchQuery = ''
|
||||
return
|
||||
}
|
||||
|
||||
// Don't search if we already have all repos
|
||||
if (totalCount <= initialRepositories.length) {
|
||||
return
|
||||
}
|
||||
|
||||
isFetching = true
|
||||
try {
|
||||
const installations = await GitSyncService.getGlobalConnectedRepositories({
|
||||
search: query
|
||||
})
|
||||
|
||||
// Find the matching installation and get its repositories
|
||||
const installation = installations.find((inst) => inst.account_id === accountId)
|
||||
searchResults = installation?.repositories || []
|
||||
lastSearchQuery = query
|
||||
isFetching = false
|
||||
return searchResults
|
||||
} catch (error) {
|
||||
isFetching = false
|
||||
onError?.(error)
|
||||
console.error('Error searching repositories:', error)
|
||||
searchResults = []
|
||||
lastSearchQuery = ''
|
||||
return []
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class={containerClass}>
|
||||
{#if searchMode}
|
||||
<div class="flex flex-col gap-1">
|
||||
<Select
|
||||
containerStyle={'min-width: ' + minWidth}
|
||||
items={availableRepos().map((repo) => ({
|
||||
label: repo.name,
|
||||
value: repo.url
|
||||
}))}
|
||||
placeholder={isFetching ? 'Searching...' : `Search all repositories...`}
|
||||
clearable
|
||||
disabled={disabled || isFetching}
|
||||
bind:filterText={selectFilterText}
|
||||
bind:value={selectedRepository}
|
||||
/>
|
||||
{#if totalCount > initialRepositories.length}
|
||||
<span class="text-3xs pl-1 text-tertiary">
|
||||
Loaded {initialRepositories.length} of {totalCount} repositories.
|
||||
</span>
|
||||
{/if}
|
||||
</div>
|
||||
{:else}
|
||||
<select bind:value={selectedRepository} {disabled} class="w-full">
|
||||
<option value="" disabled selected>Select repository</option>
|
||||
{#each initialRepositories as repository (repository.url)}
|
||||
<option value={repository.url}>{repository.name}</option>
|
||||
{/each}
|
||||
</select>
|
||||
{/if}
|
||||
</div>
|
||||
Reference in New Issue
Block a user