Merge branch 'main' of github.com:specklesystems/speckle-server into alessandro/web-943-add-stream-permissions-revoked-activity

This commit is contained in:
Alessandro Magionami
2024-10-08 09:52:01 +02:00
30 changed files with 770 additions and 509 deletions
@@ -26,7 +26,9 @@
class="absolute z-40 lg:static h-full flex w-[17rem] shrink-0 transition-all"
:class="isOpenMobile ? '' : '-translate-x-[17rem] lg:translate-x-0'"
>
<LayoutSidebar class="border-r border-outline-3 px-2 py-3 bg-foundation-page">
<LayoutSidebar
class="border-r border-outline-3 px-2 pt-3 pb-2 bg-foundation-page"
>
<LayoutSidebarMenu>
<LayoutSidebarMenuGroup>
<NuxtLink :to="homeRoute" @click="isOpenMobile = false">
@@ -123,17 +125,13 @@
</LayoutSidebarMenuGroupItem>
</NuxtLink>
<NuxtLink
to="https://docs.google.com/forms/d/e/1FAIpQLSeTOU8i0KwpgBG7ONimsh4YMqvLKZfSRhWEOz4W0MyjQ1lfAQ/viewform"
target="_blank"
@click="isOpenMobile = false"
>
<LayoutSidebarMenuGroupItem label="Give us feedback" external>
<div @click="openFeedbackDialog">
<LayoutSidebarMenuGroupItem label="Give us feedback">
<template #icon>
<IconFeedback class="size-4 text-foreground-2" />
</template>
</LayoutSidebarMenuGroupItem>
</NuxtLink>
</div>
<NuxtLink
to="https://speckle.guide/"
@@ -160,10 +158,20 @@
</NuxtLink>
</LayoutSidebarMenuGroup>
</LayoutSidebarMenu>
<template #promo>
<LayoutSidebarPromo
title="SpeckleCon 2024"
text="Join us in London on Nov 13-14 for the ultimate community event."
button-text="Get tickets"
@on-click="onPromoClick"
/>
</template>
</LayoutSidebar>
</div>
</template>
<FeedbackDialog v-model:open="showFeedbackDialog" />
<WorkspaceCreateDialog
v-model:open="showWorkspaceCreateDialog"
navigate-on-success
@@ -175,6 +183,7 @@
import {
FormButton,
LayoutSidebar,
LayoutSidebarPromo,
LayoutSidebarMenu,
LayoutSidebarMenuGroup,
LayoutSidebarMenuGroupItem
@@ -202,6 +211,7 @@ const mixpanel = useMixpanel()
const isOpenMobile = ref(false)
const showWorkspaceCreateDialog = ref(false)
const showFeedbackDialog = ref(false)
const { result: workspaceResult, onResult: onWorkspaceResult } = useQuery(
settingsSidebarQuery,
@@ -249,4 +259,18 @@ onWorkspaceResult((result) => {
}
}
})
const onPromoClick = () => {
mixpanel.track('Promo Banner Clicked', {
source: 'sidebar',
campaign: 'specklecon2024'
})
window.open('https://conf.speckle.systems/', '_blank')
}
const openFeedbackDialog = () => {
showFeedbackDialog.value = true
isOpenMobile.value = false
}
</script>
@@ -0,0 +1,80 @@
<template>
<LayoutDialog
v-model:open="isOpen"
title="Give us feedback"
:buttons="dialogButtons"
:on-submit="onSubmit"
max-width="md"
>
<div class="flex flex-col gap-4">
<p class="text-body-xs text-foreground font-medium">
How can we improve Speckle? If you have a feature request, please also share how
you would use it and why it's important to you
</p>
<FormTextArea
v-model="feedback"
:rules="[isRequired]"
name="feedback"
label="Feedback"
color="foundation"
/>
</div>
</LayoutDialog>
</template>
<script setup lang="ts">
import type { LayoutDialogButton } from '@speckle/ui-components'
import { useForm } from 'vee-validate'
import { useMixpanel } from '~/lib/core/composables/mp'
import { useZapier } from '~/lib/core/composables/zapier'
import { useGlobalToast, ToastNotificationType } from '~~/lib/common/composables/toast'
import { isRequired } from '~/lib/common/helpers/validation'
import { useActiveUser } from '~~/lib/auth/composables/activeUser'
type FormValues = { feedback: string }
const isOpen = defineModel<boolean>('open', { required: true })
const { activeUser: user } = useActiveUser()
const mixpanel = useMixpanel()
const { sendWebhook } = useZapier()
const { triggerNotification } = useGlobalToast()
const { handleSubmit } = useForm<FormValues>()
const feedback = ref('')
const dialogButtons = computed((): LayoutDialogButton[] => [
{
text: 'Send',
props: { color: 'primary' },
submit: true,
id: 'sendFeedback'
}
])
const onSubmit = handleSubmit(async () => {
if (!feedback.value) return
isOpen.value = false
triggerNotification({
type: ToastNotificationType.Success,
title: 'Thank you for your feedback!'
})
mixpanel.track('Feedback Sent', {
message: feedback.value
})
await sendWebhook('https://hooks.zapier.com/hooks/catch/12120532/2m4okri/', {
userId: user.value?.id ?? '',
feedback: feedback.value
})
})
watch(isOpen, (newVal) => {
if (newVal) {
feedback.value = ''
}
})
</script>
@@ -84,9 +84,8 @@
active ? 'bg-highlight-1' : '',
'text-body-xs flex px-2 py-1 text-foreground cursor-pointer transition mx-1 rounded'
]"
target="_blank"
to="https://docs.google.com/forms/d/e/1FAIpQLSeTOU8i0KwpgBG7ONimsh4YMqvLKZfSRhWEOz4W0MyjQ1lfAQ/viewform"
external
class="text-body-xs flex px-2 py-1 text-foreground cursor-pointer transition mx-1 rounded"
@click="openFeedbackDialog"
>
Feedback
</NuxtLink>
@@ -130,6 +129,7 @@
v-model:open="showSettingsDialog"
v-model:target-menu-item="settingsDialogTarget"
/>
<FeedbackDialog v-model:open="showFeedbackDialog" />
</div>
</template>
<script setup lang="ts">
@@ -169,6 +169,7 @@ const settingsDialogTarget = ref<string | null>(null)
const menuButtonId = useId()
const breakpoints = useBreakpoints(TailwindBreakpoints)
const isMobile = breakpoints.smaller('md')
const showFeedbackDialog = ref(false)
const version = computed(() => serverInfo.value?.version)
const isAdmin = computed(() => activeUser.value?.role === Roles.Server.Admin)
@@ -190,6 +191,10 @@ const deleteSettingsQuery = (): void => {
router.push({ query: currentQueryParams })
}
const openFeedbackDialog = () => {
showFeedbackDialog.value = true
}
onMounted(() => {
const settingsQuery = route.query?.settings
@@ -1,109 +0,0 @@
<template>
<ClientOnly>
<div class="position left-2 sm:left-auto right-2 bottom-2 fixed z-[45]">
<div
v-if="showBanner"
class="rounded-lg flex flex-col w-full sm:max-w-96 border border-outline-2 shadow-md bg-foundation-3 dark:bg-foundation"
>
<img :src="bannerImage" class="w-full" alt="Try workspaces" />
<div class="px-5 py-6 flex flex-col gap-y-2">
<h5 class="text-body-xs md:text-heading-sm text-foreground font-medium">
Still not using workspaces?
</h5>
<p class="text-body-2xs leading-5 md:text-body-xs text-foreground-2">
Be the first to reach more security options, data control, and better
project management with your team.
</p>
<div class="flex items-center gap-x-2 mt-2">
<FormButton color="primary" size="sm" @click="openWorkspaceCreateDialog">
Start for free
</FormButton>
<FormButton color="subtle" size="sm" @click="dismissedCookie = true">
Dismiss
</FormButton>
</div>
</div>
<WorkspaceCreateDialog
v-model:open="showWorkspaceCreateDialog"
navigate-on-success
event-source="promo-banner"
@created="dismissedCookie = true"
/>
</div>
</div>
</ClientOnly>
</template>
<script setup lang="ts">
// This is a temporary component, to meassure if in app-notifications can be succesful
// It will be remove after a certain period, if we continue with in-app notification we should further develop this
import { useMixpanel } from '~~/lib/core/composables/mp'
import { useSynchronizedCookie } from '~~/lib/common/composables/reactiveCookie'
import { CookieKeys } from '~/lib/common/helpers/constants'
import { useIsWorkspacesEnabled } from '~/composables/globals'
import { settingsSidebarQuery } from '~/lib/settings/graphql/queries'
import { useQuery } from '@vue/apollo-composable'
import { useTheme } from '~~/lib/core/composables/theme'
import { useBreakpoints } from '@vueuse/core'
import { TailwindBreakpoints } from '~~/lib/common/helpers/tailwind'
import imageLight from '~/assets/images/banners/workspace-promo-light.png'
import imageDark from '~/assets/images/banners/workspace-promo-dark.png'
import imageMobileLight from '~/assets/images/banners/workspace-promo-mobile-light.png'
import imageMobileDark from '~/assets/images/banners/workspace-promo-mobile-dark.png'
const { isLoggedIn } = useActiveUser()
const breakpoints = useBreakpoints(TailwindBreakpoints)
const { isDarkTheme } = useTheme()
const isWorkspacesEnabled = useIsWorkspacesEnabled()
const mixpanel = useMixpanel()
const dismissedCookie = useSynchronizedCookie<boolean>(
CookieKeys.DismissedWorkspaceBanner,
{
default: () => false
}
)
const { result } = useQuery(settingsSidebarQuery, null, {
enabled: isWorkspacesEnabled.value
})
const showWorkspaceCreateDialog = ref(false)
const isMobile = breakpoints.smaller('md')
const bannerImage = computed(() => {
if (isMobile.value) {
return isDarkTheme.value ? imageMobileDark : imageMobileLight
}
return isDarkTheme.value ? imageDark : imageLight
})
const hasWorkspaces = computed(() =>
result.value?.activeUser?.workspaces.items
? result.value.activeUser.workspaces.items.length > 0
: false
)
const showBanner = computed(
() =>
isWorkspacesEnabled.value &&
isLoggedIn.value &&
!hasWorkspaces.value &&
(import.meta.client ? !dismissedCookie.value : false)
)
const openWorkspaceCreateDialog = () => {
showWorkspaceCreateDialog.value = true
mixpanel.track('Create Workspace Button Clicked', {
source: 'promo-banner'
})
}
watch(
showBanner,
(newVal) => {
if (newVal) {
mixpanel.track('Workspace Promo Banner Viewed', {
source: 'promo-banner'
})
}
},
{ immediate: true }
)
</script>
-1
View File
@@ -1,7 +1,6 @@
<template>
<div>
<HeaderNavBar />
<PromoBannersWorkspace />
<div class="h-dvh w-dvh overflow-hidden flex flex-col">
<!-- Static Spacer to allow for absolutely positioned HeaderNavBar -->
<div class="h-12 w-full shrink-0"></div>
@@ -0,0 +1,18 @@
export function useZapier() {
const sendWebhook = async (webhookUrl: string, data: Record<string, string>) => {
const response = await fetch(webhookUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
mode: 'no-cors',
body: JSON.stringify(data)
})
return response
}
return {
sendWebhook
}
}
+5 -12
View File
@@ -3,7 +3,10 @@
<Portal to="navigation">
<HeaderNavLink :to="homeRoute" name="Dashboard" hide-chevron :separator="false" />
</Portal>
<PromoBannersWrapper v-if="promoBanners.length" :banners="promoBanners" />
<PromoBannersWrapper
v-if="promoBanners && promoBanners.length"
:banners="promoBanners"
/>
<ProjectsDashboardHeader
:projects-invites="projectsResult?.activeUser || undefined"
:workspaces-invites="workspacesResult?.activeUser || undefined"
@@ -72,7 +75,6 @@ import { downloadManager } from '~~/lib/common/utils/downloadManager'
import { ToastNotificationType, useGlobalToast } from '~~/lib/common/composables/toast'
import type { LayoutDialogButton } from '@speckle/ui-components'
import type { PromoBanner } from '~/lib/promo-banners/types'
import speckleconTicketsImage from '~/assets/images/banners/grab-your-tickets.gif'
useHead({ title: 'Dashboard' })
@@ -187,14 +189,5 @@ const onDownloadManager = (extension: ManagerExtension) => {
}
}
const promoBanners = ref<PromoBanner[]>([
{
primaryText: 'Specklecon - Grab your tickets',
url: 'https://conf.speckle.systems/',
priority: 1,
expiryDate: '2024-11-14',
image: speckleconTicketsImage,
isBackgroundImage: true
}
])
const promoBanners = ref<PromoBanner[]>()
</script>
@@ -16,8 +16,8 @@ import {
getStreamBranchesByNameFactory
} from '@/modules/core/repositories/branches'
import {
getAllBranchCommits,
getCommitsAndTheirBranchIds,
getAllBranchCommitsFactory,
getCommitsAndTheirBranchIdsFactory,
getSpecificBranchCommitsFactory
} from '@/modules/core/repositories/commits'
import { getStreamObjects } from '@/modules/core/repositories/objects'
@@ -60,7 +60,7 @@ export async function addCommentCreatedActivity(params: {
getViewerResourcesFromLegacyIdentifiers: (...args) =>
getViewerResourcesFromLegacyIdentifiers(...args) // recursive dep
}),
getCommitsAndTheirBranchIds,
getCommitsAndTheirBranchIds: getCommitsAndTheirBranchIdsFactory({ db }),
getStreamObjects
})
const getViewerResourceItemsUngrouped = getViewerResourceItemsUngroupedFactory({
@@ -69,7 +69,7 @@ export async function addCommentCreatedActivity(params: {
getBranchLatestCommits: getBranchLatestCommitsFactory({ db }),
getStreamBranchesByName: getStreamBranchesByNameFactory({ db }),
getSpecificBranchCommits: getSpecificBranchCommitsFactory({ db }),
getAllBranchCommits
getAllBranchCommits: getAllBranchCommitsFactory({ db })
})
})
@@ -146,7 +146,7 @@ export async function addCommentArchivedActivity(params: {
getViewerResourcesFromLegacyIdentifiers: (...args) =>
getViewerResourcesFromLegacyIdentifiers(...args) // recursive dep
}),
getCommitsAndTheirBranchIds,
getCommitsAndTheirBranchIds: getCommitsAndTheirBranchIdsFactory({ db }),
getStreamObjects
})
@@ -208,7 +208,7 @@ export async function addReplyAddedActivity(params: {
getViewerResourcesFromLegacyIdentifiers: (...args) =>
getViewerResourcesFromLegacyIdentifiers(...args) // recursive dep
}),
getCommitsAndTheirBranchIds,
getCommitsAndTheirBranchIds: getCommitsAndTheirBranchIdsFactory({ db }),
getStreamObjects
})
@@ -26,7 +26,7 @@ import {
} from '@/modules/core/services/commit/viewerResources'
import {
createCommitFactory,
getAllBranchCommits,
getAllBranchCommitsFactory,
getSpecificBranchCommitsFactory,
insertBranchCommitsFactory,
insertStreamCommitsFactory
@@ -102,7 +102,7 @@ const command: CommandModule<
getBranchLatestCommits,
getStreamBranchesByName: getStreamBranchesByNameFactory({ db }),
getSpecificBranchCommits: getSpecificBranchCommitsFactory({ db }),
getAllBranchCommits
getAllBranchCommits: getAllBranchCommitsFactory({ db })
})
})
const createCommentThreadAndNotify = createCommentThreadAndNotifyFactory({
@@ -32,7 +32,7 @@ import {
} from '@/modules/activitystream/services/commentActivity'
import {
createCommitFactory,
getAllBranchCommits,
getAllBranchCommitsFactory,
getSpecificBranchCommitsFactory,
insertBranchCommitsFactory,
insertStreamCommitsFactory
@@ -99,7 +99,7 @@ const command: CommandModule<
getBranchLatestCommits: getBranchLatestCommitsFactory({ db }),
getStreamBranchesByName: getStreamBranchesByNameFactory({ db }),
getSpecificBranchCommits: getSpecificBranchCommitsFactory({ db }),
getAllBranchCommits
getAllBranchCommits: getAllBranchCommitsFactory({ db })
})
})
const createCommentThreadAndNotify = createCommentThreadAndNotifyFactory({
@@ -86,8 +86,8 @@ import { CommentsEmitter } from '@/modules/comments/events/emitter'
import { getBlobsFactory } from '@/modules/blobstorage/repositories'
import { ResourceIdentifier } from '@/modules/comments/domain/types'
import {
getAllBranchCommits,
getCommitsAndTheirBranchIds,
getAllBranchCommitsFactory,
getCommitsAndTheirBranchIdsFactory,
getSpecificBranchCommitsFactory
} from '@/modules/core/repositories/commits'
import { getStreamObjects } from '@/modules/core/repositories/objects'
@@ -148,7 +148,7 @@ const getViewerResourcesFromLegacyIdentifiers =
getViewerResourcesFromLegacyIdentifiers: (...args) =>
getViewerResourcesFromLegacyIdentifiers(...args) // recursive dep
}),
getCommitsAndTheirBranchIds,
getCommitsAndTheirBranchIds: getCommitsAndTheirBranchIdsFactory({ db }),
getStreamObjects
})
@@ -179,7 +179,7 @@ const getViewerResourceItemsUngrouped = getViewerResourceItemsUngroupedFactory({
getBranchLatestCommits: getBranchLatestCommitsFactory({ db }),
getStreamBranchesByName: getStreamBranchesByNameFactory({ db }),
getSpecificBranchCommits: getSpecificBranchCommitsFactory({ db }),
getAllBranchCommits
getAllBranchCommits: getAllBranchCommitsFactory({ db })
})
})
const createCommentThreadAndNotify = createCommentThreadAndNotifyFactory({
@@ -1,9 +1,15 @@
import {
BranchCommit,
CommitWithStreamBranchMetadata,
Commit
Commit,
CommitBranch
} from '@/modules/core/domain/commits/types'
import {
CommitUpdateInput,
UpdateVersionInput
} from '@/modules/core/graph/generated/graphql'
import { BranchCommitRecord, StreamCommitRecord } from '@/modules/core/helpers/types'
import { BatchedSelectOptions } from '@/modules/shared/helpers/dbHelper'
import { MaybeNullOrUndefined, Nullable, Optional } from '@speckle/shared'
import { Knex } from 'knex'
@@ -86,3 +92,82 @@ export type InsertStreamCommits = (
trx: Knex.Transaction
}>
) => Promise<number[]>
export type UpdateCommitAndNotify = (
params: CommitUpdateInput | UpdateVersionInput,
userId: string
) => Promise<Commit>
export type GetCommitBranches = (commitIds: string[]) => Promise<CommitBranch[]>
export type GetCommitBranch = (commitId: string) => Promise<Optional<CommitBranch>>
export type SwitchCommitBranch = (
commitId: string,
newBranchId: string,
oldBranchId?: string
) => Promise<void>
export type UpdateCommit = (
commitId: string,
commit: Partial<Commit>
) => Promise<Commit>
export type GetAllBranchCommits = (params: {
branchIds?: string[]
projectId?: string
}) => Promise<{ [branchId: string]: Commit[] }>
export type GetStreamCommitCounts = (
streamIds: string[],
options?: Partial<{
ignoreGlobalsBranch: boolean
}>
) => Promise<
{
count: number
streamId: string
}[]
>
export type GetStreamCommitCount = (
streamId: string,
options?: Partial<{
ignoreGlobalsBranch: boolean
}>
) => Promise<number>
export type GetUserStreamCommitCounts = (params: {
userIds: string[]
publicOnly?: boolean
}) => Promise<{
[userId: string]: number
}>
export type GetUserAuthoredCommitCounts = (params: {
userIds: string[]
publicOnly?: boolean
}) => Promise<{
[userId: string]: number
}>
export type GetCommitsAndTheirBranchIds = (
commitIds: string[]
) => Promise<BranchCommit[]>
export type GetBatchedStreamCommits = (
streamId: string,
options?: Partial<BatchedSelectOptions>
) => AsyncGenerator<Commit[], void, unknown>
export type GetBatchedBranchCommits = (
branchIds: string[],
options?: Partial<BatchedSelectOptions>
) => AsyncGenerator<BranchCommitRecord[], void, unknown>
export type InsertCommits = (
commits: Commit[],
options?: Partial<{
trx: Knex.Transaction
}>
) => Promise<number[]>
@@ -1,3 +1,4 @@
import { Branch } from '@/modules/core/domain/branches/types'
import { CommitRecord } from '@/modules/core/helpers/types'
export type Commit = CommitRecord
@@ -11,3 +12,5 @@ export type CommitWithStreamBranchMetadata = Commit & {
branchId: string
branchName: string
}
export type CommitBranch = Branch & { commitId: string }
@@ -16,11 +16,11 @@ const {
getPaginatedBranchCommits
} = require('@/modules/core/services/commit/retrieval')
const {
updateCommitAndNotify,
markCommitReceivedAndNotify,
deleteCommitAndNotifyFactory,
createCommitByBranchIdFactory,
createCommitByBranchNameFactory
createCommitByBranchNameFactory,
updateCommitAndNotifyFactory
} = require('@/modules/core/services/commit/management')
const { RateLimitError } = require('@/modules/core/errors/ratelimit')
@@ -44,10 +44,17 @@ const {
deleteCommitFactory,
createCommitFactory,
insertStreamCommitsFactory,
insertBranchCommitsFactory
insertBranchCommitsFactory,
getCommitBranchFactory,
switchCommitBranchFactory,
updateCommitFactory
} = require('@/modules/core/repositories/commits')
const { db } = require('@/db/knex')
const { markCommitStreamUpdated } = require('@/modules/core/repositories/streams')
const {
markCommitStreamUpdated,
getStream,
getCommitStream
} = require('@/modules/core/repositories/streams')
const {
markCommitBranchUpdatedFactory,
getBranchByIdFactory,
@@ -55,7 +62,8 @@ const {
} = require('@/modules/core/repositories/branches')
const {
addCommitDeletedActivity,
addCommitCreatedActivity
addCommitCreatedActivity,
addCommitUpdatedActivity
} = require('@/modules/activitystream/services/commitActivity')
const { getObject } = require('@/modules/core/repositories/objects')
const { VersionsEmitter } = require('@/modules/core/events/versionsEmitter')
@@ -91,6 +99,19 @@ const createCommitByBranchName = createCommitByBranchNameFactory({
getBranchById: getBranchByIdFactory({ db })
})
const updateCommitAndNotify = updateCommitAndNotifyFactory({
getCommit: getCommitFactory({ db }),
getStream,
getCommitStream,
getStreamBranchByName: getStreamBranchByNameFactory({ db }),
getCommitBranch: getCommitBranchFactory({ db }),
switchCommitBranch: switchCommitBranchFactory({ db }),
updateCommit: updateCommitFactory({ db }),
addCommitUpdatedActivity,
markCommitStreamUpdated,
markCommitBranchUpdated: markCommitBranchUpdatedFactory({ db })
})
/**
* @param {boolean} publicOnly
* @param {string} userId
@@ -41,7 +41,7 @@ import { BranchNotFoundError } from '@/modules/core/errors/branch'
import { CommitNotFoundError } from '@/modules/core/errors/commit'
import { getStreamObjects } from '@/modules/core/repositories/objects'
import {
getAllBranchCommits,
getAllBranchCommitsFactory,
getSpecificBranchCommitsFactory
} from '@/modules/core/repositories/commits'
import { db } from '@/db/knex'
@@ -58,7 +58,7 @@ const getViewerResourceGroups = getViewerResourceGroupsFactory({
getBranchLatestCommits: getBranchLatestCommitsFactory({ db }),
getStreamBranchesByName: getStreamBranchesByNameFactory({ db }),
getSpecificBranchCommits: getSpecificBranchCommitsFactory({ db }),
getAllBranchCommits
getAllBranchCommits: getAllBranchCommitsFactory({ db })
})
const getPaginatedProjectModels = getPaginatedProjectModelsFactory({
@@ -14,7 +14,7 @@ import { CommitUpdateError } from '@/modules/core/errors/commit'
import {
createCommitByBranchIdFactory,
markCommitReceivedAndNotify,
updateCommitAndNotify
updateCommitAndNotifyFactory
} from '@/modules/core/services/commit/management'
import {
getRateLimitResult,
@@ -23,18 +23,30 @@ import {
import { RateLimitError } from '@/modules/core/errors/ratelimit'
import {
createCommitFactory,
getCommitBranchFactory,
getCommitFactory,
insertBranchCommitsFactory,
insertStreamCommitsFactory
insertStreamCommitsFactory,
switchCommitBranchFactory,
updateCommitFactory
} from '@/modules/core/repositories/commits'
import { db } from '@/db/knex'
import { getObject } from '@/modules/core/repositories/objects'
import {
getBranchByIdFactory,
getStreamBranchByNameFactory,
markCommitBranchUpdatedFactory
} from '@/modules/core/repositories/branches'
import { markCommitStreamUpdated } from '@/modules/core/repositories/streams'
import {
getCommitStream,
getStream,
markCommitStreamUpdated
} from '@/modules/core/repositories/streams'
import { VersionsEmitter } from '@/modules/core/events/versionsEmitter'
import { addCommitCreatedActivity } from '@/modules/activitystream/services/commitActivity'
import {
addCommitCreatedActivity,
addCommitUpdatedActivity
} from '@/modules/activitystream/services/commitActivity'
const createCommitByBranchId = createCommitByBranchIdFactory({
createCommit: createCommitFactory({ db }),
@@ -48,6 +60,19 @@ const createCommitByBranchId = createCommitByBranchIdFactory({
addCommitCreatedActivity
})
const updateCommitAndNotify = updateCommitAndNotifyFactory({
getCommit: getCommitFactory({ db }),
getStream,
getCommitStream,
getStreamBranchByName: getStreamBranchByNameFactory({ db }),
getCommitBranch: getCommitBranchFactory({ db }),
switchCommitBranch: switchCommitBranchFactory({ db }),
updateCommit: updateCommitFactory({ db }),
addCommitUpdatedActivity,
markCommitStreamUpdated,
markCommitBranchUpdated: markCommitBranchUpdatedFactory({ db })
})
export = {
Project: {
async version(parent, args, ctx) {
+8 -4
View File
@@ -23,12 +23,12 @@ import {
import { Nullable } from '@/modules/shared/helpers/typeHelper'
import { ServerInviteRecord } from '@/modules/serverinvites/domain/types'
import {
getCommitBranches,
getCommitBranchesFactory,
getCommitsFactory,
getSpecificBranchCommitsFactory,
getStreamCommitCounts,
getUserAuthoredCommitCounts,
getUserStreamCommitCounts
getStreamCommitCountsFactory,
getUserAuthoredCommitCountsFactory,
getUserStreamCommitCountsFactory
} from '@/modules/core/repositories/commits'
import { ResourceIdentifier, Scope } from '@/modules/core/graph/generated/graphql'
import {
@@ -112,6 +112,10 @@ const getStreamBranchCounts = getStreamBranchCountsFactory({ db })
const getBranchCommitCounts = getBranchCommitCountsFactory({ db })
const getCommits = getCommitsFactory({ db })
const getSpecificBranchCommits = getSpecificBranchCommitsFactory({ db })
const getCommitBranches = getCommitBranchesFactory({ db })
const getStreamCommitCounts = getStreamCommitCountsFactory({ db })
const getUserStreamCommitCounts = getUserStreamCommitCountsFactory({ db })
const getUserAuthoredCommitCounts = getUserAuthoredCommitCountsFactory({ db })
/**
* TODO: Lazy load DataLoaders to reduce memory usage
@@ -11,6 +11,7 @@ import {
BranchCommitRecord,
BranchRecord,
CommitRecord,
StreamAclRecord,
StreamCommitRecord
} from '@/modules/core/helpers/types'
import { clamp, uniq, uniqBy, reduce, keyBy, mapValues } from 'lodash'
@@ -30,13 +31,27 @@ import {
GetCommits,
GetSpecificBranchCommits,
InsertBranchCommits,
InsertStreamCommits
InsertStreamCommits,
GetCommitBranches,
GetCommitBranch,
SwitchCommitBranch,
UpdateCommit,
GetAllBranchCommits,
GetStreamCommitCounts,
GetStreamCommitCount,
GetUserStreamCommitCounts,
GetUserAuthoredCommitCounts,
GetCommitsAndTheirBranchIds,
GetBatchedStreamCommits,
GetBatchedBranchCommits,
InsertCommits
} from '@/modules/core/domain/commits/operations'
const tables = {
commits: (db: Knex) => db<CommitRecord>(Commits.name),
branchCommits: (db: Knex) => db<BranchCommitRecord>(BranchCommits.name),
streamCommits: (db: Knex) => db<StreamCommitRecord>(StreamCommits.name)
streamCommits: (db: Knex) => db<StreamCommitRecord>(StreamCommits.name),
streamAcl: (db: Knex) => db<StreamAclRecord>(StreamAcl.name)
}
export const generateCommitId = () => crs({ length: 10 })
@@ -121,38 +136,38 @@ export const deleteCommitFactory =
return !!delCount
}
export function getBatchedStreamCommits(
streamId: string,
options?: Partial<BatchedSelectOptions>
) {
const baseQuery = Commits.knex<CommitRecord[]>()
.select<CommitRecord[]>(Commits.cols)
.innerJoin(StreamCommits.name, StreamCommits.col.commitId, Commits.col.id)
.where(StreamCommits.col.streamId, streamId)
.orderBy(Commits.col.id)
export const getBatchedStreamCommitsFactory =
(deps: { db: Knex }): GetBatchedStreamCommits =>
(streamId: string, options?: Partial<BatchedSelectOptions>) => {
const baseQuery = tables
.commits(deps.db)
.select<CommitRecord[]>(Commits.cols)
.innerJoin(StreamCommits.name, StreamCommits.col.commitId, Commits.col.id)
.where(StreamCommits.col.streamId, streamId)
.orderBy(Commits.col.id)
return executeBatchedSelect(baseQuery, options)
}
return executeBatchedSelect(baseQuery, options)
}
export function getBatchedBranchCommits(
branchIds: string[],
options?: Partial<BatchedSelectOptions>
) {
const baseQuery = BranchCommits.knex<BranchCommitRecord[]>()
.whereIn(BranchCommits.col.branchId, branchIds)
.orderBy(BranchCommits.col.branchId)
export const getBatchedBranchCommitsFactory =
(deps: { db: Knex }): GetBatchedBranchCommits =>
(branchIds: string[], options?: Partial<BatchedSelectOptions>) => {
const baseQuery = tables
.branchCommits(deps.db)
.select<BranchCommitRecord[]>('*')
.whereIn(BranchCommits.col.branchId, branchIds)
.orderBy(BranchCommits.col.branchId)
return executeBatchedSelect(baseQuery, options)
}
return executeBatchedSelect(baseQuery, options)
}
export async function insertCommits(
commits: CommitRecord[],
options?: Partial<{ trx: Knex.Transaction }>
) {
const q = Commits.knex().insert(commits)
if (options?.trx) q.transacting(options.trx)
return await q
}
export const insertCommitsFactory =
(deps: { db: Knex }): InsertCommits =>
async (commits: CommitRecord[], options?: Partial<{ trx: Knex.Transaction }>) => {
const q = tables.commits(deps.db).insert(commits)
if (options?.trx) q.transacting(options.trx)
return await q
}
export const insertStreamCommitsFactory =
(deps: { db: Knex }): InsertStreamCommits =>
@@ -176,53 +191,55 @@ export const insertBranchCommitsFactory =
return await q
}
export async function getStreamCommitCounts(
streamIds: string[],
options?: Partial<{ ignoreGlobalsBranch: boolean }>
) {
if (!streamIds?.length) return []
export const getStreamCommitCountsFactory =
(deps: { db: Knex }): GetStreamCommitCounts =>
async (streamIds: string[], options?: Partial<{ ignoreGlobalsBranch: boolean }>) => {
if (!streamIds?.length) return []
const { ignoreGlobalsBranch } = options || {}
const { ignoreGlobalsBranch } = options || {}
const q = StreamCommits.knex()
.select(StreamCommits.col.streamId)
.whereIn(StreamCommits.col.streamId, streamIds)
.count()
.groupBy(StreamCommits.col.streamId)
const q = tables
.streamCommits(deps.db)
.select(StreamCommits.col.streamId)
.whereIn(StreamCommits.col.streamId, streamIds)
.count()
.groupBy(StreamCommits.col.streamId)
if (ignoreGlobalsBranch) {
q.innerJoin(
BranchCommits.name,
StreamCommits.col.commitId,
BranchCommits.col.commitId
)
.innerJoin(Branches.name, Branches.col.id, BranchCommits.col.branchId)
.andWhereNot(Branches.col.name, 'globals')
if (ignoreGlobalsBranch) {
q.innerJoin(
BranchCommits.name,
StreamCommits.col.commitId,
BranchCommits.col.commitId
)
.innerJoin(Branches.name, Branches.col.id, BranchCommits.col.branchId)
.andWhereNot(Branches.col.name, 'globals')
}
const results = (await q) as { streamId: string; count: string }[]
return results.map((r) => ({ ...r, count: parseInt(r.count) }))
}
const results = (await q) as { streamId: string; count: string }[]
return results.map((r) => ({ ...r, count: parseInt(r.count) }))
}
export const getStreamCommitCountFactory =
(deps: { db: Knex }): GetStreamCommitCount =>
async (streamId: string, options?: Partial<{ ignoreGlobalsBranch: boolean }>) => {
const [res] = await getStreamCommitCountsFactory(deps)([streamId], options)
return res?.count || 0
}
export async function getStreamCommitCount(
streamId: string,
options?: Partial<{ ignoreGlobalsBranch: boolean }>
) {
const [res] = await getStreamCommitCounts([streamId], options)
return res?.count || 0
}
export const getCommitsAndTheirBranchIdsFactory =
(deps: { db: Knex }): GetCommitsAndTheirBranchIds =>
async (commitIds: string[]) => {
if (!commitIds.length) return []
export async function getCommitsAndTheirBranchIds(commitIds: string[]) {
if (!commitIds.length) return []
return await Commits.knex()
.select<Array<CommitRecord & { branchId: string }>>([
...Commits.cols,
BranchCommits.col.branchId
])
.innerJoin(BranchCommits.name, BranchCommits.col.commitId, Commits.col.id)
.whereIn(Commits.col.id, commitIds)
}
return await tables
.commits(deps.db)
.select<Array<CommitRecord & { branchId: string }>>([
...Commits.cols,
BranchCommits.col.branchId
])
.innerJoin(BranchCommits.name, BranchCommits.col.commitId, Commits.col.id)
.whereIn(Commits.col.id, commitIds)
}
export const getSpecificBranchCommitsFactory =
(deps: { db: Knex }): GetSpecificBranchCommits =>
@@ -321,47 +338,54 @@ export async function getBranchCommitsTotalCount(
return parseInt(res?.count || '0')
}
export async function getCommitBranches(commitIds: string[]) {
if (!commitIds?.length) return []
export const getCommitBranchesFactory =
(deps: { db: Knex }): GetCommitBranches =>
async (commitIds: string[]) => {
if (!commitIds?.length) return []
const q = BranchCommits.knex()
.select<Array<BranchRecord & { commitId: string }>>([
...Branches.cols,
knex.raw(`?? as "commitId"`, [BranchCommits.col.commitId])
])
.innerJoin(Branches.name, Branches.col.id, BranchCommits.col.branchId)
.whereIn(BranchCommits.col.commitId, commitIds)
const q = tables
.branchCommits(deps.db)
.select<Array<BranchRecord & { commitId: string }>>([
...Branches.cols,
knex.raw(`?? as "commitId"`, [BranchCommits.col.commitId])
])
.innerJoin(Branches.name, Branches.col.id, BranchCommits.col.branchId)
.whereIn(BranchCommits.col.commitId, commitIds)
return await q
}
export async function getCommitBranch(commitId: string) {
const [commit] = await getCommitBranches([commitId])
return commit as Optional<typeof commit>
}
export async function switchCommitBranch(
commitId: string,
newBranchId: string,
oldBranchId?: string
) {
const q = BranchCommits.knex()
.where(BranchCommits.col.commitId, commitId)
.update(BranchCommits.withoutTablePrefix.col.branchId, newBranchId)
if (oldBranchId) {
q.andWhere(BranchCommits.col.branchId, oldBranchId)
return await q
}
await q
}
export const getCommitBranchFactory =
(deps: { db: Knex }): GetCommitBranch =>
async (commitId: string) => {
const [commit] = await getCommitBranchesFactory(deps)([commitId])
return commit as Optional<typeof commit>
}
export async function updateCommit(commitId: string, commit: Partial<CommitRecord>) {
const [newCommit] = (await Commits.knex()
.where(Commits.col.id, commitId)
.update(commit, '*')) as CommitRecord[]
return newCommit
}
export const switchCommitBranchFactory =
(deps: { db: Knex }): SwitchCommitBranch =>
async (commitId: string, newBranchId: string, oldBranchId?: string) => {
const q = tables
.branchCommits(deps.db)
.where(BranchCommits.col.commitId, commitId)
.update(BranchCommits.withoutTablePrefix.col.branchId, newBranchId)
if (oldBranchId) {
q.andWhere(BranchCommits.col.branchId, oldBranchId)
}
await q
}
export const updateCommitFactory =
(deps: { db: Knex }): UpdateCommit =>
async (commitId: string, commit: Partial<CommitRecord>) => {
const [newCommit] = (await tables
.commits(deps.db)
.where(Commits.col.id, commitId)
.update(commit, '*')) as CommitRecord[]
return newCommit
}
export const createCommitFactory =
(deps: { db: Knex }): StoreCommit =>
@@ -407,97 +431,106 @@ export async function getObjectCommitsWithStreamIds(
return await q
}
export async function getAllBranchCommits(params: {
branchIds?: string[]
projectId?: string
}): Promise<Record<string, CommitRecord[]>> {
const { branchIds, projectId } = params
if (!branchIds?.length && !projectId) return {}
export const getAllBranchCommitsFactory =
(deps: { db: Knex }): GetAllBranchCommits =>
async (params: {
branchIds?: string[]
projectId?: string
}): Promise<Record<string, CommitRecord[]>> => {
const { branchIds, projectId } = params
if (!branchIds?.length && !projectId) return {}
const q = BranchCommits.knex()
.select<Array<CommitRecord & { branchId: string }>>([
...Commits.cols,
BranchCommits.col.branchId
])
.innerJoin(Commits.name, Commits.col.id, BranchCommits.col.commitId)
const q = tables
.branchCommits(deps.db)
.select<Array<CommitRecord & { branchId: string }>>([
...Commits.cols,
BranchCommits.col.branchId
])
.innerJoin(Commits.name, Commits.col.id, BranchCommits.col.commitId)
if (branchIds?.length) {
q.whereIn(BranchCommits.col.branchId, branchIds)
if (branchIds?.length) {
q.whereIn(BranchCommits.col.branchId, branchIds)
}
if (projectId) {
q.innerJoin(Branches.name, Branches.col.id, BranchCommits.col.branchId)
q.andWhere(Branches.col.streamId, projectId)
}
const res = await q
return reduce(
res,
(res, item) => {
const branchId = item.branchId
;(res[branchId] = res[branchId] || []).push(item)
return res
},
{} as Record<string, CommitRecord[]>
)
}
if (projectId) {
q.innerJoin(Branches.name, Branches.col.id, BranchCommits.col.branchId)
q.andWhere(Branches.col.streamId, projectId)
export const getUserStreamCommitCountsFactory =
(deps: { db: Knex }): GetUserStreamCommitCounts =>
async (params: {
userIds: string[]
/**
* Only include commits from public/discoverable streams
*/
publicOnly?: boolean
}) => {
const { userIds, publicOnly } = params
if (!userIds?.length) return {}
const q = tables
.streamAcl(deps.db)
.select<{ userId: string; count: string }[]>([
StreamAcl.col.userId,
knex.raw('COUNT(*)')
])
.join(StreamCommits.name, StreamCommits.col.streamId, StreamAcl.col.resourceId)
.whereIn(StreamAcl.col.userId, userIds)
.groupBy(StreamAcl.col.userId)
if (publicOnly) {
q.join(Streams.name, Streams.col.id, StreamAcl.col.resourceId)
q.andWhere((q1) => {
q1.where(Streams.col.isPublic, true).orWhere(Streams.col.isDiscoverable, true)
})
}
const res = await q
return mapValues(keyBy(res, 'userId'), (r) => parseInt(r.count))
}
const res = await q
return reduce(
res,
(res, item) => {
const branchId = item.branchId
;(res[branchId] = res[branchId] || []).push(item)
return res
},
{} as Record<string, CommitRecord[]>
)
}
export const getUserAuthoredCommitCountsFactory =
(deps: { db: Knex }): GetUserAuthoredCommitCounts =>
async (params: {
userIds: string[]
/**
* Only include commits from public/discoverable streams
*/
publicOnly?: boolean
}) => {
const { userIds, publicOnly } = params
if (!userIds?.length) return {}
export async function getUserStreamCommitCounts(params: {
userIds: string[]
/**
* Only include commits from public/discoverable streams
*/
publicOnly?: boolean
}) {
const { userIds, publicOnly } = params
if (!userIds?.length) return {}
const q = tables
.commits(deps.db)
.select<{ authorId: string; count: string }[]>([
Commits.col.author,
knex.raw('COUNT(*)')
])
.whereIn(Commits.col.author, userIds)
.groupBy(Commits.col.author)
const q = StreamAcl.knex()
.select<{ userId: string; count: string }[]>([
StreamAcl.col.userId,
knex.raw('COUNT(*)')
])
.join(StreamCommits.name, StreamCommits.col.streamId, StreamAcl.col.resourceId)
.whereIn(StreamAcl.col.userId, userIds)
.groupBy(StreamAcl.col.userId)
if (publicOnly) {
q.join(StreamCommits.name, StreamCommits.col.commitId, Commits.col.id)
q.join(Streams.name, Streams.col.id, StreamCommits.col.streamId)
q.andWhere((q1) => {
q1.where(Streams.col.isPublic, true).orWhere(Streams.col.isDiscoverable, true)
})
}
if (publicOnly) {
q.join(Streams.name, Streams.col.id, StreamAcl.col.resourceId)
q.andWhere((q1) => {
q1.where(Streams.col.isPublic, true).orWhere(Streams.col.isDiscoverable, true)
})
const res = await q
return mapValues(keyBy(res, 'author'), (r) => parseInt(r.count))
}
const res = await q
return mapValues(keyBy(res, 'userId'), (r) => parseInt(r.count))
}
export async function getUserAuthoredCommitCounts(params: {
userIds: string[]
/**
* Only include commits from public/discoverable streams
*/
publicOnly?: boolean
}) {
const { userIds, publicOnly } = params
if (!userIds?.length) return {}
const q = Commits.knex()
.select<{ authorId: string; count: string }[]>([
Commits.col.author,
knex.raw('COUNT(*)')
])
.whereIn(Commits.col.author, userIds)
.groupBy(Commits.col.author)
if (publicOnly) {
q.join(StreamCommits.name, StreamCommits.col.commitId, Commits.col.id)
q.join(Streams.name, Streams.col.id, StreamCommits.col.streamId)
q.andWhere((q1) => {
q1.where(Streams.col.isPublic, true).orWhere(Streams.col.isDiscoverable, true)
})
}
const res = await q
return mapValues(keyBy(res, 'author'), (r) => parseInt(r.count))
}
@@ -16,9 +16,13 @@ import {
DeleteCommit,
DeleteCommitAndNotify,
GetCommit,
GetCommitBranch,
InsertBranchCommits,
InsertStreamCommits,
StoreCommit
StoreCommit,
SwitchCommitBranch,
UpdateCommit,
UpdateCommitAndNotify
} from '@/modules/core/domain/commits/operations'
import {
CommitCreateError,
@@ -37,16 +41,7 @@ import {
UpdateVersionInput
} from '@/modules/core/graph/generated/graphql'
import { CommitRecord } from '@/modules/core/helpers/types'
import {
getStreamBranchByNameFactory,
markCommitBranchUpdatedFactory
} from '@/modules/core/repositories/branches'
import {
getCommitBranch,
getCommitFactory,
switchCommitBranch,
updateCommit
} from '@/modules/core/repositories/commits'
import { getCommitFactory } from '@/modules/core/repositories/commits'
import { getObject } from '@/modules/core/repositories/objects'
import {
getCommitStream,
@@ -255,99 +250,111 @@ const isOldVersionUpdateInput = (
i: CommitUpdateInput | UpdateVersionInput
): i is CommitUpdateInput => has(i, 'streamId')
export async function updateCommitAndNotify(
params: CommitUpdateInput | UpdateVersionInput,
userId: string
) {
const {
message,
newBranchName,
streamId,
id: commitId
} = isOldVersionUpdateInput(params)
? params
: {
message: params.message,
id: params.versionId,
streamId: null,
newBranchName: null
}
export const updateCommitAndNotifyFactory =
(deps: {
getCommit: GetCommit
getStream: typeof getStream
getCommitStream: typeof getCommitStream
getStreamBranchByName: GetStreamBranchByName
getCommitBranch: GetCommitBranch
switchCommitBranch: SwitchCommitBranch
updateCommit: UpdateCommit
addCommitUpdatedActivity: typeof addCommitUpdatedActivity
markCommitStreamUpdated: typeof markCommitStreamUpdated
markCommitBranchUpdated: MarkCommitBranchUpdated
}): UpdateCommitAndNotify =>
async (params: CommitUpdateInput | UpdateVersionInput, userId: string) => {
const {
message,
newBranchName,
streamId,
id: commitId
} = isOldVersionUpdateInput(params)
? params
: {
message: params.message,
id: params.versionId,
streamId: null,
newBranchName: null
}
if (!message && !newBranchName) {
throw new CommitUpdateError('Nothing to update', {
info: { ...params, userId }
})
}
const [commit, stream] = await Promise.all([
getCommitFactory({ db })(commitId),
streamId ? getStream({ streamId, userId }) : getCommitStream({ commitId, userId })
])
if (!commit) {
throw new CommitUpdateError("Can't update nonexistant commit", {
info: { ...params, userId }
})
}
if (!stream) {
throw new CommitUpdateError("Can't resolve commit stream", {
info: { ...params, userId }
})
}
if (commit.author !== userId && stream.role !== Roles.Stream.Owner) {
throw new CommitUpdateError(
'Only the author of a commit or a stream owner may update it',
{
info: { ...params, userId, streamRole: stream.role }
}
)
}
if (newBranchName) {
try {
const [newBranch, oldBranch] = await Promise.all([
getStreamBranchByNameFactory({ db })(streamId, newBranchName),
getCommitBranch(commitId)
])
if (!newBranch || !oldBranch) {
throw new Error("Couldn't resolve branch")
}
if (!commit) {
throw new Error("Couldn't find commit")
}
await switchCommitBranch(commitId, newBranch.id, oldBranch.id)
} catch (e) {
throw new CommitUpdateError('Failed to update commit branch', {
cause: ensureError(e),
info: params
if (!message && !newBranchName) {
throw new CommitUpdateError('Nothing to update', {
info: { ...params, userId }
})
}
}
let newCommit: CommitRecord = commit
if (message) {
newCommit = await updateCommit(commitId, { message })
}
if (commit) {
await addCommitUpdatedActivity({
commitId,
streamId: stream.id,
userId,
originalCommit: commit,
update: params,
newCommit
})
await Promise.all([
markCommitStreamUpdated(commit.id),
markCommitBranchUpdatedFactory({ db })(commit.id)
const [commit, stream] = await Promise.all([
deps.getCommit(commitId),
streamId
? deps.getStream({ streamId, userId })
: deps.getCommitStream({ commitId, userId })
])
}
if (!commit) {
throw new CommitUpdateError("Can't update nonexistant commit", {
info: { ...params, userId }
})
}
if (!stream) {
throw new CommitUpdateError("Can't resolve commit stream", {
info: { ...params, userId }
})
}
if (commit.author !== userId && stream.role !== Roles.Stream.Owner) {
throw new CommitUpdateError(
'Only the author of a commit or a stream owner may update it',
{
info: { ...params, userId, streamRole: stream.role }
}
)
}
return newCommit
}
if (newBranchName) {
try {
const [newBranch, oldBranch] = await Promise.all([
deps.getStreamBranchByName(streamId, newBranchName),
deps.getCommitBranch(commitId)
])
if (!newBranch || !oldBranch) {
throw new Error("Couldn't resolve branch")
}
if (!commit) {
throw new Error("Couldn't find commit")
}
await deps.switchCommitBranch(commitId, newBranch.id, oldBranch.id)
} catch (e) {
throw new CommitUpdateError('Failed to update commit branch', {
cause: ensureError(e),
info: params
})
}
}
let newCommit: CommitRecord = commit
if (message) {
newCommit = await deps.updateCommit(commitId, { message })
}
if (commit) {
await deps.addCommitUpdatedActivity({
commitId,
streamId: stream.id,
userId,
originalCommit: commit,
update: params,
newCommit
})
await Promise.all([
deps.markCommitStreamUpdated(commit.id),
deps.markCommitBranchUpdated(commit.id)
])
}
return newCommit
}
export const deleteCommitAndNotifyFactory =
(deps: {
@@ -7,12 +7,10 @@ import {
getBranchCommitsTotalCount,
getPaginatedBranchCommits as getPaginatedBranchCommitsDb,
getSpecificBranchCommitsFactory,
getStreamCommitCountFactory,
PaginatedBranchCommitsParams
} from '@/modules/core/repositories/commits'
import {
getCommitsByStreamId,
getCommitsTotalCountByStreamId
} from '@/modules/core/services/commits'
import { getCommitsByStreamId } from '@/modules/core/services/commits'
import { BadRequestError } from '@/modules/shared/errors'
import { db } from '@/db/knex'
@@ -30,8 +28,7 @@ export async function getPaginatedStreamCommits(
cursor: params.cursor,
ignoreGlobalsBranch: true
})
const totalCount = await getCommitsTotalCountByStreamId({
streamId,
const totalCount = await getStreamCommitCountFactory({ db })(streamId, {
ignoreGlobalsBranch: true
})
@@ -10,7 +10,11 @@ import {
GetBranchLatestCommits,
GetStreamBranchesByName
} from '@/modules/core/domain/branches/operations'
import { GetSpecificBranchCommits } from '@/modules/core/domain/commits/operations'
import {
GetAllBranchCommits,
GetCommitsAndTheirBranchIds,
GetSpecificBranchCommits
} from '@/modules/core/domain/commits/operations'
import {
ResourceIdentifier,
ResourceIdentifierInput,
@@ -20,10 +24,6 @@ import {
ViewerUpdateTrackingTarget
} from '@/modules/core/graph/generated/graphql'
import { CommitRecord } from '@/modules/core/helpers/types'
import {
getAllBranchCommits,
getCommitsAndTheirBranchIds
} from '@/modules/core/repositories/commits'
import { getStreamObjects } from '@/modules/core/repositories/objects'
import { Optional, SpeckleViewer } from '@speckle/shared'
import { flatten, keyBy, reduce, uniq, uniqWith } from 'lodash'
@@ -77,7 +77,7 @@ const getObjectResourceGroupsFactory =
type GetVersionResourceGroupsIncludingAllVersionsFactoryDeps = {
getStreamBranchesByName: GetStreamBranchesByName
getAllBranchCommits: typeof getAllBranchCommits
getAllBranchCommits: GetAllBranchCommits
}
const getVersionResourceGroupsIncludingAllVersionsFactory =
@@ -370,7 +370,7 @@ export const getViewerResourcesFromLegacyIdentifiersFactory =
(
deps: {
getViewerResourcesForComments: GetViewerResourcesForComments
getCommitsAndTheirBranchIds: typeof getCommitsAndTheirBranchIds
getCommitsAndTheirBranchIds: GetCommitsAndTheirBranchIds
} & GetObjectResourceGroupsDeps
): GetViewerResourcesFromLegacyIdentifiers =>
async (
@@ -6,11 +6,9 @@ const Commits = () => knex('commits')
const StreamCommits = () => knex('stream_commits')
const {
getStreamCommitCount,
getPaginatedBranchCommits,
getBranchCommitsTotalCount
} = require('@/modules/core/repositories/commits')
const { updateCommitAndNotify } = require('@/modules/core/services/commit/management')
const { clamp } = require('lodash')
const getCommitsByUserIdBase = ({ userId, publicOnly, streamIdWhitelist }) => {
@@ -47,14 +45,6 @@ const getCommitsByUserIdBase = ({ userId, publicOnly, streamIdWhitelist }) => {
}
module.exports = {
/**
* @deprecated Use 'updateCommitAndNotify()'
*/
async updateCommit({ streamId, id, message, newBranchName, userId }) {
await updateCommitAndNotify({ streamId, id, message, newBranchName }, userId)
return true
},
/**
* @deprecated Use `getBranchCommitsTotalCount()` instead
*/
@@ -89,10 +79,6 @@ module.exports = {
return module.exports.getCommitsByBranchId({ branchId: myBranch.id, limit, cursor })
},
async getCommitsTotalCountByStreamId({ streamId, ignoreGlobalsBranch }) {
return await getStreamCommitCount(streamId, { ignoreGlobalsBranch })
},
/**
* @returns {Promise<{
* commits: import('@/modules/core/helpers/types').CommitRecord[],
@@ -15,12 +15,12 @@ import {
insertObjects
} from '@/modules/core/repositories/objects'
import {
getBatchedStreamCommits,
generateCommitId,
insertCommits,
getBatchedBranchCommits,
insertStreamCommitsFactory,
insertBranchCommitsFactory
insertBranchCommitsFactory,
getBatchedStreamCommitsFactory,
getBatchedBranchCommitsFactory,
insertCommitsFactory
} from '@/modules/core/repositories/commits'
import { chunk } from 'lodash'
import {
@@ -165,6 +165,8 @@ async function cloneCommits(state: CloneStreamInitialState) {
// oldCommitId/newCommitId
const commitIdMap = new Map<string, string>()
const insertCommits = insertCommitsFactory({ db })
const getBatchedStreamCommits = getBatchedStreamCommitsFactory({ db })
for await (const commitsBatch of getBatchedStreamCommits(state.targetStream.id, {
trx: state.trx
})) {
@@ -237,6 +239,8 @@ async function createBranchCommitReferences(
branchIdMap: Map<string, string>
) {
const oldBranchIds = [...branchIdMap.keys()]
const getBatchedBranchCommits = getBatchedBranchCommitsFactory({ db })
for await (const branchCommits of getBatchedBranchCommits(oldBranchIds, {
trx: state.trx
})) {
@@ -8,11 +8,9 @@ const { createStream } = require('../services/streams')
const { createObject } = require('../services/objects')
const {
updateCommit,
getCommitsTotalCountByBranchName,
getCommitsByBranchName,
getCommitsByStreamId,
getCommitsTotalCountByStreamId,
getCommitsByUserId
} = require('../services/commits')
const {
@@ -34,17 +32,27 @@ const {
deleteCommitFactory,
createCommitFactory,
insertStreamCommitsFactory,
insertBranchCommitsFactory
insertBranchCommitsFactory,
getCommitBranchFactory,
switchCommitBranchFactory,
updateCommitFactory,
getStreamCommitCountFactory
} = require('@/modules/core/repositories/commits')
const {
deleteCommitAndNotifyFactory,
createCommitByBranchIdFactory,
createCommitByBranchNameFactory
createCommitByBranchNameFactory,
updateCommitAndNotifyFactory
} = require('@/modules/core/services/commit/management')
const { markCommitStreamUpdated } = require('@/modules/core/repositories/streams')
const {
markCommitStreamUpdated,
getCommitStream,
getStream
} = require('@/modules/core/repositories/streams')
const {
addCommitDeletedActivity,
addCommitCreatedActivity
addCommitCreatedActivity,
addCommitUpdatedActivity
} = require('@/modules/activitystream/services/commitActivity')
const { getObject } = require('@/modules/core/repositories/objects')
const { VersionsEmitter } = require('@/modules/core/events/versionsEmitter')
@@ -82,6 +90,20 @@ const createCommitByBranchName = createCommitByBranchNameFactory({
getBranchById: getBranchByIdFactory({ db })
})
const updateCommitAndNotify = updateCommitAndNotifyFactory({
getCommit: getCommitFactory({ db }),
getStream,
getCommitStream,
getStreamBranchByName: getStreamBranchByNameFactory({ db }),
getCommitBranch: getCommitBranchFactory({ db }),
switchCommitBranch: switchCommitBranchFactory({ db }),
updateCommit: updateCommitFactory({ db }),
addCommitUpdatedActivity,
markCommitStreamUpdated,
markCommitBranchUpdated: markCommitBranchUpdatedFactory({ db })
})
const getStreamCommitCount = getStreamCommitCountFactory({ db })
describe('Commits @core-commits', () => {
const user = {
name: 'Dimitrie Stefanescu',
@@ -255,13 +277,15 @@ describe('Commits @core-commits', () => {
})
it('Should update a commit', async () => {
const res = await updateCommit({
id: commitId1,
message: 'FIRST COMMIT YOOOOOO',
userId: user.id,
streamId: stream.id
})
expect(res).to.equal(true)
const res = await updateCommitAndNotify(
{
id: commitId1,
message: 'FIRST COMMIT YOOOOOO',
streamId: stream.id
},
user.id
)
expect(res).to.be.ok
})
it('Should delete a commit', async () => {
@@ -356,7 +380,7 @@ describe('Commits @core-commits', () => {
expect(commits.length).to.equal(10)
expect(commits2.length).to.equal(5)
const c = await getCommitsTotalCountByStreamId({ streamId })
const c = await getStreamCommitCount(streamId)
expect(c).to.equal(15)
})
@@ -31,7 +31,7 @@ import {
} from '@/modules/core/repositories/branches'
import {
createCommitFactory,
getAllBranchCommits,
getAllBranchCommitsFactory,
getSpecificBranchCommitsFactory,
insertBranchCommitsFactory,
insertStreamCommitsFactory
@@ -76,7 +76,7 @@ const crossServerSyncModule: SpeckleModule = {
getBranchLatestCommits: getBranchLatestCommitsFactory({ db }),
getStreamBranchesByName: getStreamBranchesByNameFactory({ db }),
getSpecificBranchCommits: getSpecificBranchCommitsFactory({ db }),
getAllBranchCommits
getAllBranchCommits: getAllBranchCommitsFactory({ db })
})
})
const createCommentThreadAndNotify = createCommentThreadAndNotifyFactory({
@@ -0,0 +1,38 @@
<template>
<div
class="flex flex-col gap-y-1 sm:gap-y-2 border border-outline-3 rounded-lg py-2 px-3 sm:p-4 select-none"
>
<h6
v-if="title"
class="text-body-xs sm:text-heading-sm font-medium text-foreground"
>
{{ title }}
</h6>
<p v-if="text" class="text-body-2xs sm:text-body-xs text-foreground-2 !leading-5">
{{ text }}
</p>
<FormButton
v-if="buttonText"
size="sm"
class="mt-1"
:to="to"
:target="to ? '_blank' : undefined"
@click="$emit('onClick')"
>
{{ buttonText }}
</FormButton>
</div>
</template>
<script setup lang="ts">
import FormButton from '~~/src/components/form/Button.vue'
defineEmits(['onClick'])
defineProps<{
title?: string
text?: string
to?: string
buttonText?: string
}>()
</script>
@@ -1,5 +1,6 @@
import type { Meta, StoryObj } from '@storybook/vue3'
import LayoutSidebar from '~~/src/components/layout/sidebar/Sidebar.vue'
import LayoutSidebarPromo from '~~/src/components/layout/sidebar/Promo.vue'
import LayoutSidebarMenu from '~~/src/components/layout/sidebar/menu/Menu.vue'
import LayoutSidebarMenuGroup from '~~/src/components/layout/sidebar/menu/group/Group.vue'
import LayoutSidebarMenuGroupItem from '~~/src/components/layout/sidebar/menu/group/Item.vue'
@@ -20,6 +21,7 @@ export const Dashboard: StoryObj = {
render: (args) => ({
components: {
LayoutSidebar,
LayoutSidebarPromo,
LayoutSidebarMenu,
LayoutSidebarMenuGroup,
LayoutSidebarMenuGroupItem,
@@ -68,6 +70,9 @@ export const Dashboard: StoryObj = {
</LayoutSidebarMenuGroupItem>
</LayoutSidebarMenuGroup>
</LayoutSidebarMenu>
<template #promo>
<LayoutSidebarPromo title="Example Title" text="An example piece of text" />
</template>
</LayoutSidebar>
`
})
@@ -1,7 +1,17 @@
<template>
<!-- If promo content is defined, scroll the menu items. If not, scroll the whole aside -->
<aside
class="flex flex-col justify-between h-full w-full overflow-y-auto overflow-x-hidden simple-scrollbar"
class="flex flex-col justify-between h-full w-full"
:class="$slots.promo ? '' : 'overflow-y-auto overflow-x-hidden simple-scrollbar'"
>
<slot></slot>
<div
class="flex flex-col h-full w-full"
:class="$slots.promo ? 'overflow-y-auto overflow-x-hidden simple-scrollbar' : ''"
>
<slot></slot>
</div>
<div v-if="$slots.promo" class="shrink-0 pt-2">
<slot name="promo"></slot>
</div>
</aside>
</template>
+2
View File
@@ -65,6 +65,7 @@ import InfiniteLoading from '~~/src/components/InfiniteLoading.vue'
import type { InfiniteLoaderState } from '~~/src/helpers/global/components'
import LayoutPanel from '~~/src/components/layout/Panel.vue'
import LayoutSidebar from '~~/src/components/layout/sidebar/Sidebar.vue'
import LayoutSidebarPromo from '~~/src/components/layout/sidebar/Promo.vue'
import LayoutSidebarMenu from '~~/src/components/layout/sidebar/menu/Menu.vue'
import LayoutSidebarMenuGroup from '~~/src/components/layout/sidebar/menu/group/Group.vue'
import LayoutSidebarMenuGroupItem from '~~/src/components/layout/sidebar/menu/group/Item.vue'
@@ -153,6 +154,7 @@ export {
LayoutTabsVertical,
LayoutTable,
LayoutSidebar,
LayoutSidebarPromo,
LayoutSidebarMenu,
LayoutSidebarMenuGroup,
LayoutSidebarMenuGroupItem,
+16 -9
View File
@@ -208,7 +208,7 @@ export class LegacyViewer extends Viewer {
ghost = false
): Promise<FilteringState> {
return new Promise<FilteringState>((resolve) => {
const filteringState = this.preserveSelectionFilter(() => {
const filteringState = this.preserveSelectionHighlightFilter(() => {
return this.filtering.hideObjects(
objectIds,
stateKey,
@@ -226,7 +226,7 @@ export class LegacyViewer extends Viewer {
includeDescendants = false
): Promise<FilteringState> {
return new Promise<FilteringState>((resolve) => {
const filteringState = this.preserveSelectionFilter(() => {
const filteringState = this.preserveSelectionHighlightFilter(() => {
return this.filtering.showObjects(objectIds, stateKey, includeDescendants)
})
resolve(filteringState)
@@ -240,7 +240,7 @@ export class LegacyViewer extends Viewer {
ghost = true
): Promise<FilteringState> {
return new Promise<FilteringState>((resolve) => {
const filteringState = this.preserveSelectionFilter(() => {
const filteringState = this.preserveSelectionHighlightFilter(() => {
return this.filtering.isolateObjects(
objectIds,
stateKey,
@@ -258,7 +258,7 @@ export class LegacyViewer extends Viewer {
includeDescendants = false
): Promise<FilteringState> {
return new Promise<FilteringState>((resolve) => {
const filteringState = this.preserveSelectionFilter(() => {
const filteringState = this.preserveSelectionHighlightFilter(() => {
return this.filtering.unIsolateObjects(objectIds, stateKey, includeDescendants)
})
resolve(filteringState)
@@ -278,7 +278,7 @@ export class LegacyViewer extends Viewer {
public setColorFilter(property: PropertyInfo, ghost = true): Promise<FilteringState> {
return new Promise<FilteringState>((resolve) => {
const filteringState = this.preserveSelectionFilter(() => {
const filteringState = this.preserveSelectionHighlightFilter(() => {
return this.filtering.setColorFilter(property, ghost)
})
resolve(filteringState)
@@ -287,7 +287,7 @@ export class LegacyViewer extends Viewer {
public removeColorFilter(): Promise<FilteringState> {
return new Promise<FilteringState>((resolve) => {
const filteringState = this.preserveSelectionFilter(() => {
const filteringState = this.preserveSelectionHighlightFilter(() => {
return this.filtering.removeColorFilter()
})
resolve(filteringState)
@@ -298,7 +298,7 @@ export class LegacyViewer extends Viewer {
groups: { objectIds: string[]; color: string }[]
): Promise<FilteringState> {
return new Promise<FilteringState>((resolve) => {
const filteringState = this.preserveSelectionFilter(() => {
const filteringState = this.preserveSelectionHighlightFilter(() => {
return this.filtering.setUserObjectColors(groups)
})
resolve(filteringState)
@@ -307,20 +307,25 @@ export class LegacyViewer extends Viewer {
public resetFilters(): Promise<FilteringState | null> {
return new Promise<FilteringState>((resolve) => {
const filteringState = this.preserveSelectionFilter(() => {
const filteringState = this.preserveSelectionHighlightFilter(() => {
return this.filtering.resetFilters()
})
resolve(filteringState)
})
}
private preserveSelectionFilter(
private preserveSelectionHighlightFilter(
filterFn: () => FilteringState | null
): FilteringState {
const selectedObjects = this.selection
.getSelectedObjects()
.map((obj) => obj.id) as string[]
const highLightedObjects = this.highlightExtension
.getSelectedObjects()
.map((obj) => obj.id) as string[]
if (selectedObjects.length) this.selection.clearSelection()
if (highLightedObjects.length)
this.highlightExtension.unselectObjects(highLightedObjects)
const filteringState = filterFn()
if (filteringState) {
if (!filteringState.selectedObjects)
@@ -328,6 +333,8 @@ export class LegacyViewer extends Viewer {
this.selection.selectObjects(filteringState.selectedObjects)
}
if (highLightedObjects.length)
this.highlightExtension.selectObjects(highLightedObjects)
return filteringState || this.filtering.filteringState
}