platform/services/github/github-resources/src/components/GithubRepositories.svelte
Andrey Sobolev 4cce003364
UBERF-7959: Fix async issues (#6409)
Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
2024-08-28 11:51:20 +05:00

306 lines
9.2 KiB
Svelte

<script lang="ts">
import { AttachedDoc, Ref, WithLookup } from '@hcengineering/core'
import { GithubIntegration, GithubIntegrationRepository, GithubProject } from '@hcengineering/github'
import { getMetadata } from '@hcengineering/platform'
import presentation, { NavLink, getClient, isAdminUser } from '@hcengineering/presentation'
import MessageBox from '@hcengineering/presentation/src/components/MessageBox.svelte'
import tracker, { Project } from '@hcengineering/tracker'
import ui, {
Action,
Button,
Expandable,
Icon,
IconColStar,
IconMoreV,
Label,
Menu,
SearchEdit,
TimeSince,
getEventPositionElement,
showPopup
} from '@hcengineering/ui'
import { ObjectPresenter } from '@hcengineering/view-resources'
import github from '../plugin'
import ConnectProject from './ConnectProject.svelte'
import { githubLanguageColors } from './languageColors'
import { sendGHServiceRequest } from './utils'
export let integration: WithLookup<GithubIntegration>
export let projects: Project[] = []
export let giProjects: GithubProject[] = []
const client = getClient()
const asRepos = (docs: AttachedDoc[]) => docs as GithubIntegrationRepository[]
let search: string = ''
let limit = 5
async function disconnect (prj: GithubProject, repository: GithubIntegrationRepository): Promise<void> {
// We need to disable repository first
await client.update(repository, {
enabled: false,
githubProject: null
})
await client.update(prj, {
$pull: { repositories: repository._id }
})
// // We need to delete all issues related to repository
// const ops = client.apply('cleanup:' + repository._id)
// const issuesQuery = await client.findAll(
// github.mixin.GithubIssue,
// {
// space: prj._id as Ref<Project>,
// repository: repository._id
// },
// { projection: { _id: 1, _class: 1 } }
// )
// for (const i of issuesQuery) {
// await ops.removeDoc(i._class, prj._id, i._id)
// }
// const docInfo = await client.findAll(
// github.class.DocSyncInfo,
// {
// space: prj._id as Ref<Project>,
// repository: repository._id
// },
// { projection: { _id: 1, _class: 1 } }
// )
// for (const i of docInfo) {
// await ops.removeDoc(i._class, prj._id, i._id)
// }
// await ops.commit()
}
async function onDisconnect (
event: MouseEvent,
prj: GithubProject,
repository: GithubIntegrationRepository
): Promise<void> {
const issuesQuery = await client.findAll(
github.mixin.GithubIssue,
{
space: prj._id as Ref<Project>,
repository: repository._id
},
{ total: true, limit: 1 }
)
showPopup(MessageBox, {
label: github.string.UnlinkRepository,
message: github.string.UnlinkMessage,
params: { repositoryName: repository.name, prjName: prj.name, total: issuesQuery.total },
richMessage: true,
action: async () => {
await disconnect(prj, repository)
}
})
}
$: repos = asRepos(integration.$lookup?.repositories ?? [])
.filter((it) => it.name.toLowerCase().includes(search))
.sort((a, b) => {
const aprj = giProjects.find((it) => it.repositories.includes(a._id))
const bprj = giProjects.find((it) => it.repositories.includes(b._id))
if (aprj !== undefined && bprj === undefined) {
return -1
}
if (bprj !== undefined && aprj === undefined) {
return 1
}
return (b.updatedAt ?? 0) - (a.updatedAt ?? 0)
})
async function showMenu (evt: MouseEvent, prj: GithubProject, repository: GithubIntegrationRepository): Promise<void> {
if (isAdminUser()) {
const actions: Action[] = [
{
label: !repository.enabled ? github.string.Enable : github.string.Disable,
action: async (props: any, ev: Event) => {
void client.update(repository, { enabled: !repository.enabled })
}
}
]
showPopup(
Menu,
{
actions
},
getEventPositionElement(evt),
() => {}
)
}
}
</script>
<Expandable expanded={true}>
<svelte:fragment slot="title">
<span class="fs-title flex-row-center flex-between flex-grow flex">
<div class="ml-2 mr-2">
<img class="svg-large" src={integration.name.replace('github.com', 'avatars.githubusercontent.com')} />
</div>
{#if integration.name.length > 0}
{integration.name}
{#if (integration.type ?? '') !== ''}
({integration.type})
{/if}
{:else}
<Label label={github.string.ConnectionPending} />
{/if}
{#if !integration.alive}
<Label label={github.string.Closed} />
{/if}
</span>
</svelte:fragment>
<svelte:fragment slot="tools">
<Button
kind={'dangerous'}
label={github.string.RemoveInstallation}
on:click={() => {
showPopup(MessageBox, {
label: github.string.UnlinkInstallationTitle,
message: github.string.UnlinkInstallation,
params: {},
richMessage: true,
action: async () => {
await sendGHServiceRequest('installation-remove', {
installationId: integration.installationId,
token: getMetadata(presentation.metadata.Token) ?? ''
})
}
})
}}
/>
</svelte:fragment>
<div class="flex flex-row-center flex-between m-0-5">
<SearchEdit bind:value={search} width={'100%'} />
</div>
{#each repos.slice(0, limit) as repository}
{@const prj = giProjects.find((it) => it.repositories.includes(repository._id))}
<div
class="repository-card flex-col m-0-5"
class:selected={prj !== undefined}
class:disabled={prj !== undefined && !repository.enabled}
>
<div class="flex flex-row-center flex-between">
<div class="flex-row-center">
<NavLink href={repository.htmlURL}>{repository.name}</NavLink>
<div class="ml-2 visibility">
{repository.visibility}
</div>
{#if !repository.enabled && prj !== undefined}
<div class="ml-2 visibility">
<Label label={github.string.Disabled} />
</div>
{/if}
</div>
<div class="flex flex-row-center">
{#if prj !== undefined}
<div class="mr-2">
<Label label={github.string.LinkedWith} />
</div>
<ObjectPresenter _class={prj._class} objectId={prj._id} value={prj} />
<div class="ml-2">
<Button
kind={'dangerous'}
label={github.string.UnlinkFromProject}
size={'medium'}
on:click={(evt) => {
void onDisconnect(evt, prj, repository)
}}
/>
</div>
<div class="ml-2">
<Button
icon={IconMoreV}
size={'small'}
on:click={(evt) => {
void showMenu(evt, prj, repository)
}}
/>
</div>
{:else}
<ConnectProject {integration} {repository} {projects} />
{/if}
</div>
</div>
<div class="mt-2">
{repository.description ?? ''}
</div>
<div class="flex flex-row-center mt-4">
{#if repository.language != null}
<div class="flex-row-center mr-4">
<div class="lcolor-pin mr-1" style:background-color={githubLanguageColors[repository.language] ?? ''} />
{repository.language}
</div>
{/if}
<div class="flex-row-center">
<Icon icon={IconColStar} fill={'none'} size={'small'} />
<span class="ml-1">{repository.stargazers ?? 0}</span>
</div>
<div class="flex-row-center ml-4">
<Icon icon={github.icon.Forks} size={'small'} />
<span class="ml-1">{repository.forks ?? 0}</span>
</div>
<div class="flex-row-center ml-4">
<Icon icon={tracker.icon.Issue} size={'small'} />
<span class="ml-1">{repository.openIssues ?? 0}</span>
</div>
{#if repository.updatedAt !== undefined}
<div class="flex-row-center ml-4">
<Label label={github.string.Updated} />
<span class="ml-2">
<TimeSince value={repository.updatedAt} />
</span>
</div>
{/if}
</div>
</div>
{/each}
{#if repos.length > limit}
<Button
label={ui.string.ShowMore}
on:click={() => {
limit = limit + 10
}}
/>
{/if}
</Expandable>
<style lang="scss">
.bordered {
border: 1px dashed var(--theme-divider-color);
}
.repository-card {
border: 1px solid var(--theme-divider-color);
border-radius: 8px;
padding: 1rem;
&.selected {
background-color: var(--theme-divider-color);
}
&.disabled {
border-color: red;
}
}
.visibility {
border: 1px solid var(--theme-divider-color);
border-radius: 2em;
padding: 0 7px;
height: fit-content;
}
.lcolor-pin {
border-radius: 50%;
width: 12px;
height: 12px;
background: var(--theme-divider-color);
}
</style>