mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-10-17 16:03:06 +00:00
* fix(frontend): key the GitHub App installation selector on installation_id The GitHub Account ID dropdown used `account_id` as both the option value and the lookup key. A workspace can hold several installations for the same org (re-installed, or added from another workspace), so `.find()` resolved to whichever came first: picking the live installation could hand back a stale, token-errored one whose `repositories` are empty, leaving the repository dropdown blank. `RepositorySelector`'s pagination matched the same way and appended the wrong installation's page. Both now key on `installation_id`, and the dropdown appends the installation id to the label only for orgs that appear more than once. Switching installation remounts `RepositorySelector` and clears the selected repository, so its loaded pages no longer carry over. Fixes WIN-2448 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(frontend): shorten the duplicate-org installation label Drop the "installation" word from the disambiguating suffix: the id alone already tells the two entries apart, and it keeps the errored variant short enough to read at a glance. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
129 lines
3.4 KiB
Svelte
129 lines
3.4 KiB
Svelte
<script lang="ts">
|
|
import { untrack } from 'svelte'
|
|
import { GitSyncService } from '$lib/gen'
|
|
import Select from './select/Select.svelte'
|
|
|
|
interface Repository {
|
|
name: string
|
|
url: string
|
|
}
|
|
|
|
interface Props {
|
|
disabled?: boolean
|
|
selectedRepository?: string | undefined
|
|
installationId: number
|
|
initialRepositories: Repository[]
|
|
totalCount: number
|
|
perPage: number
|
|
containerClass?: string
|
|
minWidth?: string
|
|
onError?: (error: Error) => void
|
|
}
|
|
|
|
let {
|
|
disabled = false,
|
|
selectedRepository = $bindable(),
|
|
installationId,
|
|
initialRepositories,
|
|
totalCount,
|
|
perPage,
|
|
containerClass = 'flex-1',
|
|
minWidth = '160px',
|
|
onError
|
|
}: Props = $props()
|
|
|
|
// Track all loaded repositories across pages
|
|
let loadedRepositories = $state<Repository[]>(untrack(() => initialRepositories))
|
|
let currentPage = $state(1)
|
|
let isLoadingMore = $state(false)
|
|
|
|
// Client-side filtered repositories based on search text
|
|
let filterText = $state('')
|
|
|
|
const filteredRepositories = $derived(() => {
|
|
if (!filterText) {
|
|
return loadedRepositories
|
|
}
|
|
const search = filterText.toLowerCase()
|
|
return loadedRepositories.filter((repo) => repo.name.toLowerCase().includes(search))
|
|
})
|
|
|
|
// Check if there are more repos to load
|
|
const hasMoreRepos = $derived(loadedRepositories.length < totalCount)
|
|
|
|
// Show the select when we have more repos than the initial load
|
|
const showSearchableSelect = $derived(totalCount > perPage)
|
|
|
|
async function loadMoreRepositories() {
|
|
if (isLoadingMore || !hasMoreRepos) {
|
|
return
|
|
}
|
|
|
|
isLoadingMore = true
|
|
const nextPage = currentPage + 1
|
|
|
|
try {
|
|
const installations = await GitSyncService.getGlobalConnectedRepositories({
|
|
page: nextPage
|
|
})
|
|
|
|
// Match on installation_id: several installations can share one account_id
|
|
const installation = installations.find((inst) => inst.installation_id === installationId)
|
|
|
|
if (installation?.repositories) {
|
|
// Append new repos to existing ones
|
|
loadedRepositories = [...loadedRepositories, ...installation.repositories]
|
|
currentPage = nextPage
|
|
}
|
|
} catch (error) {
|
|
onError?.(error as Error)
|
|
console.error('Error loading more repositories:', error)
|
|
} finally {
|
|
isLoadingMore = false
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<div class={containerClass}>
|
|
{#if showSearchableSelect}
|
|
<div class="flex flex-col gap-1">
|
|
<Select
|
|
containerStyle={'min-width: ' + minWidth}
|
|
items={filteredRepositories().map((repo) => ({
|
|
label: repo.name,
|
|
value: repo.url
|
|
}))}
|
|
placeholder="Select repository..."
|
|
clearable
|
|
{disabled}
|
|
bind:filterText
|
|
bind:value={selectedRepository}
|
|
/>
|
|
{#if hasMoreRepos}
|
|
<div class="flex items-center gap-2 pl-1">
|
|
<span class="text-2xs text-tertiary">
|
|
Loaded {loadedRepositories.length} of {totalCount}
|
|
</span>
|
|
{#if hasMoreRepos}
|
|
<button
|
|
type="button"
|
|
class="text-2xs text-blue-500 hover:text-blue-600 dark:text-blue-400 dark:hover:text-blue-300 cursor-pointer disabled:opacity-50 disabled:cursor-not-allowed"
|
|
onclick={loadMoreRepositories}
|
|
disabled={isLoadingMore}
|
|
>
|
|
{isLoadingMore ? 'loading...' : 'load more...'}
|
|
</button>
|
|
{/if}
|
|
</div>
|
|
{/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>
|