mirror of
https://github.com/hcengineering/platform.git
synced 2026-08-25 13:52:24 +02:00
Move services to public (#6156)
Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
This commit is contained in:
@@ -0,0 +1,42 @@
|
||||
<script lang="ts">
|
||||
import { Ref, Space } from '@hcengineering/core'
|
||||
import ui, { ModernButton } from '@hcengineering/ui'
|
||||
import { GithubProject } from '@hcengineering/github'
|
||||
import github from '../plugin'
|
||||
import { githubAuth, githubProjects, onAuthorize } from './utils'
|
||||
import { getMetadata } from '@hcengineering/platform'
|
||||
|
||||
export let space: Ref<Space>
|
||||
export let kind: 'primary' | 'secondary' | 'tertiary' | 'negative' = 'secondary'
|
||||
export let readonly: boolean = false
|
||||
|
||||
$: auth = $githubAuth
|
||||
|
||||
$: spaceObj = $githubProjects.get(space as Ref<GithubProject>)
|
||||
</script>
|
||||
|
||||
{#if spaceObj !== undefined}
|
||||
{#if auth === undefined || auth.login === '' || auth.error != null}
|
||||
{#if !readonly}
|
||||
<ModernButton
|
||||
label={github.string.Authorize}
|
||||
labelParams={{ title: getMetadata(ui.metadata.PlatformTitle) }}
|
||||
icon={github.icon.Github}
|
||||
kind={'primary'}
|
||||
size={'small'}
|
||||
on:click={() => {
|
||||
void onAuthorize()
|
||||
}}
|
||||
/>
|
||||
{/if}
|
||||
{:else}
|
||||
<ModernButton
|
||||
icon={github.icon.Github}
|
||||
label={github.string.AuthorizeAs}
|
||||
labelParams={{ login: auth.login }}
|
||||
disabled={true}
|
||||
{kind}
|
||||
size={'small'}
|
||||
/>
|
||||
{/if}
|
||||
{/if}
|
||||
@@ -0,0 +1,8 @@
|
||||
<script lang="ts">
|
||||
import { Integration } from '@hcengineering/setting'
|
||||
import Connect from './Connect.svelte'
|
||||
|
||||
export let integration: Integration
|
||||
</script>
|
||||
|
||||
<Connect bind:integration on:close />
|
||||
@@ -0,0 +1,174 @@
|
||||
<!--
|
||||
//
|
||||
// Copyright © 2023 Hardcore Engineering Inc.
|
||||
//GithubIntegrationsGithubIntegrations
|
||||
-->
|
||||
<script lang="ts">
|
||||
import GithubIntegrations from './GithubIntegerations.svelte'
|
||||
|
||||
import GithubPersonProfile from './GithubPersonProfile.svelte'
|
||||
|
||||
import { Analytics } from '@hcengineering/analytics'
|
||||
import { WithLookup, getCurrentAccount } from '@hcengineering/core'
|
||||
import { getEmbeddedLabel, getMetadata, translate } from '@hcengineering/platform'
|
||||
import presentation, { Card, HTMLViewer, NavLink, createQuery } from '@hcengineering/presentation'
|
||||
import { Integration } from '@hcengineering/setting'
|
||||
import tracker, { Project } from '@hcengineering/tracker'
|
||||
import ui, { Button, Label, Loading, TabItem, TabList, location, ticker } from '@hcengineering/ui'
|
||||
import { GithubAuthentication, GithubIntegration } from '@hcengineering/github'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import github from '../plugin'
|
||||
import { onAuthorize } from './utils'
|
||||
|
||||
export let integration: Integration
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
const query = createQuery()
|
||||
const authQuery = createQuery()
|
||||
const projectsQuery = createQuery()
|
||||
|
||||
let loading = true
|
||||
let integrations: WithLookup<GithubIntegration>[] = []
|
||||
let auth: GithubAuthentication | undefined
|
||||
let projects: Project[] = []
|
||||
|
||||
query.query(
|
||||
github.class.GithubIntegration,
|
||||
{},
|
||||
(res) => {
|
||||
integrations = res
|
||||
loading = false
|
||||
},
|
||||
{
|
||||
lookup: {
|
||||
_id: {
|
||||
repositories: github.class.GithubIntegrationRepository
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
authQuery.query(github.class.GithubAuthentication, {}, (res) => {
|
||||
;[auth] = res
|
||||
})
|
||||
|
||||
projectsQuery.query(tracker.class.Project, {}, (res) => {
|
||||
projects = res
|
||||
})
|
||||
|
||||
function save (): void {
|
||||
dispatch('close', { value: auth?.login ?? '-' })
|
||||
}
|
||||
function onConnect (): void {
|
||||
const state = btoa(
|
||||
JSON.stringify({
|
||||
accountId: getCurrentAccount()._id,
|
||||
op: 'installation',
|
||||
workspace: $location.path[1],
|
||||
token: getMetadata(presentation.metadata.Token)
|
||||
})
|
||||
)
|
||||
Analytics.handleEvent('Install github app clicked')
|
||||
const githubApp = getMetadata(github.metadata.GithubApplication) ?? ''
|
||||
window.open(`https://github.com/apps/${githubApp}/installations/new?state=${state}`)
|
||||
}
|
||||
|
||||
const tabs: TabItem[] = [
|
||||
{
|
||||
id: 'personal',
|
||||
labelIntl: getEmbeddedLabel('Your Github account')
|
||||
},
|
||||
{
|
||||
id: 'installations',
|
||||
labelIntl: getEmbeddedLabel('Github Repositories')
|
||||
}
|
||||
]
|
||||
let selectedTab: string = tabs[0].id
|
||||
|
||||
$: loading = $ticker - (auth?.authRequestTime ?? 0) < 5000
|
||||
</script>
|
||||
|
||||
<Card
|
||||
label={github.string.GithubDesc}
|
||||
okAction={save}
|
||||
canSave={true}
|
||||
okLabel={presentation.string.Ok}
|
||||
on:close={() => dispatch('close')}
|
||||
on:changeContent
|
||||
>
|
||||
{#if loading}
|
||||
<Loading />
|
||||
{:else}
|
||||
<TabList
|
||||
items={tabs}
|
||||
bind:selected={selectedTab}
|
||||
kind={'plain'}
|
||||
on:select={(result) => {
|
||||
selectedTab = result.detail.id
|
||||
}}
|
||||
/>
|
||||
<div class="flex flex-grow mt-4">
|
||||
{#if selectedTab === 'personal'}
|
||||
<div class="flex-row flex-grow p-3">
|
||||
{#if auth}
|
||||
<GithubPersonProfile {auth} />
|
||||
{:else}
|
||||
<Label label={github.string.PleaseAuthorizeAs} params={{ title: getMetadata(ui.metadata.PlatformTitle) }} />
|
||||
{/if}
|
||||
</div>
|
||||
{:else if selectedTab === 'installations'}
|
||||
<div class="flex flex-col flex-grow">
|
||||
<GithubIntegrations {integrations} {projects}></GithubIntegrations>
|
||||
<div>
|
||||
{#if integrations.length === 0}
|
||||
<div class="flex-grow flex-col">
|
||||
<div class="">
|
||||
{#await translate(github.string.NoIntegrationsConfigured, { appName: getMetadata(github.metadata.GithubApplication), title: getMetadata(ui.metadata.PlatformTitle) }) then msg}
|
||||
<HTMLViewer value={msg} />
|
||||
{/await}
|
||||
<div class="underline">
|
||||
<NavLink href={'https://github.com/settings/installations/'} noUnderline={false}>
|
||||
<b><Label label={github.string.Uninstall} /></b>
|
||||
</NavLink>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
<svelte:fragment slot="footer">
|
||||
{#if selectedTab === 'personal'}
|
||||
{#if auth !== undefined && $ticker - (auth?.authRequestTime ?? 0) > 0 && auth.login === ''}
|
||||
<div class="fs-title" style:color={'red'}>
|
||||
<Label label={github.string.PleaseRetry} />
|
||||
</div>
|
||||
{/if}
|
||||
<Button
|
||||
label={auth !== undefined ? github.string.ReAuthorize : github.string.Authorize}
|
||||
labelParams={{ title: getMetadata(ui.metadata.PlatformTitle) }}
|
||||
{loading}
|
||||
on:click={() => onAuthorize()}
|
||||
size={'large'}
|
||||
kind={'primary'}
|
||||
/>
|
||||
{:else if selectedTab === 'installations'}
|
||||
<Button
|
||||
label={integrations.length === 0 ? github.string.InstallApp : github.string.Configure}
|
||||
labelParams={{ title: getMetadata(ui.metadata.PlatformTitle) }}
|
||||
on:click={onConnect}
|
||||
size={'large'}
|
||||
kind={'primary'}
|
||||
/>
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
</Card>
|
||||
|
||||
<style lang="scss">
|
||||
.bordered {
|
||||
border: 1px dashed var(--theme-divider-color);
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,116 @@
|
||||
<!--
|
||||
//
|
||||
// Copyright © 2023 Hardcore Engineering Inc.
|
||||
//
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { Account, Ref } from '@hcengineering/core'
|
||||
import ui, { Label, Location, Spinner, location } from '@hcengineering/ui'
|
||||
import { onDestroy } from 'svelte'
|
||||
import github from '../plugin'
|
||||
import { sendGHServiceRequest } from './utils'
|
||||
import { getMetadata } from '@hcengineering/platform'
|
||||
|
||||
let autoClose = 10
|
||||
|
||||
let promise: Promise<void> | undefined
|
||||
|
||||
let interval: any
|
||||
|
||||
let installationId: number | undefined
|
||||
let showSelector = false
|
||||
|
||||
async function createIntegration (loc: Location): Promise<void> {
|
||||
if (loc.query?.error != null) {
|
||||
window.close()
|
||||
return
|
||||
}
|
||||
installationId = parseInt(loc.query?.installation_id ?? '-1')
|
||||
const state = loc.query?.state
|
||||
const code = loc.query?.code
|
||||
const setupAction = loc.query?.setup_action
|
||||
|
||||
if (state == null) {
|
||||
// we need to show a list of workspaces available to install application ito.
|
||||
if (setupAction === 'install') {
|
||||
// Show error
|
||||
showSelector = true
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
function doAutoClose (): void {
|
||||
autoClose = 3
|
||||
clearInterval(interval)
|
||||
interval = setInterval(() => {
|
||||
autoClose = autoClose - 1
|
||||
if (autoClose === 0) {
|
||||
clearInterval(interval)
|
||||
window.close()
|
||||
}
|
||||
}, 1000)
|
||||
}
|
||||
|
||||
const rawState = JSON.parse(atob(state))
|
||||
const {
|
||||
accountId,
|
||||
op,
|
||||
workspace,
|
||||
token
|
||||
}: { accountId: Ref<Account>, workspace: string, op: string, token: string } = rawState
|
||||
|
||||
if (op === 'installation') {
|
||||
if (installationId == null || setupAction === null) {
|
||||
window.close()
|
||||
return
|
||||
}
|
||||
promise = sendGHServiceRequest('installation', {
|
||||
installationId,
|
||||
workspace,
|
||||
accountId,
|
||||
token
|
||||
}).then(doAutoClose)
|
||||
}
|
||||
if (code !== null) {
|
||||
promise = sendGHServiceRequest('auth', {
|
||||
code,
|
||||
state,
|
||||
workspace,
|
||||
accountId
|
||||
}).then(doAutoClose)
|
||||
}
|
||||
}
|
||||
|
||||
onDestroy(
|
||||
location.subscribe((loc) => {
|
||||
void createIntegration(loc)
|
||||
})
|
||||
)
|
||||
</script>
|
||||
|
||||
{#if showSelector}
|
||||
<div class="flex flex-center flex-col h-full w-full">
|
||||
<span class="text-lg">
|
||||
<Label label={github.string.SelectWorkspaceToInstallApp} />
|
||||
</span>
|
||||
<span class="text-lg">
|
||||
<Label label={github.string.SelectWorkspaceToInstallAppMsg} />
|
||||
</span>
|
||||
<a href={`https://github.com/settings/installations/${installationId}`}>
|
||||
<Label label={github.string.Configure} params={{ title: getMetadata(ui.metadata.PlatformTitle) }} />
|
||||
</a>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="flex flex-center fs-title text-center items-center h-full w-full">
|
||||
{#await promise}
|
||||
<div class="flex flex-row-center flex-center flex-grow">
|
||||
<Spinner />
|
||||
<div class="ml-1">
|
||||
<Label label={github.string.Processing} />
|
||||
</div>
|
||||
</div>
|
||||
{:then}
|
||||
<Label label={github.string.AutoClose} params={{ time: autoClose }} />
|
||||
{/await}
|
||||
</div>
|
||||
{/if}
|
||||
@@ -0,0 +1,160 @@
|
||||
<script lang="ts">
|
||||
import { Analytics } from '@hcengineering/analytics'
|
||||
import core, { ClassifierKind, Ref, WithLookup, generateId } from '@hcengineering/core'
|
||||
import { getEmbeddedLabel, getMetadata, translate } from '@hcengineering/platform'
|
||||
import { getClient } from '@hcengineering/presentation'
|
||||
import task, { TaskType, updateProjectType, type TaskStatusFactory } from '@hcengineering/task'
|
||||
import tracker, { Project, createStatesData } from '@hcengineering/tracker'
|
||||
import ui, {
|
||||
Button,
|
||||
IconChevronDown,
|
||||
PaletteColorIndexes,
|
||||
getEventPopupPositionElement,
|
||||
showPopup
|
||||
} from '@hcengineering/ui'
|
||||
import DropdownLabelsPopup from '@hcengineering/ui/src/components/DropdownLabelsPopup.svelte'
|
||||
import { GithubIntegration, GithubIntegrationRepository, githubPullRequestStates } from '@hcengineering/github'
|
||||
import github from '../plugin'
|
||||
|
||||
export let integration: WithLookup<GithubIntegration>
|
||||
export let repository: GithubIntegrationRepository
|
||||
export let projects: Project[] = []
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export const baseIssueTaskStatuses: TaskStatusFactory[] = [
|
||||
{
|
||||
category: task.statusCategory.UnStarted,
|
||||
statuses: [['Backlog', PaletteColorIndexes.Cloud, tracker.status.Backlog]]
|
||||
},
|
||||
{
|
||||
category: task.statusCategory.Active,
|
||||
statuses: [
|
||||
['Coding', PaletteColorIndexes.Porpoise, tracker.status.Coding],
|
||||
['Under review', PaletteColorIndexes.Cerulean, tracker.status.UnderReview]
|
||||
]
|
||||
},
|
||||
{ category: task.statusCategory.Won, statuses: [['Done', PaletteColorIndexes.Grass, tracker.status.Done]] },
|
||||
{
|
||||
category: task.statusCategory.Lost,
|
||||
statuses: [['Canceled', PaletteColorIndexes.Coin, tracker.status.Canceled]]
|
||||
}
|
||||
]
|
||||
|
||||
const client = getClient()
|
||||
|
||||
async function assignRepository (project: Ref<Project>): Promise<void> {
|
||||
if (project === undefined) {
|
||||
return
|
||||
}
|
||||
const projectInst = projects.find((it) => it._id === project)
|
||||
if (projectInst === undefined) {
|
||||
return
|
||||
}
|
||||
|
||||
Analytics.handleEvent('Connect project to github')
|
||||
|
||||
if (!client.getHierarchy().hasMixin(projectInst, github.mixin.GithubProject)) {
|
||||
// We need to add GithubProject mixin
|
||||
const mixinId = await getClient().createDoc(core.class.Mixin, core.space.Model, {
|
||||
extends: github.mixin.GithubIssue,
|
||||
kind: ClassifierKind.MIXIN,
|
||||
label: getEmbeddedLabel(projectInst.name),
|
||||
hidden: false,
|
||||
icon: github.icon.Github
|
||||
})
|
||||
await getClient().createMixin(
|
||||
projectInst._id,
|
||||
tracker.class.Project,
|
||||
core.space.Space,
|
||||
github.mixin.GithubProject,
|
||||
{
|
||||
integration: integration._id,
|
||||
repositories: [],
|
||||
mixinClass: mixinId,
|
||||
mappings: []
|
||||
}
|
||||
)
|
||||
}
|
||||
// Check if issue and pull request are missing, we need to add them, and mark both of them as system to prevent deletion.
|
||||
|
||||
const issueId: Ref<TaskType> = generateId()
|
||||
|
||||
await updateProjectType(client, projectInst.type, [
|
||||
{
|
||||
_id: issueId,
|
||||
descriptor: tracker.descriptors.Issue,
|
||||
kind: 'both',
|
||||
name: await translate(tracker.string.Issue, {}),
|
||||
ofClass: tracker.class.Issue,
|
||||
statusCategories: baseIssueTaskStatuses.map((it) => it.category),
|
||||
statusClass: tracker.class.IssueStatus,
|
||||
icon: tracker.icon.Issue,
|
||||
color: 0,
|
||||
allowedAsChildOf: [issueId],
|
||||
factory: createStatesData(baseIssueTaskStatuses)
|
||||
},
|
||||
{
|
||||
_id: generateId(),
|
||||
descriptor: github.descriptors.PullRequest,
|
||||
kind: 'both',
|
||||
name: await translate(github.string.PullRequest, {}),
|
||||
ofClass: github.class.GithubPullRequest,
|
||||
statusCategories: githubPullRequestStates.map((it) => it.category),
|
||||
statusClass: tracker.class.IssueStatus,
|
||||
icon: tracker.icon.Issue,
|
||||
color: 0,
|
||||
allowedAsChildOf: [issueId],
|
||||
factory: createStatesData(githubPullRequestStates)
|
||||
}
|
||||
])
|
||||
|
||||
const githubProject = client.getHierarchy().as(projectInst, github.mixin.GithubProject)
|
||||
|
||||
void getClient().update(githubProject, {
|
||||
$push: { repositories: repository._id }
|
||||
})
|
||||
void getClient().update(repository, { githubProject: githubProject._id, enabled: true })
|
||||
}
|
||||
|
||||
$: allowedProjects = projects.filter(
|
||||
(it) =>
|
||||
(client.getHierarchy().asIf(it, github.mixin.GithubProject)?.integration ?? integration._id) === integration._id
|
||||
)
|
||||
async function selectProject (event: MouseEvent): Promise<void> {
|
||||
showPopup(
|
||||
DropdownLabelsPopup,
|
||||
{
|
||||
enableSearch: allowedProjects.length > 5,
|
||||
items: [
|
||||
...allowedProjects.map((it) => ({ id: `${it._id}`, label: it.name })),
|
||||
{ id: '#', label: await translate(tracker.string.NewProject, {}) }
|
||||
]
|
||||
},
|
||||
getEventPopupPositionElement(event),
|
||||
(result) => {
|
||||
if (result != null) {
|
||||
if (result === '#') {
|
||||
showPopup(tracker.component.CreateProject, {}, 'center', (prj) => {
|
||||
if (prj != null) {
|
||||
void assignRepository(prj)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
void assignRepository(result)
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
</script>
|
||||
|
||||
<Button
|
||||
size={'medium'}
|
||||
kind={'primary'}
|
||||
label={github.string.LinkToProject}
|
||||
labelParams={{ title: getMetadata(ui.metadata.PlatformTitle) }}
|
||||
on:click={selectProject}
|
||||
iconRight={IconChevronDown}
|
||||
/>
|
||||
@@ -0,0 +1,15 @@
|
||||
<!--
|
||||
// Copyright © 2023 Hardcore Engineering Inc.
|
||||
//
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { GithubPullRequest } from '@hcengineering/github'
|
||||
import PullRequestDiff from './PullRequestDiff.svelte'
|
||||
|
||||
export let object: GithubPullRequest
|
||||
export let embedded = false
|
||||
</script>
|
||||
|
||||
<div class="mt-6">
|
||||
<PullRequestDiff pullRequest={object} />
|
||||
</div>
|
||||
@@ -0,0 +1,14 @@
|
||||
<!--
|
||||
// Copyright © 2023 Hardcore Engineering Inc.
|
||||
//
|
||||
-->
|
||||
<script lang="ts">
|
||||
export let size: 'small' | 'medium' | 'large'
|
||||
const fill: string = 'currentColor'
|
||||
</script>
|
||||
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="svg-{size}" {fill} viewBox="0 0 24 24">
|
||||
<path
|
||||
d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z"
|
||||
/>
|
||||
</svg>
|
||||
@@ -0,0 +1,56 @@
|
||||
<script lang="ts">
|
||||
import GithubRepositories from './GithubRepositories.svelte'
|
||||
|
||||
import { WithLookup } from '@hcengineering/core'
|
||||
import { getClient } from '@hcengineering/presentation'
|
||||
import { Project } from '@hcengineering/tracker'
|
||||
import { Scroller } from '@hcengineering/ui'
|
||||
import { GithubIntegration } from '@hcengineering/github'
|
||||
import github from '../plugin'
|
||||
|
||||
export let integrations: WithLookup<GithubIntegration>[] = []
|
||||
export let projects: Project[] = []
|
||||
|
||||
const client = getClient()
|
||||
|
||||
$: githubProjects = client.getHierarchy().asIfArray(projects, github.mixin.GithubProject)
|
||||
</script>
|
||||
|
||||
{#if integrations.length > 0}
|
||||
<Scroller shrink={false}>
|
||||
<div class="mt-4 ml-4 mb-4 flex-grow h-90">
|
||||
{#each integrations as gi}
|
||||
{@const giprj = githubProjects.filter((it) => it.integration === gi._id)}
|
||||
<div class="flex flex-col mb-4">
|
||||
<!-- svelte-ignore a11y-missing-attribute -->
|
||||
<GithubRepositories integration={gi} giProjects={giprj} {projects} />
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</Scroller>
|
||||
{/if}
|
||||
|
||||
<style lang="scss">
|
||||
.bordered {
|
||||
border: 1px dashed var(--theme-divider-color);
|
||||
}
|
||||
.repository-card {
|
||||
border: 1px solid var(--theme-divider-color);
|
||||
border-radius: 8px;
|
||||
margin: 0.25rem;
|
||||
padding: 1rem;
|
||||
// height: 7rem;
|
||||
}
|
||||
.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>
|
||||
@@ -0,0 +1,99 @@
|
||||
<script lang="ts">
|
||||
import { Ref, WithLookup } from '@hcengineering/core'
|
||||
import { getEmbeddedLabel } from '@hcengineering/platform'
|
||||
import { NavLink, createQuery, getClient, isAdminUser } from '@hcengineering/presentation'
|
||||
import { Issue } from '@hcengineering/tracker'
|
||||
import { ButtonKind, showPopup } from '@hcengineering/ui'
|
||||
import Button from '@hcengineering/ui/src/components/Button.svelte'
|
||||
import Icon from '@hcengineering/ui/src/components/Icon.svelte'
|
||||
import { DocSyncInfo, GithubIntegrationRepository, GithubProject } from '@hcengineering/github'
|
||||
import github from '../plugin'
|
||||
import MarkdownDescriptionDiff from './MarkdownDescriptionDiff.svelte'
|
||||
import RepositoryPresenterRefEditor from './RepositoryPresenterRefEditor.svelte'
|
||||
import { githubProjects, integrationRepositories } from './utils'
|
||||
|
||||
export let space: Ref<GithubProject>
|
||||
export let kind: ButtonKind = 'regular'
|
||||
export let readonly: boolean = false
|
||||
export let value: WithLookup<Issue>
|
||||
const client = getClient()
|
||||
|
||||
$: ghProject = $githubProjects.get(space)
|
||||
$: ghIssue = client.getHierarchy().asIf(value, github.mixin.GithubIssue)
|
||||
|
||||
$: repository = ghIssue?.repository !== undefined ? $integrationRepositories.get(ghIssue.repository) : undefined
|
||||
|
||||
async function assignRepository (repository: Ref<GithubIntegrationRepository>): Promise<void> {
|
||||
if (ghIssue !== undefined) {
|
||||
// We just need to assign the repository to the issue
|
||||
await getClient().update(ghIssue, { repository })
|
||||
} else {
|
||||
// We need to create a new issue
|
||||
await getClient().createMixin(value._id, value._class, value.space, github.mixin.GithubIssue, {
|
||||
repository,
|
||||
url: '',
|
||||
githubNumber: 0
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const syncInfoQuery = createQuery()
|
||||
let docSyncInfo: DocSyncInfo | undefined
|
||||
|
||||
$: if (isAdminUser()) {
|
||||
syncInfoQuery.query(github.class.DocSyncInfo, { _id: value._id as any }, (info) => {
|
||||
;[docSyncInfo] = info
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if ghIssue !== undefined || ghProject !== undefined}
|
||||
<div class="ml-2">
|
||||
{#if ghIssue?.repository == null}
|
||||
<RepositoryPresenterRefEditor
|
||||
label={github.string.CreateGithubIssue}
|
||||
kind={'regular'}
|
||||
showIcon={true}
|
||||
{space}
|
||||
disabled={readonly}
|
||||
onChange={(val) => {
|
||||
if (val !== undefined) {
|
||||
void assignRepository(val)
|
||||
}
|
||||
}}
|
||||
/>
|
||||
{:else if ghIssue.url !== ''}
|
||||
<div class="flex flex-row-center">
|
||||
{#if repository !== undefined}
|
||||
<Icon icon={github.icon.Github} size={'small'} />
|
||||
<span class="ml-1">
|
||||
{repository?.name}
|
||||
</span>
|
||||
{/if}
|
||||
<NavLink
|
||||
disabled={readonly}
|
||||
href={ghIssue.url}
|
||||
accent={true}
|
||||
noOverflow={true}
|
||||
onClick={() => {
|
||||
window.open(ghIssue.url, '_blank')
|
||||
}}
|
||||
>
|
||||
<span class="ml-2">
|
||||
#{ghIssue.githubNumber}
|
||||
</span>
|
||||
</NavLink>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{#if isAdminUser() && docSyncInfo?.markdown !== undefined}
|
||||
<div class="ml-2">
|
||||
<Button
|
||||
label={getEmbeddedLabel('Diff')}
|
||||
on:click={(evt) => {
|
||||
showPopup(MarkdownDescriptionDiff, { issue: docSyncInfo }, 'center')
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
@@ -0,0 +1,21 @@
|
||||
<script lang="ts">
|
||||
import { Ref } from '@hcengineering/core'
|
||||
import { Icon, Label } from '@hcengineering/ui'
|
||||
import { GithubIntegrationRepository } from '@hcengineering/github'
|
||||
import github from '../plugin'
|
||||
import { integrationRepositories } from './utils'
|
||||
|
||||
export let repository: Ref<GithubIntegrationRepository> | undefined = undefined
|
||||
|
||||
$: repositoryValue = repository != null ? $integrationRepositories.get(repository) : undefined
|
||||
</script>
|
||||
|
||||
<div class="flex-row-center">
|
||||
{#if repositoryValue}
|
||||
<Label label={github.string.IssueRepositoryTarget} />
|
||||
<div class="ml-2 mr-2">
|
||||
<Icon icon={github.icon.Github} size={'small'} />
|
||||
</div>
|
||||
{repositoryValue.name}
|
||||
{/if}
|
||||
</div>
|
||||
@@ -0,0 +1,203 @@
|
||||
<script lang="ts">
|
||||
import { DocumentUpdate, Ref } from '@hcengineering/core'
|
||||
import { IntlString, getEmbeddedLabel } from '@hcengineering/platform'
|
||||
import { getClient } from '@hcengineering/presentation'
|
||||
import tracker, { Project, ProjectTargetPreference } from '@hcengineering/tracker'
|
||||
import {
|
||||
Button,
|
||||
ButtonWithDropdown,
|
||||
Icon,
|
||||
IconDropdown,
|
||||
Label,
|
||||
SelectPopup,
|
||||
eventToHTMLElement,
|
||||
showPopup,
|
||||
type SelectPopupValueType
|
||||
} from '@hcengineering/ui'
|
||||
import { GithubIntegrationRepository } from '@hcengineering/github'
|
||||
import { Writable } from 'svelte/store'
|
||||
import github from '../plugin'
|
||||
import { integrationRepositories } from './utils'
|
||||
|
||||
export let state: Writable<Record<string, any>>
|
||||
export let okProcessing: boolean = false
|
||||
export let canSave: boolean = true
|
||||
export let okLabel: IntlString | undefined
|
||||
export let space: Project | undefined
|
||||
export let popupPlaceholder: IntlString = github.string.Repository
|
||||
export let handleOkClick: () => void
|
||||
export let preferences: ProjectTargetPreference[] = []
|
||||
|
||||
$: githubProject =
|
||||
space !== undefined && getClient().getHierarchy().hasMixin(space, github.mixin.GithubProject)
|
||||
? getClient().getHierarchy().as(space, github.mixin.GithubProject)
|
||||
: undefined
|
||||
|
||||
let repository: Ref<GithubIntegrationRepository> | undefined
|
||||
let selectedRepository: GithubIntegrationRepository | undefined
|
||||
|
||||
$: spacePreferences = preferences.find((it) => it.attachedTo === space?._id)
|
||||
$: if (spacePreferences !== undefined) {
|
||||
repository = spacePreferences.props?.find((it) => it.key === github.class.GithubIntegrationRepository)?.value
|
||||
}
|
||||
|
||||
$: rawRepositories = Array.from($integrationRepositories.values()).filter(
|
||||
(it) => it.githubProject === githubProject?._id
|
||||
)
|
||||
|
||||
const handleSelectedRepositoryIdUpdated = async (
|
||||
newRepositoryId: Ref<GithubIntegrationRepository> | null | undefined,
|
||||
components: GithubIntegrationRepository[]
|
||||
): Promise<void> => {
|
||||
if (newRepositoryId === null || newRepositoryId === undefined) {
|
||||
selectedRepository = undefined
|
||||
|
||||
return
|
||||
}
|
||||
selectedRepository = components.find((it) => it._id === newRepositoryId)
|
||||
}
|
||||
|
||||
function performOK (repository: Ref<GithubIntegrationRepository> | undefined): void {
|
||||
$state.repository = repository ?? undefined
|
||||
handleOkClick()
|
||||
}
|
||||
|
||||
$: void handleSelectedRepositoryIdUpdated(repository, rawRepositories)
|
||||
|
||||
function getRepositoryInfo (rawComponents: GithubIntegrationRepository[]): SelectPopupValueType[] {
|
||||
return [
|
||||
...rawComponents.map((p) => ({
|
||||
id: p._id,
|
||||
icon: github.icon.Github,
|
||||
label: getEmbeddedLabel(p.name),
|
||||
props: {
|
||||
value: p
|
||||
}
|
||||
})),
|
||||
{
|
||||
id: '#',
|
||||
label: github.string.WithoutRepository
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
let repositories: SelectPopupValueType[] = []
|
||||
$: repositories = getRepositoryInfo(rawRepositories)
|
||||
|
||||
function updateProjectPreferences (
|
||||
space: Ref<Project>,
|
||||
spacePreferences: ProjectTargetPreference | undefined,
|
||||
repository: Ref<GithubIntegrationRepository> | undefined
|
||||
): void {
|
||||
if (spacePreferences !== undefined) {
|
||||
const data: DocumentUpdate<ProjectTargetPreference> = {
|
||||
usedOn: Date.now()
|
||||
}
|
||||
const value = (spacePreferences.props ?? []).find((it) => it.key === github.class.GithubIntegrationRepository)
|
||||
if (value === undefined) {
|
||||
data.props = [
|
||||
...(spacePreferences.props ?? []),
|
||||
{ key: github.class.GithubIntegrationRepository, value: repository }
|
||||
]
|
||||
} else if (value.value !== repository) {
|
||||
// no value
|
||||
value.value = repository
|
||||
data.props = spacePreferences.props
|
||||
}
|
||||
void getClient().update(spacePreferences, data)
|
||||
} else {
|
||||
void getClient().createDoc(tracker.class.ProjectTargetPreference, space, {
|
||||
attachedTo: space,
|
||||
usedOn: Date.now(),
|
||||
props: [
|
||||
{
|
||||
key: github.class.GithubIntegrationRepository as string,
|
||||
value: repository
|
||||
}
|
||||
]
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const handleRepositoryEditorOpened = async (event: MouseEvent): Promise<void> => {
|
||||
event.stopPropagation()
|
||||
if (!canSave) {
|
||||
return
|
||||
}
|
||||
|
||||
if (repository != null) {
|
||||
performOK(repository)
|
||||
return
|
||||
}
|
||||
|
||||
showPopup(
|
||||
SelectPopup,
|
||||
{ value: repositories, placeholder: popupPlaceholder, searchable: false },
|
||||
eventToHTMLElement(event),
|
||||
(evt) => {
|
||||
if (evt !== undefined) {
|
||||
if (evt === '#') {
|
||||
performOK(undefined)
|
||||
} else {
|
||||
repository = evt
|
||||
if (repository != null && space !== undefined) {
|
||||
updateProjectPreferences(space._id, spacePreferences, repository)
|
||||
}
|
||||
performOK(repository)
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if githubProject !== undefined && rawRepositories.length > 0}
|
||||
<ButtonWithDropdown
|
||||
kind={'primary'}
|
||||
size={'large'}
|
||||
dropdownItems={repositories}
|
||||
disabled={!canSave}
|
||||
label={okLabel}
|
||||
dropdownIcon={IconDropdown}
|
||||
loading={okProcessing}
|
||||
on:click={handleRepositoryEditorOpened}
|
||||
on:dropdown-selected={(ev) => {
|
||||
if (ev.detail != null) {
|
||||
if (ev.detail === '#') {
|
||||
performOK(undefined)
|
||||
} else {
|
||||
repository = ev.detail
|
||||
if (repository != null && space !== undefined) {
|
||||
updateProjectPreferences(space._id, spacePreferences, repository)
|
||||
}
|
||||
}
|
||||
}
|
||||
}}
|
||||
>
|
||||
<svelte:fragment slot="content">
|
||||
<div class="ml-1">
|
||||
<Label label={github.string.RepositoryIn} />
|
||||
</div>
|
||||
{#if selectedRepository !== undefined}
|
||||
<div class="flex-row-center">
|
||||
<div class="p-1">
|
||||
<Icon icon={github.icon.Github} size={'small'} />
|
||||
</div>
|
||||
{selectedRepository.name}
|
||||
</div>
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
</ButtonWithDropdown>
|
||||
{:else}
|
||||
<Button
|
||||
loading={okProcessing}
|
||||
focusIndex={10001}
|
||||
disabled={!canSave}
|
||||
label={okLabel}
|
||||
kind={'primary'}
|
||||
size={'large'}
|
||||
on:click={() => {
|
||||
performOK(repository)
|
||||
}}
|
||||
/>
|
||||
{/if}
|
||||
@@ -0,0 +1,146 @@
|
||||
<script lang="ts">
|
||||
import contact from '@hcengineering/contact'
|
||||
import { NavLink } from '@hcengineering/presentation'
|
||||
import tracker from '@hcengineering/tracker'
|
||||
import { Icon } from '@hcengineering/ui'
|
||||
import { GithubAuthentication } from '@hcengineering/github'
|
||||
import github from '../plugin'
|
||||
|
||||
export let auth: GithubAuthentication
|
||||
</script>
|
||||
|
||||
<div class="flex flex-row">
|
||||
<div class="flex flex-col">
|
||||
<div class="">
|
||||
<img src={auth?.avatar} width="64" height={'64'} alt={auth.name ?? ''} />
|
||||
</div>
|
||||
{#if auth?.name}
|
||||
<div class="p1 fs-title text-lg">
|
||||
{auth.name}
|
||||
</div>
|
||||
{/if}
|
||||
<div class="p1 text-base">
|
||||
<NavLink href={auth?.url ?? ''}>{auth.login}</NavLink>
|
||||
</div>
|
||||
{#if auth}
|
||||
<div class="flex-row-center mt-4 text-sm no-word-wrap">
|
||||
<Icon icon={contact.icon.Person} size={'small'} />
|
||||
<span class="ml-1">
|
||||
followers <b>{auth.followers}</b>
|
||||
</span>
|
||||
{#if auth.following}
|
||||
<div class="ml-1">
|
||||
⋅ following <b>{auth.following}</b>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="flex-row-center mt-4 text-sm no-word-wrap">
|
||||
<Icon icon={github.icon.GithubRepository} size={'small'} />
|
||||
<span class="ml-1">
|
||||
repositories <b>{auth.repositories}</b>
|
||||
</span>
|
||||
|
||||
<div class="ml-1">
|
||||
⋅ starred <b>{auth.starredRepositories}</b>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex-row-center mt-4 text-sm no-word-wrap">
|
||||
<Icon icon={tracker.icon.Issue} size={'small'} />
|
||||
<span class="ml-1">
|
||||
open <b>{auth.openIssues}</b>
|
||||
</span>
|
||||
<div class="ml-1">
|
||||
⋅ closed <b>{auth.closedIssues}</b>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-row-center mt-4 text-sm no-word-wrap">
|
||||
<Icon icon={github.icon.PullRequest} size={'small'} />
|
||||
<span class="ml-1">
|
||||
open <b>{auth.openPRs}</b>
|
||||
</span>
|
||||
<div class="ml-1">
|
||||
⋅ merged <b>{auth.mergedPRs}</b>
|
||||
</div>
|
||||
<!-- <div class="ml-1">
|
||||
⋅ closed <b>{auth.closedPRs}</b>
|
||||
</div> -->
|
||||
</div>
|
||||
|
||||
{#if auth.bio}
|
||||
<div class="p1 mt-4 no-word-wrap infoCard">
|
||||
bio <b class="ml-1">{auth.bio}</b>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if auth.blog}
|
||||
<div class="flex-row-center mt-4 text-sm no-word-wrap infoCard">
|
||||
blog <b class="ml-1">{auth.blog}</b>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if auth.company}
|
||||
<div class="flex-row-center mt-4 text-sm no-word-wrap infoCard">
|
||||
company <b class="ml-1">{auth.company}</b>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if auth.email}
|
||||
<div class="flex-row-center mt-4 text-sm infoCard">
|
||||
email <b class="ml-1">{auth.email}</b>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if auth.location}
|
||||
<div class="flex-row-center mt-4 text-sm infoCard">
|
||||
location <b class="ml-1">{auth.location}</b>
|
||||
</div>
|
||||
{/if}
|
||||
<div class="flex-row-center mt-4 text-sm">
|
||||
<Icon icon={github.icon.Github} size={'small'} />
|
||||
<span class="ml-1">
|
||||
organizations <b class="ml-1">{auth.organizations?.totalCount ?? 0}</b>
|
||||
</span>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="flex flex-col ml-4 flex-grow">
|
||||
<div class="flex-grow" style:overflow={'auto'}>
|
||||
{#each auth?.organizations?.nodes ?? [] as organization}
|
||||
<div class="org-card mt-2 flex-grow">
|
||||
<div class="flex flex-row-center">
|
||||
{#if organization.avatarUrl}
|
||||
<div class="mr-2">
|
||||
<img src={organization.avatarUrl} width="32" height={'32'} alt={organization.name} />
|
||||
</div>
|
||||
{/if}
|
||||
<div class="flex-row-center flex-no-wrap">
|
||||
<NavLink href={organization.url}>{organization.name}</NavLink>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-2">
|
||||
{organization.description ?? ''}
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
{#if auth.error}
|
||||
{auth.error}
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.bordered {
|
||||
border: 1px dashed var(--theme-divider-color);
|
||||
}
|
||||
.org-card {
|
||||
border: 1px solid var(--theme-divider-color);
|
||||
border-radius: 8px;
|
||||
padding: 1rem;
|
||||
}
|
||||
.infoCard {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,317 @@
|
||||
<script lang="ts">
|
||||
import { AttachedDoc, Ref, WithLookup } from '@hcengineering/core'
|
||||
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 { GithubIntegration, GithubIntegrationRepository, GithubProject } from '@hcengineering/github'
|
||||
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
|
||||
},
|
||||
undefined,
|
||||
async (res) => {
|
||||
if (res === true) {
|
||||
void 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
|
||||
},
|
||||
undefined,
|
||||
async (res) => {
|
||||
if (res !== null) {
|
||||
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>
|
||||
@@ -0,0 +1,61 @@
|
||||
<script lang="ts">
|
||||
import { getEmbeddedLabel } from '@hcengineering/platform'
|
||||
import { Card, getClient } from '@hcengineering/presentation'
|
||||
import { DocSyncInfo } from '@hcengineering/github'
|
||||
|
||||
export let issue: DocSyncInfo
|
||||
function allowEdit (): void {
|
||||
void getClient().update(issue, {
|
||||
isDescriptionLocked: false,
|
||||
needSync: ''
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<Card
|
||||
okLabel={getEmbeddedLabel('Unlock')}
|
||||
label={getEmbeddedLabel('Description merge conflict')}
|
||||
on:close
|
||||
on:changeContent
|
||||
okAction={allowEdit}
|
||||
fullSize={true}
|
||||
canSave={true}
|
||||
>
|
||||
<div class="flex flex-row flex-grow">
|
||||
<div class="flex-row" style={'width: 50%'} style:overflow={'auto'}>
|
||||
Github Markdown value:
|
||||
<div class="proseCodeBlock select-text flex-row" style={'text-wrap: wrap;'}>
|
||||
{#each (issue?.external.body ?? '').split('\n') as line, i}
|
||||
<div class="flex">
|
||||
<div class="line">
|
||||
{i + 1}
|
||||
</div>
|
||||
{line}
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-row" style={'width: 50%'} style:overflow={'auto'}>
|
||||
Platform markdown value:
|
||||
<div class="proseCodeBlock select-text flex-col" style={'text-wrap: wrap;'}>
|
||||
{#each (issue?.markdown ?? '').split('\n') as line, i}
|
||||
<div class="flex">
|
||||
<div class="line">
|
||||
{i + 1}
|
||||
</div>
|
||||
{line}
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
<style lang="scss">
|
||||
.line {
|
||||
border-right: 1px solid #ddd;
|
||||
padding: 0 0.5em;
|
||||
margin-right: 0.5em;
|
||||
color: #888;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,141 @@
|
||||
<!--
|
||||
// Copyright © 2023 Hardcore Engineering Inc.
|
||||
//
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { PersonAccount } from '@hcengineering/contact'
|
||||
import { getCurrentAccount } from '@hcengineering/core'
|
||||
import { createQuery, getClient, getFileUrl } from '@hcengineering/presentation'
|
||||
import { Button, Chevron, Component, ExpandCollapse, Label } from '@hcengineering/ui'
|
||||
import diffview from '@hcengineering/diffview'
|
||||
import { GithubPatch, GithubPullRequest, GithubPullRequestReview } from '@hcengineering/github'
|
||||
|
||||
import github from '../plugin'
|
||||
|
||||
export let pullRequest: GithubPullRequest
|
||||
|
||||
const me = getCurrentAccount() as PersonAccount
|
||||
|
||||
let isCollapsed = true
|
||||
|
||||
let patch: GithubPatch | undefined
|
||||
|
||||
const patchQuery = createQuery()
|
||||
|
||||
$: patchQuery.query(github.class.GithubPatch, { attachedTo: pullRequest._id }, (res) => {
|
||||
;[patch] = res
|
||||
})
|
||||
|
||||
let patchText: string = ''
|
||||
|
||||
$: if (patch !== undefined) {
|
||||
void fetch(getFileUrl(patch.file, patch.name))
|
||||
.then((data) => data.text())
|
||||
.then((text) => {
|
||||
patchText = text
|
||||
})
|
||||
}
|
||||
|
||||
$: hasPatch = patch !== undefined && patchText !== ''
|
||||
|
||||
let review: GithubPullRequestReview | undefined
|
||||
|
||||
const reviewQuery = createQuery()
|
||||
|
||||
$: reviewQuery.query(
|
||||
github.class.GithubPullRequestReview,
|
||||
{
|
||||
attachedTo: pullRequest._id,
|
||||
author: me.person
|
||||
},
|
||||
(res) => {
|
||||
;[review] = res
|
||||
}
|
||||
)
|
||||
|
||||
$: viewedFiles = review?.files ?? []
|
||||
|
||||
const client = getClient()
|
||||
|
||||
async function handleFileViewed (fileName: string, sha: string, viewed: boolean): Promise<void> {
|
||||
const current = await client.findOne(github.class.GithubPullRequestReview, {
|
||||
attachedTo: pullRequest._id,
|
||||
author: me.person
|
||||
})
|
||||
|
||||
const files = current?.files ?? []
|
||||
|
||||
const index = files.findIndex((file) => file.fileName === fileName && file.sha === sha)
|
||||
if (index !== -1) {
|
||||
files.splice(index, 1)
|
||||
}
|
||||
if (viewed) {
|
||||
files.push({ fileName, sha })
|
||||
}
|
||||
|
||||
if (current) {
|
||||
await client.update(current, { files })
|
||||
} else {
|
||||
await client.addCollection(
|
||||
github.class.GithubPullRequestReview,
|
||||
pullRequest.space,
|
||||
pullRequest._id,
|
||||
github.class.GithubPullRequest,
|
||||
'reviewsVisual',
|
||||
{ author: me.person, files }
|
||||
)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="mt-6">
|
||||
{#if hasPatch}
|
||||
<div class="flex-between mb-1">
|
||||
<Button
|
||||
width="min-content"
|
||||
kind="ghost"
|
||||
on:click={() => {
|
||||
isCollapsed = !isCollapsed
|
||||
}}
|
||||
>
|
||||
<svelte:fragment slot="content">
|
||||
<Chevron
|
||||
size={'small'}
|
||||
expanded={!isCollapsed}
|
||||
outline
|
||||
fill={'var(--caption-color)'}
|
||||
marginRight={'.375rem'}
|
||||
/>
|
||||
<Label label={github.string.ChangedFiles} params={{ files: pullRequest.files }} />
|
||||
</svelte:fragment>
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{#if !isCollapsed}
|
||||
<ExpandCollapse isExpanded={!isCollapsed}>
|
||||
<div class="list" class:collapsed={isCollapsed}>
|
||||
<Component
|
||||
is={diffview.component.DiffView}
|
||||
props={{ patch: patchText, viewed: viewedFiles }}
|
||||
on:change={(evt) => {
|
||||
const { fileName, sha, viewed } = evt.detail
|
||||
handleFileViewed(fileName, sha, viewed)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</ExpandCollapse>
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.list {
|
||||
padding-top: 0.75rem;
|
||||
border-top: 1px solid var(--divider-color);
|
||||
|
||||
&.collapsed {
|
||||
padding-top: 1px;
|
||||
border-top: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,44 @@
|
||||
<script lang="ts">
|
||||
import { getClient } from '@hcengineering/presentation'
|
||||
import { Issue } from '@hcengineering/tracker'
|
||||
import { ButtonSize, Icon, Label } from '@hcengineering/ui'
|
||||
import { GithubPullRequest, GithubPullRequestState } from '@hcengineering/github'
|
||||
import github from '../plugin'
|
||||
import PullRequestReviewDecisionValuePresenter from './presenters/PullRequestReviewDecisionValuePresenter.svelte'
|
||||
|
||||
export let value: Issue
|
||||
export let size: ButtonSize = 'medium'
|
||||
export let small = false
|
||||
|
||||
$: pr = getClient().getHierarchy().isDerived(value?._class, github.class.GithubPullRequest)
|
||||
? (value as GithubPullRequest)
|
||||
: undefined
|
||||
</script>
|
||||
|
||||
{#if pr}
|
||||
{#if pr.state === GithubPullRequestState.open}
|
||||
<div class="ml-4">
|
||||
<PullRequestReviewDecisionValuePresenter value={pr.reviewDecision} {small} />
|
||||
</div>
|
||||
|
||||
{#if pr.mergeable === 'CONFLICTING' && !small}
|
||||
<div class="ml-4">
|
||||
<Label label={github.string.Conflict} />
|
||||
</div>
|
||||
{/if}
|
||||
{:else if pr.state === GithubPullRequestState.merged}
|
||||
<div class:ml-4={!small} class="flex-row-center">
|
||||
<Icon icon={github.icon.PullRequestMerged} size={'small'} />
|
||||
{#if !small}
|
||||
<Label label={github.string.PRMerged} />
|
||||
{/if}
|
||||
</div>
|
||||
{:else if pr.state === GithubPullRequestState.closed}
|
||||
<div class:ml-4={!small} class="flex-row-center">
|
||||
<Icon icon={github.icon.PullRequestClosed} size={'small'} />
|
||||
{#if !small}
|
||||
<Label label={github.string.PRClosed} />
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
@@ -0,0 +1,84 @@
|
||||
<!--
|
||||
// Copyright © 2023 Hardcore Engineering Inc.
|
||||
//
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { DocumentQuery, Ref } from '@hcengineering/core'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
|
||||
import { IntlString } from '@hcengineering/platform'
|
||||
import { createQuery } from '@hcengineering/presentation'
|
||||
import tracker, { Issue, Project } from '@hcengineering/tracker'
|
||||
import { IModeSelector, resolvedLocationStore } from '@hcengineering/ui'
|
||||
|
||||
import task from '@hcengineering/task'
|
||||
import { GithubProject, GithubPullRequest } from '@hcengineering/github'
|
||||
import PullRequestsView from './PullRequestsView.svelte'
|
||||
|
||||
export let currentSpace: Ref<GithubProject> | undefined = undefined
|
||||
export let baseQuery: DocumentQuery<GithubPullRequest> = {}
|
||||
export let title: IntlString
|
||||
export let config: [string, IntlString, object][]
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
let query: DocumentQuery<GithubPullRequest> | undefined = undefined
|
||||
let modeSelectorProps: IModeSelector | undefined = undefined
|
||||
|
||||
const archivedProjectQuery = createQuery()
|
||||
let archived: Ref<Project>[] = []
|
||||
|
||||
archivedProjectQuery.query(
|
||||
tracker.class.Project,
|
||||
{ archived: true },
|
||||
(res) => {
|
||||
archived = res.map((it) => it._id)
|
||||
},
|
||||
{ projection: { _id: 1 } }
|
||||
)
|
||||
|
||||
$: spaceQuery = currentSpace ? { space: currentSpace } : { space: { $nin: archived as Ref<GithubProject>[] } }
|
||||
|
||||
const activeStatusQuery = createQuery()
|
||||
let active: DocumentQuery<Issue>
|
||||
|
||||
$: all = { ...spaceQuery }
|
||||
$: activeStatusQuery.query(
|
||||
tracker.class.IssueStatus,
|
||||
{
|
||||
category: { $nin: [task.statusCategory.Won, task.statusCategory.Lost] }
|
||||
},
|
||||
(result) => {
|
||||
active = { status: { $in: result.map(({ _id }) => _id) }, ...spaceQuery }
|
||||
}
|
||||
)
|
||||
|
||||
const closedStatusQuery = createQuery()
|
||||
let closed: DocumentQuery<GithubPullRequest> = {}
|
||||
$: closedStatusQuery.query(
|
||||
tracker.class.IssueStatus,
|
||||
{ category: { $in: [task.statusCategory.Won, task.statusCategory.Lost] } },
|
||||
(result) => {
|
||||
closed = { status: { $in: result.map(({ _id }) => _id) }, ...spaceQuery }
|
||||
}
|
||||
)
|
||||
|
||||
$: queries = { all, active, closed }
|
||||
$: mode = $resolvedLocationStore.query?.mode ?? undefined
|
||||
$: if (mode === undefined || (queries as any)[mode] === undefined) {
|
||||
;[[mode]] = config
|
||||
}
|
||||
$: if (mode !== undefined) {
|
||||
query = { ...((queries as any)[mode] ?? {}) }
|
||||
modeSelectorProps = {
|
||||
config,
|
||||
mode,
|
||||
onChange: (newMode: string) => dispatch('action', { mode: newMode })
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if query !== undefined && modeSelectorProps !== undefined}
|
||||
{#key query && currentSpace}
|
||||
<PullRequestsView {query} space={currentSpace} {title} {modeSelectorProps} />
|
||||
{/key}
|
||||
{/if}
|
||||
@@ -0,0 +1,95 @@
|
||||
<script lang="ts">
|
||||
import { DocumentQuery, Ref, Space, WithLookup } from '@hcengineering/core'
|
||||
import { IntlString, translate } from '@hcengineering/platform'
|
||||
import { Button, IModeSelector, IconDetails, IconDetailsFilled, themeStore } from '@hcengineering/ui'
|
||||
import { ViewOptions, Viewlet } from '@hcengineering/view'
|
||||
import { FilterBar, SpaceHeader, ViewletContentView, ViewletSettingButton } from '@hcengineering/view-resources'
|
||||
import { GithubPullRequest } from '@hcengineering/github'
|
||||
import github from '../plugin'
|
||||
|
||||
export let space: Ref<Space> | undefined = undefined
|
||||
export let query: DocumentQuery<GithubPullRequest> = {}
|
||||
export let title: IntlString | undefined = undefined
|
||||
export let label: string = ''
|
||||
export let panelWidth: number = 0
|
||||
export let modeSelectorProps: IModeSelector | undefined = undefined
|
||||
|
||||
let viewlet: WithLookup<Viewlet> | undefined = undefined
|
||||
const viewlets: WithLookup<Viewlet>[] = []
|
||||
let viewOptions: ViewOptions | undefined
|
||||
let search = ''
|
||||
let searchQuery: DocumentQuery<GithubPullRequest> = { ...query }
|
||||
function updateSearchQuery (search: string): void {
|
||||
searchQuery = search === '' ? { ...query } : { ...query, $search: search }
|
||||
}
|
||||
$: if (query) updateSearchQuery(search)
|
||||
let resultQuery: DocumentQuery<GithubPullRequest> = { ...searchQuery }
|
||||
|
||||
$: if (!label && title) {
|
||||
void translate(title, {}, $themeStore.language).then((res) => {
|
||||
label = res
|
||||
})
|
||||
}
|
||||
|
||||
let asideFloat: boolean = false
|
||||
let asideShown: boolean = true
|
||||
$: if (panelWidth < 900 && !asideFloat) asideFloat = true
|
||||
$: if (panelWidth >= 900 && asideFloat) {
|
||||
asideFloat = false
|
||||
asideShown = false
|
||||
}
|
||||
let docWidth: number
|
||||
let docSize: boolean = false
|
||||
$: if (docWidth <= 900 && !docSize) docSize = true
|
||||
$: if (docWidth > 900 && docSize) docSize = false
|
||||
</script>
|
||||
|
||||
<SpaceHeader
|
||||
bind:viewlet
|
||||
bind:search
|
||||
_class={github.class.GithubPullRequest}
|
||||
showLabelSelector={$$slots.label_selector}
|
||||
{viewlets}
|
||||
{label}
|
||||
{space}
|
||||
{modeSelectorProps}
|
||||
>
|
||||
<svelte:fragment slot="label_selector">
|
||||
<slot name="label_selector" />
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="extra">
|
||||
<ViewletSettingButton bind:viewOptions bind:viewlet />
|
||||
{#if asideFloat && $$slots.aside}
|
||||
<div class="buttons-divider" />
|
||||
<Button
|
||||
icon={asideShown ? IconDetailsFilled : IconDetails}
|
||||
kind={'ghost'}
|
||||
size={'medium'}
|
||||
selected={asideShown}
|
||||
on:click={() => {
|
||||
asideShown = !asideShown
|
||||
}}
|
||||
/>
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
</SpaceHeader>
|
||||
{#if viewlet && viewOptions}
|
||||
<FilterBar
|
||||
_class={github.class.GithubPullRequest}
|
||||
query={searchQuery}
|
||||
{space}
|
||||
{viewOptions}
|
||||
on:change={(e) => (resultQuery = e.detail)}
|
||||
/>
|
||||
<slot name="afterHeader" />
|
||||
<div class="popupPanel rowContent">
|
||||
{#if viewlet}
|
||||
<ViewletContentView _class={github.class.GithubPullRequest} {viewlet} query={resultQuery} {space} {viewOptions} />
|
||||
{/if}
|
||||
{#if $$slots.aside !== undefined && asideShown}
|
||||
<div class="popupPanel-body__aside" class:shown={asideShown}>
|
||||
<slot name="aside" />
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
@@ -0,0 +1,60 @@
|
||||
<!--
|
||||
// Copyright © 2022 Hardcore Engineering Inc.
|
||||
//
|
||||
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License. You may
|
||||
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { WithLookup } from '@hcengineering/core'
|
||||
import { translate } from '@hcengineering/platform'
|
||||
import { Icon, themeStore } from '@hcengineering/ui'
|
||||
import { GithubIntegrationRepository } from '@hcengineering/github'
|
||||
import github from '../plugin'
|
||||
|
||||
export let value: WithLookup<GithubIntegrationRepository> | undefined
|
||||
export let shouldShowAvatar = true
|
||||
export let onClick: (() => void) | undefined = undefined
|
||||
export let disabled: boolean = false
|
||||
export let inline: boolean = false
|
||||
export let accent: boolean = false
|
||||
export let noUnderline = false
|
||||
export let kind: 'list' | undefined = undefined
|
||||
|
||||
let label: string
|
||||
|
||||
$: if (value !== undefined) {
|
||||
label = value.name
|
||||
} else {
|
||||
translate(github.string.NoRepository, {}, $themeStore.language)
|
||||
.then((r) => {
|
||||
label = r
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err)
|
||||
})
|
||||
}
|
||||
$: disabled = disabled || value === undefined
|
||||
</script>
|
||||
|
||||
<div class="flex-row-center">
|
||||
<span class="flex-presenter flex-row-center" class:list={kind === 'list'}>
|
||||
<div class="flex-row-center">
|
||||
{#if shouldShowAvatar}
|
||||
<div class="icon ml-2">
|
||||
<Icon icon={github.icon.Github} size={'small'} />
|
||||
</div>
|
||||
{/if}
|
||||
<span title={label} class="nowrap" class:no-underline={disabled || noUnderline} class:fs-bold={accent}>
|
||||
{label}
|
||||
</span>
|
||||
</div>
|
||||
</span>
|
||||
</div>
|
||||
@@ -0,0 +1,33 @@
|
||||
<!--
|
||||
//
|
||||
// Copyright © 2023 Hardcore Engineering Inc.
|
||||
//
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { Ref } from '@hcengineering/core'
|
||||
import { getEmbeddedLabel } from '@hcengineering/platform'
|
||||
import { ButtonKind, ButtonSize } from '@hcengineering/ui'
|
||||
import { HyperlinkEditor } from '@hcengineering/view-resources'
|
||||
import github, { GithubIntegrationRepository } from '@hcengineering/github'
|
||||
import { integrationRepositories } from './utils'
|
||||
|
||||
export let value: Ref<GithubIntegrationRepository>
|
||||
export let kind: ButtonKind | undefined = undefined
|
||||
export let size: ButtonSize = 'small'
|
||||
export let justify: 'left' | 'center' = 'center'
|
||||
export let width: string | undefined = 'fit-content'
|
||||
|
||||
$: repository = $integrationRepositories.get(value)
|
||||
</script>
|
||||
|
||||
<HyperlinkEditor
|
||||
value={repository?.repository?.html_url}
|
||||
placeholder={getEmbeddedLabel(repository?.name ?? '')}
|
||||
title={repository?.name ?? ''}
|
||||
readonly
|
||||
icon={github.icon.Github}
|
||||
{kind}
|
||||
{size}
|
||||
{justify}
|
||||
{width}
|
||||
/>
|
||||
@@ -0,0 +1,128 @@
|
||||
<!--
|
||||
//
|
||||
// Copyright © 2023 Hardcore Engineering Inc.
|
||||
//
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { Ref } from '@hcengineering/core'
|
||||
import { IntlString, getEmbeddedLabel } from '@hcengineering/platform'
|
||||
import {
|
||||
Button,
|
||||
ButtonKind,
|
||||
ButtonSize,
|
||||
Icon,
|
||||
IconChevronDown,
|
||||
LabelAndProps,
|
||||
SelectPopup,
|
||||
SelectPopupValueType,
|
||||
eventToHTMLElement,
|
||||
showPopup
|
||||
} from '@hcengineering/ui'
|
||||
import { HyperlinkEditor } from '@hcengineering/view-resources'
|
||||
import { GithubIntegrationRepository, GithubProject } from '@hcengineering/github'
|
||||
import github from '../plugin'
|
||||
import { integrationRepositories } from './utils'
|
||||
|
||||
export let value: Ref<GithubIntegrationRepository> | undefined = undefined
|
||||
export let space: Ref<GithubProject>
|
||||
export let kind: ButtonKind | undefined = undefined
|
||||
export let size: ButtonSize = 'small'
|
||||
export let justify: 'left' | 'center' = 'center'
|
||||
export let width: string | undefined = 'fit-content'
|
||||
export let onChange: (value: Ref<GithubIntegrationRepository> | undefined) => void
|
||||
export let disabled: boolean
|
||||
export let popupPlaceholder: IntlString = github.string.Repository
|
||||
export let focusIndex: number | undefined = undefined
|
||||
export let showTooltip: LabelAndProps | undefined = undefined
|
||||
export let label: IntlString = github.string.AssignRepository
|
||||
export let showIcon: boolean = false
|
||||
|
||||
$: repository = $integrationRepositories.get(value)
|
||||
|
||||
let selectedRepository: GithubIntegrationRepository | undefined
|
||||
|
||||
$: rawComponents = Array.from($integrationRepositories.values()).filter((it) => it.githubProject === space)
|
||||
|
||||
const handleSelectedRepositoryIdUpdated = async (
|
||||
newRepositoryId: Ref<GithubIntegrationRepository> | null | undefined,
|
||||
components: GithubIntegrationRepository[]
|
||||
): Promise<void> => {
|
||||
if (newRepositoryId === null || newRepositoryId === undefined) {
|
||||
selectedRepository = undefined
|
||||
|
||||
return
|
||||
}
|
||||
selectedRepository = components.find((it) => it._id === newRepositoryId)
|
||||
}
|
||||
|
||||
$: void handleSelectedRepositoryIdUpdated(value, rawComponents)
|
||||
|
||||
function getRepositoryInfo (
|
||||
rawComponents: GithubIntegrationRepository[],
|
||||
sp: GithubIntegrationRepository | undefined
|
||||
): SelectPopupValueType[] {
|
||||
return [
|
||||
...rawComponents.map((p) => ({
|
||||
id: p._id,
|
||||
icon: github.icon.Github,
|
||||
label: getEmbeddedLabel(p.name),
|
||||
props: {
|
||||
value: p
|
||||
}
|
||||
}))
|
||||
]
|
||||
}
|
||||
|
||||
let components: SelectPopupValueType[] = []
|
||||
$: components = getRepositoryInfo(rawComponents, selectedRepository)
|
||||
|
||||
const handleRepositoryEditorOpened = async (event: MouseEvent): Promise<void> => {
|
||||
event.stopPropagation()
|
||||
if (disabled) {
|
||||
return
|
||||
}
|
||||
|
||||
showPopup(
|
||||
SelectPopup,
|
||||
{ value: components, placeholder: popupPlaceholder, searchable: false },
|
||||
eventToHTMLElement(event),
|
||||
onChange
|
||||
)
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if value == null}
|
||||
<Button
|
||||
{kind}
|
||||
{justify}
|
||||
{size}
|
||||
{focusIndex}
|
||||
{showTooltip}
|
||||
{disabled}
|
||||
{label}
|
||||
icon={showIcon ? github.icon.Github : undefined}
|
||||
iconRight={IconChevronDown}
|
||||
on:click={handleRepositoryEditorOpened}
|
||||
>
|
||||
<svelte:fragment slot="content">
|
||||
{#if selectedRepository !== undefined}
|
||||
<div class="p-1">
|
||||
<Icon icon={github.icon.Github} size={'small'} />
|
||||
</div>
|
||||
{selectedRepository.name}
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
</Button>
|
||||
{:else}
|
||||
<HyperlinkEditor
|
||||
value={repository?.htmlURL ?? ''}
|
||||
placeholder={getEmbeddedLabel(repository?.name ?? '')}
|
||||
title={repository?.name ?? ''}
|
||||
readonly
|
||||
icon={github.icon.Github}
|
||||
{kind}
|
||||
{size}
|
||||
{justify}
|
||||
{width}
|
||||
/>
|
||||
{/if}
|
||||
@@ -0,0 +1,111 @@
|
||||
<!--
|
||||
// Copyright © 2024 Hardcore Engineering Inc.
|
||||
//
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { Ref } from '@hcengineering/core'
|
||||
import { IntlString, getEmbeddedLabel } from '@hcengineering/platform'
|
||||
import type { ButtonKind, ButtonShape, ButtonSize, LabelAndProps, SelectPopupValueType } from '@hcengineering/ui'
|
||||
import { ButtonWithDropdown, Icon, IconDropdown, SelectPopup, eventToHTMLElement, showPopup } from '@hcengineering/ui'
|
||||
import { GithubIntegrationRepository, GithubProject } from '@hcengineering/github'
|
||||
import github from '../plugin'
|
||||
import { integrationRepositories } from './utils'
|
||||
|
||||
export let value: Ref<GithubIntegrationRepository> | null | undefined
|
||||
export let githubProject: Ref<GithubProject>
|
||||
|
||||
// export let shouldShowLabel: boolean = true
|
||||
export let isEditable: boolean = true
|
||||
export let onChange: ((newRepositoryId: Ref<GithubIntegrationRepository> | undefined) => void) | undefined = undefined
|
||||
export let popupPlaceholder: IntlString = github.string.Repository
|
||||
|
||||
export let kind: ButtonKind = 'no-border'
|
||||
export let size: ButtonSize = 'small'
|
||||
export let justify: 'left' | 'center' = 'center'
|
||||
|
||||
export let focusIndex: number | undefined = undefined
|
||||
|
||||
export let showTooltip: LabelAndProps | undefined = undefined
|
||||
|
||||
let selectedRepository: GithubIntegrationRepository | undefined
|
||||
|
||||
$: rawComponents = Array.from($integrationRepositories.values()).filter((it) => it.githubProject === githubProject)
|
||||
|
||||
const handleSelectedRepositoryIdUpdated = async (
|
||||
newRepositoryId: Ref<GithubIntegrationRepository> | null | undefined,
|
||||
components: GithubIntegrationRepository[]
|
||||
): Promise<void> => {
|
||||
if (newRepositoryId === null || newRepositoryId === undefined) {
|
||||
selectedRepository = undefined
|
||||
|
||||
return
|
||||
}
|
||||
selectedRepository = components.find((it) => it._id === newRepositoryId)
|
||||
}
|
||||
|
||||
$: void handleSelectedRepositoryIdUpdated(value, rawComponents)
|
||||
|
||||
function getRepositoryInfo (
|
||||
rawComponents: GithubIntegrationRepository[],
|
||||
sp: GithubIntegrationRepository | undefined
|
||||
): SelectPopupValueType[] {
|
||||
return [
|
||||
...rawComponents.map((p) => ({
|
||||
id: p._id,
|
||||
icon: github.icon.Github,
|
||||
label: getEmbeddedLabel(p.name),
|
||||
props: {
|
||||
value: p
|
||||
}
|
||||
})),
|
||||
{
|
||||
id: null,
|
||||
label: github.string.WithoutRepository
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
let components: SelectPopupValueType[] = []
|
||||
$: components = getRepositoryInfo(rawComponents, selectedRepository)
|
||||
|
||||
const handleRepositoryEditorOpened = async (event: MouseEvent): Promise<void> => {
|
||||
event.stopPropagation()
|
||||
if (!isEditable) {
|
||||
return
|
||||
}
|
||||
|
||||
showPopup(
|
||||
SelectPopup,
|
||||
{ value: components, placeholder: popupPlaceholder, searchable: false },
|
||||
eventToHTMLElement(event),
|
||||
onChange
|
||||
)
|
||||
}
|
||||
</script>
|
||||
|
||||
<ButtonWithDropdown
|
||||
{kind}
|
||||
{justify}
|
||||
{size}
|
||||
{focusIndex}
|
||||
showTooltipMain={showTooltip}
|
||||
dropdownItems={components}
|
||||
disabled={!isEditable}
|
||||
label={github.string.NoRepository}
|
||||
dropdownIcon={IconDropdown}
|
||||
on:click={handleRepositoryEditorOpened}
|
||||
on:dropdown-selected={(ev) => {
|
||||
if (ev.detail != null) {
|
||||
value = ev.detail
|
||||
}
|
||||
}}
|
||||
>
|
||||
<svelte:fragment slot="content">
|
||||
{#if selectedRepository !== undefined}
|
||||
<div class="p-1">
|
||||
<Icon icon={github.icon.Github} size={'small'} />
|
||||
</div>
|
||||
{selectedRepository.name}
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
</ButtonWithDropdown>
|
||||
@@ -0,0 +1,200 @@
|
||||
export const githubLanguageColors: Record<string, string> = {
|
||||
ABAP: '#E8274B',
|
||||
ActionScript: '#882B0F',
|
||||
Ada: '#02f88c',
|
||||
Agda: '#315665',
|
||||
'AGS Script': '#B9D9FF',
|
||||
Alloy: '#64C800',
|
||||
AMPL: '#E6EFBB',
|
||||
ANTLR: '#9DC3FF',
|
||||
'API Blueprint': '#2ACCA8',
|
||||
APL: '#5A8164',
|
||||
Arc: '#aa2afe',
|
||||
Arduino: '#bd79d1',
|
||||
ASP: '#6a40fd',
|
||||
AspectJ: '#a957b0',
|
||||
Assembly: '#6E4C13',
|
||||
ATS: '#1ac620',
|
||||
AutoHotkey: '#6594b9',
|
||||
AutoIt: '#1C3552',
|
||||
BlitzMax: '#cd6400',
|
||||
Boo: '#d4bec1',
|
||||
Brainfuck: '#2F2530',
|
||||
'C Sharp': '#178600',
|
||||
C: '#555555',
|
||||
Chapel: '#8dc63f',
|
||||
Cirru: '#ccccff',
|
||||
Clarion: '#db901e',
|
||||
Clean: '#3F85AF',
|
||||
Click: '#E4E6F3',
|
||||
Clojure: '#db5855',
|
||||
CoffeeScript: '#244776',
|
||||
'ColdFusion CFC': '#ed2cd6',
|
||||
ColdFusion: '#ed2cd6',
|
||||
'Common Lisp': '#3fb68b',
|
||||
'Component Pascal': '#b0ce4e',
|
||||
cpp: '#f34b7d',
|
||||
Crystal: '#776791',
|
||||
CSS: '#563d7c',
|
||||
D: '#ba595e',
|
||||
Dart: '#00B4AB',
|
||||
Diff: '#88dddd',
|
||||
DM: '#447265',
|
||||
Dogescript: '#cca760',
|
||||
Dylan: '#6c616e',
|
||||
E: '#ccce35',
|
||||
Eagle: '#814C05',
|
||||
eC: '#913960',
|
||||
ECL: '#8a1267',
|
||||
edn: '#db5855',
|
||||
Eiffel: '#946d57',
|
||||
Elixir: '#6e4a7e',
|
||||
Elm: '#60B5CC',
|
||||
'Emacs Lisp': '#c065db',
|
||||
EmberScript: '#FFF4F3',
|
||||
Erlang: '#B83998',
|
||||
'F#': '#b845fc',
|
||||
Factor: '#636746',
|
||||
Fancy: '#7b9db4',
|
||||
Fantom: '#dbded5',
|
||||
FLUX: '#88ccff',
|
||||
Forth: '#341708',
|
||||
FORTRAN: '#4d41b1',
|
||||
FreeMarker: '#0050b2',
|
||||
Frege: '#00cafe',
|
||||
'Game Maker Language': '#8fb200',
|
||||
Glyph: '#e4cc98',
|
||||
Gnuplot: '#f0a9f0',
|
||||
Go: '#375eab',
|
||||
Golo: '#88562A',
|
||||
Gosu: '#82937f',
|
||||
'Grammatical Framework': '#79aa7a',
|
||||
Groovy: '#e69f56',
|
||||
Handlebars: '#01a9d6',
|
||||
Harbour: '#0e60e3',
|
||||
Haskell: '#29b544',
|
||||
Haxe: '#df7900',
|
||||
HTML: '#e44b23',
|
||||
Hy: '#7790B2',
|
||||
IDL: '#a3522f',
|
||||
Io: '#a9188d',
|
||||
Ioke: '#078193',
|
||||
Isabelle: '#FEFE00',
|
||||
J: '#9EEDFF',
|
||||
Java: '#b07219',
|
||||
JavaScript: '#f1e05a',
|
||||
JFlex: '#DBCA00',
|
||||
JSONiq: '#40d47e',
|
||||
Julia: '#a270ba',
|
||||
'Jupyter Notebook': '#DA5B0B',
|
||||
Kotlin: '#F18E33',
|
||||
KRL: '#28431f',
|
||||
Lasso: '#999999',
|
||||
Latte: '#A8FF97',
|
||||
Lex: '#DBCA00',
|
||||
LFE: '#004200',
|
||||
LiveScript: '#499886',
|
||||
LOLCODE: '#cc9900',
|
||||
LookML: '#652B81',
|
||||
LSL: '#3d9970',
|
||||
Lua: '#000080',
|
||||
Makefile: '#427819',
|
||||
Mask: '#f97732',
|
||||
Matlab: '#bb92ac',
|
||||
Max: '#c4a79c',
|
||||
MAXScript: '#00a6a6',
|
||||
Mercury: '#ff2b2b',
|
||||
Metal: '#8f14e9',
|
||||
Mirah: '#c7a938',
|
||||
MTML: '#b7e1f4',
|
||||
NCL: '#28431f',
|
||||
Nemerle: '#3d3c6e',
|
||||
nesC: '#94B0C7',
|
||||
NetLinx: '#0aa0ff',
|
||||
'NetLinx+ERB': '#747faa',
|
||||
NetLogo: '#ff6375',
|
||||
NewLisp: '#87AED7',
|
||||
Nimrod: '#37775b',
|
||||
Nit: '#009917',
|
||||
Nix: '#7e7eff',
|
||||
Nu: '#c9df40',
|
||||
'Objective-C': '#438eff',
|
||||
'Objective-C++': '#6866fb',
|
||||
'Objective-J': '#ff0c5a',
|
||||
OCaml: '#3be133',
|
||||
Omgrofl: '#cabbff',
|
||||
ooc: '#b0b77e',
|
||||
Opal: '#f7ede0',
|
||||
Oxygene: '#cdd0e3',
|
||||
Oz: '#fab738',
|
||||
Pan: '#cc0000',
|
||||
Papyrus: '#6600cc',
|
||||
Parrot: '#f3ca0a',
|
||||
Pascal: '#b0ce4e',
|
||||
PAWN: '#dbb284',
|
||||
Perl: '#0298c3',
|
||||
Perl6: '#0000fb',
|
||||
PHP: '#4F5D95',
|
||||
PigLatin: '#fcd7de',
|
||||
Pike: '#005390',
|
||||
PLSQL: '#dad8d8',
|
||||
PogoScript: '#d80074',
|
||||
Processing: '#0096D8',
|
||||
Prolog: '#74283c',
|
||||
'Propeller Spin': '#7fa2a7',
|
||||
Puppet: '#302B6D',
|
||||
'Pure Data': '#91de79',
|
||||
PureBasic: '#5a6986',
|
||||
PureScript: '#1D222D',
|
||||
Python: '#3572A5',
|
||||
QML: '#44a51c',
|
||||
R: '#198ce7',
|
||||
Racket: '#22228f',
|
||||
'Ragel in Ruby Host': '#9d5200',
|
||||
RAML: '#77d9fb',
|
||||
Rebol: '#358a5b',
|
||||
Red: '#ee0000',
|
||||
"Ren'Py": '#ff7f7f',
|
||||
Rouge: '#cc0088',
|
||||
Ruby: '#701516',
|
||||
Rust: '#dea584',
|
||||
SaltStack: '#646464',
|
||||
SAS: '#B34936',
|
||||
Scala: '#DC322F',
|
||||
Scheme: '#1e4aec',
|
||||
Self: '#0579aa',
|
||||
Shell: '#89e051',
|
||||
Shen: '#120F14',
|
||||
Slash: '#007eff',
|
||||
Slim: '#ff8f77',
|
||||
Smalltalk: '#596706',
|
||||
SourcePawn: '#5c7611',
|
||||
SQF: '#3F3F3F',
|
||||
Squirrel: '#800000',
|
||||
Stan: '#b2011d',
|
||||
'Standard ML': '#dc566d',
|
||||
SuperCollider: '#46390b',
|
||||
Swift: '#ffac45',
|
||||
SystemVerilog: '#DAE1C2',
|
||||
Tcl: '#e4cc98',
|
||||
TeX: '#3D6117',
|
||||
Turing: '#45f715',
|
||||
TypeScript: '#2b7489',
|
||||
'Unified Parallel C': '#4e3617',
|
||||
'Unity3D Asset': '#ab69a1',
|
||||
UnrealScript: '#a54c4d',
|
||||
Vala: '#fbe5cd',
|
||||
Verilog: '#b2b7f8',
|
||||
VHDL: '#adb2cb',
|
||||
VimL: '#199f4b',
|
||||
'Visual Basic': '#945db7',
|
||||
Volt: '#1F1F1F',
|
||||
Vue: '#2c3e50',
|
||||
'Web Ontology Language': '#9cc9dd',
|
||||
wisp: '#7582D1',
|
||||
X10: '#4B6BEF',
|
||||
xBase: '#403a40',
|
||||
XC: '#99DA07',
|
||||
XQuery: '#5232e7',
|
||||
Zephir: '#118f9e'
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
<script lang="ts">
|
||||
import { Issue } from '@hcengineering/tracker'
|
||||
|
||||
import { getClient } from '@hcengineering/presentation'
|
||||
import { HyperlinkEditor } from '@hcengineering/view-resources'
|
||||
import github from '../../plugin'
|
||||
import { integrationRepositories } from '../utils'
|
||||
|
||||
export let value: Issue
|
||||
|
||||
$: ghIssue = getClient().getHierarchy().asIf(value, github.mixin.GithubIssue)
|
||||
|
||||
$: repository = ghIssue?.repository !== undefined ? $integrationRepositories.get(ghIssue?.repository) : undefined
|
||||
</script>
|
||||
|
||||
{#if ghIssue !== undefined && ghIssue.url !== '' && repository !== undefined}
|
||||
<div class="flex flex-row-center">
|
||||
<HyperlinkEditor
|
||||
readonly
|
||||
icon={github.icon.Github}
|
||||
kind={'ghost'}
|
||||
value={ghIssue.url}
|
||||
placeholder={github.string.Issue}
|
||||
title={`${repository.name}`}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
+112
@@ -0,0 +1,112 @@
|
||||
<!--
|
||||
// Copyright © 2023 Hardcore Engineering Inc.
|
||||
//
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { Ref, WithLookup, groupByArray } from '@hcengineering/core'
|
||||
import {
|
||||
GithubPullRequestReviewState,
|
||||
GithubReview,
|
||||
GithubReviewComment,
|
||||
GithubReviewThread
|
||||
} from '@hcengineering/github'
|
||||
|
||||
import { ActivityMessageHeader, ActivityMessageTemplate } from '@hcengineering/activity-resources'
|
||||
import { Person, PersonAccount } from '@hcengineering/contact'
|
||||
import { personAccountByIdStore, personByIdStore } from '@hcengineering/contact-resources'
|
||||
import { IntlString } from '@hcengineering/platform'
|
||||
import { MessageViewer, createQuery } from '@hcengineering/presentation'
|
||||
import { Component, PaletteColorIndexes, getPlatformColor, themeStore } from '@hcengineering/ui'
|
||||
import diffview from '@hcengineering/diffview'
|
||||
import github from '../../plugin'
|
||||
import ReviewCommentPresenter from './ReviewCommentPresenter.svelte'
|
||||
import { isEmptyMarkup } from '@hcengineering/text'
|
||||
|
||||
export let value: WithLookup<GithubReview>
|
||||
export let showNotify: boolean = false
|
||||
export let isHighlighted: boolean = false
|
||||
export let isSelected: boolean = false
|
||||
export let shouldScroll: boolean = false
|
||||
export let embedded: boolean = false
|
||||
export let onClick: (() => void) | undefined = undefined
|
||||
|
||||
$: personAccount = $personAccountByIdStore.get((value?.createdBy ?? value?.modifiedBy) as Ref<PersonAccount>)
|
||||
|
||||
$: person = $personByIdStore.get(personAccount?.person as Ref<Person>)
|
||||
|
||||
function getCommentFromState (value?: GithubPullRequestReviewState): {
|
||||
label: IntlString
|
||||
color?: number
|
||||
} {
|
||||
switch (value) {
|
||||
case GithubPullRequestReviewState.Approved:
|
||||
return { label: github.string.ReviewApproved, color: PaletteColorIndexes.Grass }
|
||||
case GithubPullRequestReviewState.ChangesRequested:
|
||||
return { label: github.string.ReviewChangesRequested, color: PaletteColorIndexes.Sunshine }
|
||||
case GithubPullRequestReviewState.Commented:
|
||||
return { label: github.string.ReviewCommented }
|
||||
case GithubPullRequestReviewState.Dismissed:
|
||||
return { label: github.string.ReviewDismissed, color: PaletteColorIndexes.Coin }
|
||||
case GithubPullRequestReviewState.Pending:
|
||||
default:
|
||||
return { label: github.string.ReviewPending }
|
||||
}
|
||||
}
|
||||
|
||||
$: presentationState = getCommentFromState(value?.state)
|
||||
</script>
|
||||
|
||||
{#if value?.state !== GithubPullRequestReviewState.Commented || (value?.state === GithubPullRequestReviewState.Commented && (value?.body?.length ?? 0) > 0 && !isEmptyMarkup(value?.body ?? ''))}
|
||||
<div
|
||||
class:review={presentationState.color !== undefined}
|
||||
style:border-color={presentationState.color !== undefined
|
||||
? getPlatformColor(presentationState.color, $themeStore.dark)
|
||||
: undefined}
|
||||
>
|
||||
<ActivityMessageTemplate
|
||||
message={value}
|
||||
parentMessage={undefined}
|
||||
{person}
|
||||
{showNotify}
|
||||
{isHighlighted}
|
||||
{isSelected}
|
||||
{shouldScroll}
|
||||
{embedded}
|
||||
viewlet={undefined}
|
||||
{onClick}
|
||||
>
|
||||
<svelte:fragment slot="header">
|
||||
<ActivityMessageHeader
|
||||
message={value}
|
||||
{person}
|
||||
object={undefined}
|
||||
parentObject={undefined}
|
||||
isEdited={false}
|
||||
label={presentationState.label}
|
||||
/>
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="content">
|
||||
<div class="flex-row-center">
|
||||
<div class="customContent">
|
||||
<MessageViewer message={value.body} />
|
||||
</div>
|
||||
</div>
|
||||
</svelte:fragment>
|
||||
</ActivityMessageTemplate>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<style lang="scss">
|
||||
.review {
|
||||
border: 1px solid;
|
||||
border-radius: 0.5rem;
|
||||
margin-top: 0.25rem;
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
.customContent {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
column-gap: 0.625rem;
|
||||
row-gap: 0.625rem;
|
||||
}
|
||||
</style>
|
||||
+190
@@ -0,0 +1,190 @@
|
||||
<!--
|
||||
// Copyright © 2023 Hardcore Engineering Inc.
|
||||
//
|
||||
-->
|
||||
<script lang="ts">
|
||||
import core, { Account, Ref, WithLookup, getCurrentAccount } from '@hcengineering/core'
|
||||
import { GithubPullRequest, GithubReviewComment, GithubReviewThread } from '@hcengineering/github'
|
||||
|
||||
import { ActivityMessageHeader, ActivityMessageTemplate } from '@hcengineering/activity-resources'
|
||||
import { Person, PersonAccount } from '@hcengineering/contact'
|
||||
import { EmployeePresenter, personAccountByIdStore, personByIdStore } from '@hcengineering/contact-resources'
|
||||
import { getEmbeddedLabel } from '@hcengineering/platform'
|
||||
import { createQuery, getClient } from '@hcengineering/presentation'
|
||||
import { ReferenceInput } from '@hcengineering/text-editor-resources'
|
||||
import { Button, Component, Label, PaletteColorIndexes, getPlatformColor, themeStore } from '@hcengineering/ui'
|
||||
import diffview from '@hcengineering/diffview'
|
||||
import github from '../../plugin'
|
||||
import ReviewCommentPresenter from './ReviewCommentPresenter.svelte'
|
||||
import { githubConfiguration } from '../../configuration'
|
||||
|
||||
export let value: WithLookup<GithubReviewThread>
|
||||
export let showNotify: boolean = false
|
||||
export let isHighlighted: boolean = false
|
||||
export let isSelected: boolean = false
|
||||
export let shouldScroll: boolean = false
|
||||
export let embedded: boolean = false
|
||||
export let onClick: (() => void) | undefined = undefined
|
||||
|
||||
$: personAccount = $personAccountByIdStore.get((value?.createdBy ?? value?.modifiedBy) as Ref<PersonAccount>)
|
||||
$: person = $personByIdStore.get(personAccount?.person as Ref<Person>)
|
||||
|
||||
const commentsQuery = createQuery()
|
||||
|
||||
let comments: GithubReviewComment[] = []
|
||||
|
||||
$: commentsQuery.query(
|
||||
github.class.GithubReviewComment,
|
||||
{ attachedTo: value.attachedTo as Ref<GithubPullRequest>, reviewThreadId: value.threadId },
|
||||
(res) => {
|
||||
comments = res
|
||||
}
|
||||
)
|
||||
let expanded = !value.isResolved
|
||||
|
||||
async function onMessage (event: CustomEvent<string>): Promise<void> {
|
||||
await getClient().addCollection(
|
||||
github.class.GithubReviewComment,
|
||||
value.space,
|
||||
value.attachedTo,
|
||||
value.attachedToClass,
|
||||
'reviewComments',
|
||||
{
|
||||
body: event.detail,
|
||||
diffHunk: '',
|
||||
includesCreatedEdit: false,
|
||||
isMinimized: false,
|
||||
line: 0,
|
||||
minimizedReason: null,
|
||||
originalLine: 0,
|
||||
originalStartLine: 0,
|
||||
outdated: false,
|
||||
path: value.path,
|
||||
reviewThreadId: value.threadId,
|
||||
reviewUrl: '',
|
||||
startLine: 0,
|
||||
url: ''
|
||||
}
|
||||
)
|
||||
}
|
||||
async function changeResolution (): Promise<void> {
|
||||
await getClient().update(value, { isResolved: !value.isResolved, resolvedBy: null })
|
||||
}
|
||||
|
||||
const toRefPersonAccount = (account: Ref<Account>): Ref<PersonAccount> => account as Ref<PersonAccount>
|
||||
const toRefPerson = (account?: Ref<Person>): Ref<Person> => account as Ref<Person>
|
||||
</script>
|
||||
|
||||
<div
|
||||
class:reviewUnresolved={!value.isResolved}
|
||||
style:border-color={!value.isResolved ? getPlatformColor(PaletteColorIndexes.Orange, $themeStore.dark) : undefined}
|
||||
>
|
||||
<ActivityMessageTemplate
|
||||
message={value}
|
||||
parentMessage={undefined}
|
||||
{person}
|
||||
{showNotify}
|
||||
{isHighlighted}
|
||||
{isSelected}
|
||||
{shouldScroll}
|
||||
{embedded}
|
||||
viewlet={undefined}
|
||||
{onClick}
|
||||
>
|
||||
<svelte:fragment slot="header">
|
||||
<ActivityMessageHeader
|
||||
message={value}
|
||||
{person}
|
||||
object={undefined}
|
||||
parentObject={undefined}
|
||||
isEdited={false}
|
||||
label={getEmbeddedLabel('reviewed')}
|
||||
/>
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="content">
|
||||
<div class="file-content">
|
||||
{#if comments.length > 0}
|
||||
<Component
|
||||
is={diffview.component.InlineDiffView}
|
||||
props={{
|
||||
patch: comments?.[0]?.diffHunk ?? '',
|
||||
fileName: value.path,
|
||||
expandable: value.isResolved,
|
||||
expanded,
|
||||
onExpand: (value) => {
|
||||
expanded = value
|
||||
}
|
||||
}}
|
||||
/>
|
||||
{/if}
|
||||
{#if expanded}
|
||||
<div class="ml-4">
|
||||
{#each comments as comment}
|
||||
<ReviewCommentPresenter {comment} />
|
||||
{/each}
|
||||
</div>
|
||||
<ReferenceInput showSend={true} showHeader showActions on:message={onMessage} />
|
||||
<div class="p-2 flex-row-center flex-grow">
|
||||
{#if githubConfiguration.ResolveThreadSupported}
|
||||
<Button
|
||||
label={value.isResolved
|
||||
? getEmbeddedLabel('Unresolve conversation')
|
||||
: getEmbeddedLabel('Resolve conversation')}
|
||||
on:click={changeResolution}
|
||||
/>
|
||||
{/if}
|
||||
{#if value.isResolved && value.resolvedBy != null}
|
||||
{@const resolveAccount = $personAccountByIdStore.get(toRefPersonAccount(value.resolvedBy))}
|
||||
{@const resolvePerson = $personByIdStore.get(toRefPerson(resolveAccount?.person))}
|
||||
{#if resolvePerson !== undefined}
|
||||
<div class="flex-row-center ml-4">
|
||||
<Label label={getEmbeddedLabel('resolved by')} />
|
||||
|
||||
<div class="content ml-2 clear-mins">
|
||||
<div class="header clear-mins">
|
||||
{#if resolvePerson}
|
||||
<EmployeePresenter value={resolvePerson} shouldShowAvatar={true} />
|
||||
{:else}
|
||||
<div class="strong">
|
||||
<Label label={core.string.System} />
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</svelte:fragment>
|
||||
</ActivityMessageTemplate>
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.reviewUnresolved {
|
||||
border: 1px solid;
|
||||
border-radius: 0.5rem;
|
||||
margin-top: 0.25rem;
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
.customContent {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
column-gap: 0.625rem;
|
||||
row-gap: 0.625rem;
|
||||
}
|
||||
.file-content {
|
||||
border: 1px solid var(--theme-divider-color);
|
||||
border-top: 0;
|
||||
border-bottom-left-radius: 0.25rem;
|
||||
border-bottom-right-radius: 0.25rem;
|
||||
overflow-y: hidden;
|
||||
}
|
||||
.file-info {
|
||||
border-top: 1px solid var(--theme-divider-color);
|
||||
font-weight: 600;
|
||||
direction: rtl;
|
||||
text-align: left;
|
||||
}
|
||||
</style>
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
<script lang="ts">
|
||||
import { Label } from '@hcengineering/ui'
|
||||
import { PullRequestMergeable } from '@hcengineering/github'
|
||||
import github from '../../plugin'
|
||||
|
||||
export let value: PullRequestMergeable
|
||||
export let accent = false
|
||||
</script>
|
||||
|
||||
<div class="p-1" class:fs-bold={accent}>
|
||||
{#if value === PullRequestMergeable.CONFLICTING}
|
||||
<Label label={github.string.Conflict} />
|
||||
{:else if value === PullRequestMergeable.MERGEABLE}
|
||||
<Label label={github.string.ReadyForMerge} />
|
||||
{/if}
|
||||
</div>
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
<!--
|
||||
// Copyright © 2023 Hardcore Engineering Inc.
|
||||
//
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { createQuery, getClient } from '@hcengineering/presentation'
|
||||
|
||||
import { Issue } from '@hcengineering/tracker'
|
||||
import { GithubIssue, GithubProject, GithubPullRequest } from '@hcengineering/github'
|
||||
import github from '../../plugin'
|
||||
|
||||
export let value: GithubPullRequest
|
||||
|
||||
const client = getClient()
|
||||
const spaceQuery = createQuery()
|
||||
let currentProject: GithubProject | undefined = undefined
|
||||
|
||||
$: spaceQuery.query(github.mixin.GithubProject, { _id: value.space }, (res) => {
|
||||
;[currentProject] = res
|
||||
})
|
||||
|
||||
$: ghIssue = client.getHierarchy().hasMixin(value, github.mixin.GithubIssue)
|
||||
? client.getHierarchy().as<Issue, GithubIssue>(value, github.mixin.GithubIssue)
|
||||
: undefined
|
||||
</script>
|
||||
|
||||
{#if value}
|
||||
<div class="flex-col">
|
||||
<div class="flex-row-center crop-presenter">
|
||||
<span class="font-medium mr-2 whitespace-nowrap clear-mins">{value.identifier}</span>
|
||||
<span class="overflow-label">
|
||||
{currentProject?.name}
|
||||
</span>
|
||||
</div>
|
||||
<span class="overflow-label mt-10px">
|
||||
{value.title}
|
||||
{#if ghIssue}
|
||||
# {ghIssue.githubNumber}
|
||||
{/if}
|
||||
</span>
|
||||
</div>
|
||||
{/if}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
<!--
|
||||
//
|
||||
// Copyright © 2023 Hardcore Engineering Inc.
|
||||
//
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { WithLookup } from '@hcengineering/core'
|
||||
import { IssuePresenter } from '@hcengineering/tracker-resources'
|
||||
import github, { GithubPullRequest } from '@hcengineering/github'
|
||||
|
||||
export let value: WithLookup<GithubPullRequest>
|
||||
export let disabled = false
|
||||
export let onClick: (() => void) | undefined = undefined
|
||||
export let shouldShowAvatar: boolean = false
|
||||
export let noUnderline = false
|
||||
export let inline = false
|
||||
export let kind: 'list' | undefined = undefined
|
||||
</script>
|
||||
|
||||
<IssuePresenter
|
||||
{value}
|
||||
{disabled}
|
||||
{onClick}
|
||||
{shouldShowAvatar}
|
||||
{noUnderline}
|
||||
{inline}
|
||||
{kind}
|
||||
icon={github.icon.PullRequest}
|
||||
/>
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
<script lang="ts">
|
||||
import { Asset, IntlString } from '@hcengineering/platform'
|
||||
import { AnySvelteComponent, Icon, Label, PaletteColorIndexes, getPlatformColor, themeStore } from '@hcengineering/ui'
|
||||
import { GithubReviewDecisionState } from '@hcengineering/github'
|
||||
import { ComponentType } from 'svelte'
|
||||
import github from '../../plugin'
|
||||
|
||||
export let value: GithubReviewDecisionState
|
||||
export let small = false
|
||||
|
||||
const labels: Record<
|
||||
GithubReviewDecisionState,
|
||||
{ label: IntlString, color: number, icon: Asset | AnySvelteComponent | ComponentType }
|
||||
> = {
|
||||
[GithubReviewDecisionState.Approved]: {
|
||||
icon: github.icon.PullRequest,
|
||||
label: github.string.ReviewApproved,
|
||||
color: PaletteColorIndexes.Grass
|
||||
},
|
||||
[GithubReviewDecisionState.ChangesRequested]: {
|
||||
icon: github.icon.PullRequest,
|
||||
label: github.string.ReviewChangesRequested,
|
||||
color: PaletteColorIndexes.Firework
|
||||
},
|
||||
[GithubReviewDecisionState.ReviewRequired]: {
|
||||
icon: github.icon.PullRequest,
|
||||
label: github.string.ReviewPending,
|
||||
color: PaletteColorIndexes.Blueberry
|
||||
}
|
||||
}
|
||||
|
||||
$: label = labels[value]
|
||||
</script>
|
||||
|
||||
<div
|
||||
class={!small ? 'p-1 border-radius-1' : ''}
|
||||
style:background-color={!small ? getPlatformColor(label.color, $themeStore.dark) : undefined}
|
||||
style:color={'white'}
|
||||
>
|
||||
<div class="flex-row-center no-word-wrap">
|
||||
<Icon
|
||||
icon={label.icon}
|
||||
size={'small'}
|
||||
fill={small ? getPlatformColor(label.color, $themeStore.dark) : 'currentColor'}
|
||||
/>
|
||||
{#if !small}
|
||||
<div class="ml-1">
|
||||
<Label label={label.label} />
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
<script lang="ts">
|
||||
import { Label } from '@hcengineering/ui'
|
||||
import { GithubPullRequestState } from '@hcengineering/github'
|
||||
import github from '../../plugin'
|
||||
|
||||
export let value: GithubPullRequestState
|
||||
export let accent = false
|
||||
</script>
|
||||
|
||||
<div class="p-1" class:fs-bold={accent}>
|
||||
{#if value === GithubPullRequestState.open}
|
||||
<Label label={github.string.PROpen} />
|
||||
{:else if value === GithubPullRequestState.merged}
|
||||
<Label label={github.string.PRMerged} />
|
||||
{:else if value === GithubPullRequestState.closed}
|
||||
<Label label={github.string.PRClosed} />
|
||||
{/if}
|
||||
</div>
|
||||
@@ -0,0 +1,17 @@
|
||||
<!--
|
||||
//
|
||||
// Copyright © 2023 Hardcore Engineering Inc.
|
||||
//
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { Icon } from '@hcengineering/ui'
|
||||
import { GithubIntegrationRepository } from '@hcengineering/github'
|
||||
import github from '../../plugin'
|
||||
|
||||
export let value: GithubIntegrationRepository
|
||||
</script>
|
||||
|
||||
<div class="flex-row-center">
|
||||
<Icon icon={github.icon.GithubRepository} size={'small'} />
|
||||
<span class="ml-1">{value.name}</span>
|
||||
</div>
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
<script lang="ts">
|
||||
import { Person, PersonAccount } from '@hcengineering/contact'
|
||||
import {
|
||||
EmployeePresenter,
|
||||
SystemAvatar,
|
||||
personAccountByIdStore,
|
||||
personByIdStore
|
||||
} from '@hcengineering/contact-resources'
|
||||
import Avatar from '@hcengineering/contact-resources/src/components/Avatar.svelte'
|
||||
import core, { Ref, getDisplayTime } from '@hcengineering/core'
|
||||
import { MessageViewer } from '@hcengineering/presentation'
|
||||
import { Label } from '@hcengineering/ui'
|
||||
import { GithubReviewComment } from '@hcengineering/github'
|
||||
|
||||
export let comment: GithubReviewComment
|
||||
|
||||
$: personAccount = $personAccountByIdStore.get((comment?.createdBy ?? comment?.modifiedBy) as Ref<PersonAccount>)
|
||||
$: person = $personByIdStore.get(personAccount?.person as Ref<Person>)
|
||||
</script>
|
||||
|
||||
{#if comment}
|
||||
<div>
|
||||
<div class="flex-row-center">
|
||||
<div class="min-w-6 mt-1">
|
||||
{#if $$slots.icon}
|
||||
<slot name="icon" />
|
||||
{:else if person}
|
||||
<Avatar size="tiny" {person} name={person.name} />
|
||||
{:else}
|
||||
<SystemAvatar size="tiny" />
|
||||
{/if}
|
||||
</div>
|
||||
<div class="header clear-mins flex-row-center">
|
||||
{#if person}
|
||||
<EmployeePresenter value={person} shouldShowAvatar={false} />
|
||||
{:else}
|
||||
<div class="strong">
|
||||
<Label label={core.string.System} />
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<span class="text-sm ml-2">{getDisplayTime(comment.createdOn ?? 0)}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="customContent p-2">
|
||||
<MessageViewer message={comment.body} />
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<style lang="scss">
|
||||
.customContent {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
column-gap: 0.625rem;
|
||||
row-gap: 0.625rem;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,21 @@
|
||||
<!--
|
||||
//
|
||||
// Copyright © 2023 Hardcore Engineering Inc.
|
||||
//
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { WithLookup } from '@hcengineering/core'
|
||||
import { TitlePresenter } from '@hcengineering/tracker-resources'
|
||||
import { GithubPullRequest } from '@hcengineering/github'
|
||||
|
||||
export let value: WithLookup<GithubPullRequest>
|
||||
export let shouldUseMargin: boolean = false
|
||||
export let showParent = true
|
||||
export let kind: 'list' | undefined = undefined
|
||||
export let onClick: (() => void) | undefined = undefined
|
||||
export let disabled = false
|
||||
</script>
|
||||
|
||||
{#if value}
|
||||
<TitlePresenter {value} {shouldUseMargin} {showParent} {kind} {onClick} {disabled} />
|
||||
{/if}
|
||||
@@ -0,0 +1,111 @@
|
||||
import { Analytics } from '@hcengineering/analytics'
|
||||
import core, { concatLink, getCurrentAccount, toIdMap, type IdMap } from '@hcengineering/core'
|
||||
import { PlatformError, getMetadata, unknownError } from '@hcengineering/platform'
|
||||
import presentation, { createQuery, getClient } from '@hcengineering/presentation'
|
||||
import { location } from '@hcengineering/ui'
|
||||
import {
|
||||
makeQuery,
|
||||
type GithubAuthentication,
|
||||
type GithubIntegrationRepository,
|
||||
type GithubProject
|
||||
} from '@hcengineering/github'
|
||||
import { get, writable } from 'svelte/store'
|
||||
import github from '../plugin'
|
||||
|
||||
export async function onAuthorize (login?: string): Promise<void> {
|
||||
const meId = getCurrentAccount()._id
|
||||
const state = btoa(
|
||||
JSON.stringify({
|
||||
accountId: meId,
|
||||
workspace: get(location).path[1],
|
||||
token: getMetadata(presentation.metadata.Token),
|
||||
op: 'authorize'
|
||||
})
|
||||
)
|
||||
const client = getClient()
|
||||
|
||||
const config = await client.findOne(github.class.GithubAuthentication, {})
|
||||
|
||||
if (config !== undefined) {
|
||||
await client.remove(config)
|
||||
}
|
||||
await client.createDoc<GithubAuthentication>(github.class.GithubAuthentication, core.space.Workspace, {
|
||||
attachedTo: meId,
|
||||
login: '',
|
||||
error: null,
|
||||
authRequestTime: Date.now(),
|
||||
createdAt: new Date(),
|
||||
followers: 0,
|
||||
following: 0,
|
||||
nodeId: '',
|
||||
updatedAt: new Date(),
|
||||
url: '',
|
||||
repositories: 0,
|
||||
organizations: { totalCount: 0, nodes: [] },
|
||||
closedIssues: 0,
|
||||
openIssues: 0,
|
||||
mergedPRs: 0,
|
||||
openPRs: 0,
|
||||
closedPRs: 0,
|
||||
repositoryDiscussions: 0,
|
||||
starredRepositories: 0
|
||||
})
|
||||
|
||||
Analytics.handleEvent('Authorize github clicked')
|
||||
|
||||
const url =
|
||||
'https://github.com/login/oauth/authorize?' +
|
||||
makeQuery({
|
||||
client_id: getMetadata(github.metadata.GithubClientID),
|
||||
login: '',
|
||||
state,
|
||||
allow_signup: 'true'
|
||||
})
|
||||
window.open(url)
|
||||
}
|
||||
|
||||
const repositoryQuery = createQuery(true)
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export const integrationRepositories = writable<IdMap<GithubIntegrationRepository>>(new Map())
|
||||
|
||||
repositoryQuery.query(github.class.GithubIntegrationRepository, {}, (res) => {
|
||||
integrationRepositories.set(toIdMap(res))
|
||||
})
|
||||
|
||||
const projectQuery = createQuery(true)
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export const githubProjects = writable<IdMap<GithubProject>>(new Map())
|
||||
|
||||
projectQuery.query(github.mixin.GithubProject, {}, (res) => {
|
||||
githubProjects.set(toIdMap(res))
|
||||
})
|
||||
|
||||
const authQuery = createQuery(true)
|
||||
export const githubAuth = writable<GithubAuthentication | undefined>(undefined)
|
||||
authQuery.query(github.class.GithubAuthentication, {}, (res) => {
|
||||
githubAuth.set(res.shift())
|
||||
})
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export async function sendGHServiceRequest (path: string, args: Record<string, any>): Promise<any> {
|
||||
const githubURL = getMetadata(github.metadata.GithubURL)
|
||||
if (githubURL === undefined) {
|
||||
// We could try use recognition service to find some document properties.
|
||||
throw new PlatformError(unknownError('Github integration is not configured'))
|
||||
}
|
||||
return await fetch(concatLink(concatLink(githubURL, '/api/v1/'), path), {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(args)
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user