Merge branch 'main' of github.com:specklesystems/speckle-server into alessandro/web-2488-create-email-verification-mutation
This commit is contained in:
@@ -28,7 +28,6 @@
|
||||
:size="buttonSize"
|
||||
color="outline"
|
||||
class="px-4"
|
||||
:icon-left="CheckIcon"
|
||||
:disabled="loading"
|
||||
@click="onAcceptClick(token)"
|
||||
>
|
||||
@@ -52,7 +51,6 @@
|
||||
<script setup lang="ts">
|
||||
import type { MaybeNullOrUndefined, Optional } from '@speckle/shared'
|
||||
import type { AvatarUserType } from '~/lib/user/composables/avatar'
|
||||
import { CheckIcon } from '@heroicons/vue/24/solid'
|
||||
import { usePostAuthRedirect } from '~/lib/auth/composables/postAuthRedirect'
|
||||
import {
|
||||
useNavigateToLogin,
|
||||
@@ -125,7 +123,9 @@ const mainInfoBlockClasses = computed(() => {
|
||||
const avatarSize = computed(() => (props.block ? 'xxl' : 'base'))
|
||||
const buttonSize = computed(() => (props.block ? 'lg' : 'sm'))
|
||||
const isForRegisteredUser = computed(() => !!props.invite.user?.id)
|
||||
const acceptMessage = computed(() => (props.invite.workspace ? 'Join' : 'Accept'))
|
||||
const acceptMessage = computed(() =>
|
||||
props.invite.workspace ? 'Request to join' : 'Accept'
|
||||
)
|
||||
const declineMessage = computed(() => (props.invite.workspace ? 'Dismiss' : 'Decline'))
|
||||
|
||||
const onLoginSignupClick = async () => {
|
||||
|
||||
@@ -112,6 +112,8 @@ graphql(`
|
||||
parameters
|
||||
release {
|
||||
id
|
||||
versionTag
|
||||
createdAt
|
||||
inputSchema
|
||||
function {
|
||||
id
|
||||
@@ -157,7 +159,9 @@ const mixpanel = useMixpanel()
|
||||
|
||||
const jsonForm = ref<{ triggerChange: () => Promise<Optional<JsonFormsChangeEvent>> }>()
|
||||
const selectedModel = ref<CommonModelSelectorModelFragment>()
|
||||
const selectedRelease = ref<SearchAutomateFunctionReleaseItemFragment>()
|
||||
const selectedRelease = ref<SearchAutomateFunctionReleaseItemFragment | undefined>(
|
||||
props.revisionFn?.release
|
||||
)
|
||||
const inputSchema = computed(() =>
|
||||
formattedJsonFormSchema(selectedRelease.value?.inputSchema)
|
||||
)
|
||||
@@ -198,6 +202,7 @@ const resolveFirstModelValue = (items: SearchAutomateFunctionReleaseItemFragment
|
||||
functionId: functionId.value,
|
||||
functionVersionId: currentReleaseId.value
|
||||
})
|
||||
return props.revisionFn?.release
|
||||
}
|
||||
|
||||
return modelValue
|
||||
|
||||
@@ -47,6 +47,7 @@ graphql(`
|
||||
functions {
|
||||
release {
|
||||
id
|
||||
inputSchema
|
||||
function {
|
||||
id
|
||||
...AutomationsFunctionsCard_AutomateFunction
|
||||
@@ -73,12 +74,12 @@ const props = defineProps<{
|
||||
const dialogOpen = ref(false)
|
||||
const dialogFunction = ref<Optional<EditableFunctionRevision>>()
|
||||
|
||||
const functionRevisions = computed(
|
||||
const revisionFunctions = computed(
|
||||
() => props.automation.currentRevision?.functions || []
|
||||
)
|
||||
const functions = computed(
|
||||
() =>
|
||||
functionRevisions.value.map((f) => ({
|
||||
revisionFunctions.value.map((f) => ({
|
||||
fn: f.release.function,
|
||||
fnReleaseId: f.release.id
|
||||
})) || []
|
||||
@@ -86,11 +87,11 @@ const functions = computed(
|
||||
|
||||
const onEdit = (fn: EditableFunction) => {
|
||||
const fid = fn.id
|
||||
const revision = functionRevisions.value.find((f) => f.release.function.id === fid)
|
||||
const revision = revisionFunctions.value.find((f) => f.release.function.id === fid)
|
||||
|
||||
if (revision) {
|
||||
dialogOpen.value = true
|
||||
dialogFunction.value = revision
|
||||
dialogOpen.value = true
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
v-model:search="search"
|
||||
search-placeholder="Search guests..."
|
||||
:workspace="workspace"
|
||||
show-invite-button
|
||||
/>
|
||||
<LayoutTable
|
||||
class="mt-6 md:mt-8"
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
v-model:search="search"
|
||||
search-placeholder="Search pending invites..."
|
||||
:workspace="workspace"
|
||||
show-invite-button
|
||||
/>
|
||||
<LayoutTable
|
||||
class="mt-6 md:mt-8 mb-12"
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
<template>
|
||||
<div>
|
||||
<LayoutTable
|
||||
class="mt-2 mb-12"
|
||||
:columns="[
|
||||
{ id: 'name', header: 'Name', classes: 'col-span-3' },
|
||||
{ id: 'createdAt', header: 'Requested at', classes: 'col-span-3' },
|
||||
{
|
||||
id: 'actions',
|
||||
header: '',
|
||||
classes: 'col-span-3 lg:col-span-6 flex items-center justify-end'
|
||||
}
|
||||
]"
|
||||
:items="joinRequests"
|
||||
empty-message="There are no pending join requests"
|
||||
>
|
||||
<template #name="{ item }">
|
||||
<div class="flex items-center gap-2">
|
||||
<UserAvatar hide-tooltip :user="item.user" />
|
||||
<span class="truncate text-body-xs text-foreground">
|
||||
{{ item.user.name }}
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
<template #createdAt="{ item }">
|
||||
<p class="text-body-xs text-foreground">
|
||||
{{ formattedFullDate(item.createdAt) }}
|
||||
</p>
|
||||
</template>
|
||||
<template #actions="{ item }">
|
||||
<div class="flex items-center gap-x-2">
|
||||
<FormButton color="outline" size="sm" @click="onApprove(item)">
|
||||
Approve
|
||||
</FormButton>
|
||||
<FormButton color="outline" size="sm" @click="onDeny(item)">Deny</FormButton>
|
||||
</div>
|
||||
</template>
|
||||
</LayoutTable>
|
||||
|
||||
<WorkspaceJoinRequestApproveDialog
|
||||
v-model:open="showApproveJoinRequestDialog"
|
||||
:join-request="requestToApprove"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { type MaybeNullOrUndefined } from '@speckle/shared'
|
||||
import type {
|
||||
SettingsWorkspacesMembersRequestsTable_WorkspaceFragment,
|
||||
WorkspaceJoinRequestApproveDialog_WorkspaceJoinRequestFragment
|
||||
} from '~~/lib/common/generated/gql/graphql'
|
||||
import { graphql } from '~/lib/common/generated/gql'
|
||||
import { useWorkspaceJoinRequest } from '~/lib/workspaces/composables/joinRequests'
|
||||
|
||||
graphql(`
|
||||
fragment SettingsWorkspacesMembersRequestsTable_Workspace on Workspace {
|
||||
...SettingsWorkspacesMembersTableHeader_Workspace
|
||||
id
|
||||
adminWorkspacesJoinRequests(filter: $joinRequestsFilter) {
|
||||
totalCount
|
||||
items {
|
||||
...WorkspaceJoinRequestApproveDialog_WorkspaceJoinRequest
|
||||
id
|
||||
createdAt
|
||||
status
|
||||
user {
|
||||
id
|
||||
avatar
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`)
|
||||
|
||||
const props = defineProps<{
|
||||
workspace: MaybeNullOrUndefined<SettingsWorkspacesMembersRequestsTable_WorkspaceFragment>
|
||||
}>()
|
||||
|
||||
const { deny } = useWorkspaceJoinRequest()
|
||||
|
||||
const showApproveJoinRequestDialog = ref(false)
|
||||
const requestToApprove =
|
||||
ref<WorkspaceJoinRequestApproveDialog_WorkspaceJoinRequestFragment>()
|
||||
|
||||
const joinRequests = computed(
|
||||
() => props.workspace?.adminWorkspacesJoinRequests?.items || []
|
||||
)
|
||||
|
||||
const onApprove = (
|
||||
request: WorkspaceJoinRequestApproveDialog_WorkspaceJoinRequestFragment
|
||||
) => {
|
||||
requestToApprove.value = request
|
||||
showApproveJoinRequestDialog.value = true
|
||||
}
|
||||
|
||||
const onDeny = async (
|
||||
request: WorkspaceJoinRequestApproveDialog_WorkspaceJoinRequestFragment
|
||||
) => {
|
||||
if (!props.workspace?.id) return
|
||||
await deny(
|
||||
{
|
||||
workspaceId: props.workspace?.id,
|
||||
userId: request.user.id
|
||||
},
|
||||
request.id
|
||||
)
|
||||
}
|
||||
</script>
|
||||
@@ -6,6 +6,7 @@
|
||||
search-placeholder="Search members..."
|
||||
:workspace="workspace"
|
||||
show-role-filter
|
||||
show-invite-button
|
||||
/>
|
||||
<LayoutTable
|
||||
class="mt-6 md:mt-8 mb-12"
|
||||
|
||||
@@ -24,12 +24,14 @@
|
||||
:hide-items="[Roles.Workspace.Guest]"
|
||||
/>
|
||||
</div>
|
||||
<div v-if="!isWorkspaceAdmin" v-tippy="'You must be a workspace admin'">
|
||||
<FormButton :disabled="!isWorkspaceAdmin">Invite</FormButton>
|
||||
</div>
|
||||
<FormButton v-else @click="() => (isInviteDialogOpen = !isInviteDialogOpen)">
|
||||
Invite
|
||||
</FormButton>
|
||||
<template v-if="showInviteButton">
|
||||
<div v-if="!isWorkspaceAdmin" v-tippy="'You must be a workspace admin'">
|
||||
<FormButton :disabled="!isWorkspaceAdmin">Invite</FormButton>
|
||||
</div>
|
||||
<FormButton v-else @click="() => (isInviteDialogOpen = !isInviteDialogOpen)">
|
||||
Invite
|
||||
</FormButton>
|
||||
</template>
|
||||
</div>
|
||||
<InviteDialogWorkspace
|
||||
v-if="workspace"
|
||||
@@ -57,6 +59,7 @@ const props = defineProps<{
|
||||
searchPlaceholder: string
|
||||
workspace: MaybeNullOrUndefined<SettingsWorkspacesMembersTableHeader_WorkspaceFragment>
|
||||
showRoleFilter?: boolean
|
||||
showInviteButton?: boolean
|
||||
}>()
|
||||
|
||||
const search = defineModel<string>('search')
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<InviteBanner :invite="invite" @processed="processJoin">
|
||||
<InviteBanner :invite="invite" @processed="processRequest">
|
||||
<template #message>
|
||||
Your team is already using Workspaces! Collaborate in the
|
||||
Your team is already using Workspaces, request to join the
|
||||
<span class="font-medium">{{ workspace.name }}</span>
|
||||
space!
|
||||
</template>
|
||||
@@ -9,21 +9,17 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useApolloClient } from '@vue/apollo-composable'
|
||||
import { useMutation } from '@vue/apollo-composable'
|
||||
import { useSynchronizedCookie } from '~/lib/common/composables/reactiveCookie'
|
||||
import { graphql } from '~/lib/common/generated/gql'
|
||||
import {
|
||||
DashboardJoinWorkspaceDocument,
|
||||
type WorkspaceInviteDiscoverableWorkspaceBanner_LimitedWorkspaceFragment
|
||||
} from '~/lib/common/generated/gql/graphql'
|
||||
import { type WorkspaceInviteDiscoverableWorkspaceBanner_LimitedWorkspaceFragment } from '~/lib/common/generated/gql/graphql'
|
||||
import { CookieKeys } from '~/lib/common/helpers/constants'
|
||||
import {
|
||||
getCacheId,
|
||||
getFirstErrorMessage,
|
||||
modifyObjectField
|
||||
} from '~/lib/common/helpers/graphql'
|
||||
import { workspaceRoute } from '~/lib/common/helpers/route'
|
||||
import { useMixpanel } from '~~/lib/core/composables/mp'
|
||||
import { dashboardRequestToJoinWorkspaceMutation } from '~~/lib/dashboard/graphql/mutations'
|
||||
import {
|
||||
convertThrowIntoFetchResult,
|
||||
getFirstErrorMessage
|
||||
} from '~~/lib/common/helpers/graphql'
|
||||
|
||||
graphql(`
|
||||
fragment WorkspaceInviteDiscoverableWorkspaceBanner_LimitedWorkspace on LimitedWorkspace {
|
||||
@@ -33,27 +29,15 @@ graphql(`
|
||||
description
|
||||
logo
|
||||
}
|
||||
fragment WorkspaceInviteDiscoverableWorkspaceBanner_Workspace on Workspace {
|
||||
id
|
||||
name
|
||||
description
|
||||
createdAt
|
||||
updatedAt
|
||||
logo
|
||||
domainBasedMembershipProtectionEnabled
|
||||
discoverabilityEnabled
|
||||
}
|
||||
`)
|
||||
|
||||
const props = defineProps<{
|
||||
workspace: WorkspaceInviteDiscoverableWorkspaceBanner_LimitedWorkspaceFragment
|
||||
}>()
|
||||
|
||||
const { mutate: requestToJoin } = useMutation(dashboardRequestToJoinWorkspaceMutation)
|
||||
const mixpanel = useMixpanel()
|
||||
const { client: apollo } = useApolloClient()
|
||||
const { activeUser } = useActiveUser()
|
||||
const { triggerNotification } = useGlobalToast()
|
||||
const router = useRouter()
|
||||
const dismissedDiscoverableWorkspaces = useSynchronizedCookie<string[]>(
|
||||
CookieKeys.DismissedDiscoverableWorkspaces,
|
||||
{
|
||||
@@ -69,73 +53,55 @@ const invite = computed(() => ({
|
||||
}
|
||||
}))
|
||||
|
||||
const processJoin = async (accept: boolean) => {
|
||||
if (!accept) {
|
||||
const processRequest = async (accept: boolean) => {
|
||||
if (accept) {
|
||||
const result = await requestToJoin({
|
||||
input: { workspaceId: props.workspace.id }
|
||||
}).catch(convertThrowIntoFetchResult)
|
||||
|
||||
if (result?.data) {
|
||||
// Dismiss it show it doesnt show again
|
||||
dismissedDiscoverableWorkspaces.value = [
|
||||
...dismissedDiscoverableWorkspaces.value,
|
||||
props.workspace.id
|
||||
]
|
||||
|
||||
mixpanel.track('Workspace Join Request Sent', {
|
||||
workspaceId: props.workspace.id,
|
||||
location: 'discovery banner',
|
||||
// eslint-disable-next-line camelcase
|
||||
workspace_id: props.workspace.id
|
||||
})
|
||||
|
||||
triggerNotification({
|
||||
title: 'Request sent',
|
||||
description: 'Your request to join the workspace has been sent.',
|
||||
type: ToastNotificationType.Success
|
||||
})
|
||||
} else {
|
||||
const errorMessage = getFirstErrorMessage(result?.errors)
|
||||
triggerNotification({
|
||||
title: 'Failed to send request',
|
||||
description: errorMessage,
|
||||
type: ToastNotificationType.Danger
|
||||
})
|
||||
}
|
||||
} else {
|
||||
dismissedDiscoverableWorkspaces.value = [
|
||||
...dismissedDiscoverableWorkspaces.value,
|
||||
props.workspace.id
|
||||
]
|
||||
apollo.cache.evict({
|
||||
id: getCacheId('LimitedWorkspace', props.workspace.id)
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
const userId = activeUser.value?.id
|
||||
if (!userId) return
|
||||
|
||||
const result = await apollo
|
||||
.mutate({
|
||||
mutation: DashboardJoinWorkspaceDocument,
|
||||
variables: {
|
||||
input: {
|
||||
workspaceId: props.workspace.id
|
||||
}
|
||||
},
|
||||
update(cache, { data }) {
|
||||
const workspaceId = data?.workspaceMutations.join.id
|
||||
if (!workspaceId) return
|
||||
|
||||
modifyObjectField(
|
||||
cache,
|
||||
getCacheId('User', userId),
|
||||
'workspaces',
|
||||
({ variables, helpers: { evict, createUpdatedValue, ref } }) => {
|
||||
if (variables.filter?.search?.length) return evict()
|
||||
|
||||
return createUpdatedValue(({ update }) => {
|
||||
update('totalCount', (totalCount) => totalCount + 1)
|
||||
update('items', (items) => [...items, ref('Workspace', workspaceId)])
|
||||
})
|
||||
}
|
||||
)
|
||||
}
|
||||
})
|
||||
.catch(convertThrowIntoFetchResult)
|
||||
|
||||
if (result?.data) {
|
||||
apollo.cache.evict({
|
||||
id: getCacheId('LimitedWorkspace', props.workspace.id)
|
||||
})
|
||||
|
||||
triggerNotification({
|
||||
type: ToastNotificationType.Success,
|
||||
title: 'Joined workspace',
|
||||
description: 'Successfully joined workspace'
|
||||
})
|
||||
|
||||
mixpanel.track('Workspace Joined', {
|
||||
mixpanel.track('Workspace Discovery Banner Dismissed', {
|
||||
workspaceId: props.workspace.id,
|
||||
location: 'discovery banner',
|
||||
// eslint-disable-next-line camelcase
|
||||
workspace_id: props.workspace.id
|
||||
})
|
||||
|
||||
router.push(workspaceRoute(props.workspace.slug))
|
||||
} else {
|
||||
triggerNotification({
|
||||
type: ToastNotificationType.Danger,
|
||||
title: 'Failed to join workspace',
|
||||
description: getFirstErrorMessage(result?.errors)
|
||||
title: 'Discoverable workspace dismissed',
|
||||
type: ToastNotificationType.Info
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
<template>
|
||||
<LayoutDialog v-model:open="open" max-width="xs" :buttons="dialogButtons">
|
||||
<template #header>Approve join request</template>
|
||||
<p class="text-body-xs text-foreground">
|
||||
Are you sure you want to approve
|
||||
<span class="font-semibold">{{ joinRequest?.user.name }}'s</span>
|
||||
join request?
|
||||
</p>
|
||||
<div class="text-body-2xs text-foreground-2 leading-5 mt-4 mb-1">
|
||||
<p>
|
||||
Adding users may add seats to your current billing cycle. If there are available
|
||||
seats, they will be used first.
|
||||
</p>
|
||||
</div>
|
||||
</LayoutDialog>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import type { LayoutDialogButton } from '@speckle/ui-components'
|
||||
import { useWorkspaceJoinRequest } from '~/lib/workspaces/composables/joinRequests'
|
||||
import { graphql } from '~/lib/common/generated/gql'
|
||||
import type { WorkspaceJoinRequestApproveDialog_WorkspaceJoinRequestFragment } from '~/lib/common/generated/gql/graphql'
|
||||
import type { MaybeNullOrUndefined } from '@speckle/shared'
|
||||
|
||||
graphql(`
|
||||
fragment WorkspaceJoinRequestApproveDialog_WorkspaceJoinRequest on WorkspaceJoinRequest {
|
||||
id
|
||||
user {
|
||||
id
|
||||
name
|
||||
}
|
||||
workspace {
|
||||
id
|
||||
}
|
||||
}
|
||||
`)
|
||||
|
||||
const props = defineProps<{
|
||||
joinRequest: MaybeNullOrUndefined<WorkspaceJoinRequestApproveDialog_WorkspaceJoinRequestFragment>
|
||||
}>()
|
||||
|
||||
const open = defineModel<boolean>('open', { required: true })
|
||||
|
||||
const { approve } = useWorkspaceJoinRequest()
|
||||
|
||||
const dialogButtons = computed((): LayoutDialogButton[] => {
|
||||
return [
|
||||
{
|
||||
text: 'Cancel',
|
||||
props: { color: 'outline' },
|
||||
onClick: () => {
|
||||
open.value = false
|
||||
}
|
||||
},
|
||||
{
|
||||
text: 'Approve',
|
||||
onClick: async () => {
|
||||
if (props.joinRequest?.workspace.id && props.joinRequest?.user.id) {
|
||||
await approve(
|
||||
{
|
||||
workspaceId: props.joinRequest.workspace.id,
|
||||
userId: props.joinRequest.user.id
|
||||
},
|
||||
props.joinRequest.id
|
||||
)
|
||||
|
||||
open.value = false
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
})
|
||||
</script>
|
||||
@@ -61,9 +61,9 @@ const documents = {
|
||||
"\n fragment ProjectModelPageVersionsCardVersion on Version {\n id\n message\n authorUser {\n ...LimitedUserAvatar\n }\n createdAt\n previewUrl\n sourceApplication\n commentThreadCount: commentThreads(limit: 0) {\n totalCount\n }\n ...ProjectModelPageDialogDeleteVersion\n ...ProjectModelPageDialogMoveToVersion\n automationsStatus {\n ...AutomateRunsTriggerStatus_TriggeredAutomationsStatus\n }\n }\n": types.ProjectModelPageVersionsCardVersionFragmentDoc,
|
||||
"\n fragment ProjectPageProjectHeader on Project {\n id\n role\n name\n description\n visibility\n allowPublicComments\n workspace {\n id\n slug\n name\n logo\n }\n }\n": types.ProjectPageProjectHeaderFragmentDoc,
|
||||
"\n fragment ProjectPageInviteDialog_Project on Project {\n id\n workspaceId\n workspace {\n id\n defaultProjectRole\n team {\n items {\n role\n user {\n id\n name\n bio\n company\n avatar\n verified\n role\n }\n }\n }\n }\n ...ProjectPageTeamInternals_Project\n workspace {\n id\n domainBasedMembershipProtectionEnabled\n domains {\n domain\n id\n }\n }\n }\n": types.ProjectPageInviteDialog_ProjectFragmentDoc,
|
||||
"\n fragment ProjectPageAutomationFunctionSettingsDialog_AutomationRevisionFunction on AutomationRevisionFunction {\n parameters\n release {\n id\n inputSchema\n function {\n id\n }\n }\n }\n": types.ProjectPageAutomationFunctionSettingsDialog_AutomationRevisionFunctionFragmentDoc,
|
||||
"\n fragment ProjectPageAutomationFunctionSettingsDialog_AutomationRevisionFunction on AutomationRevisionFunction {\n parameters\n release {\n id\n versionTag\n createdAt\n inputSchema\n function {\n id\n }\n }\n }\n": types.ProjectPageAutomationFunctionSettingsDialog_AutomationRevisionFunctionFragmentDoc,
|
||||
"\n fragment ProjectPageAutomationFunctionSettingsDialog_AutomationRevision on AutomationRevision {\n id\n triggerDefinitions {\n ... on VersionCreatedTriggerDefinition {\n type\n model {\n id\n ...CommonModelSelectorModel\n }\n }\n }\n }\n": types.ProjectPageAutomationFunctionSettingsDialog_AutomationRevisionFragmentDoc,
|
||||
"\n fragment ProjectPageAutomationFunctions_Automation on Automation {\n id\n currentRevision {\n id\n ...ProjectPageAutomationFunctionSettingsDialog_AutomationRevision\n functions {\n release {\n id\n function {\n id\n ...AutomationsFunctionsCard_AutomateFunction\n releases(limit: 1) {\n items {\n id\n }\n }\n }\n }\n ...ProjectPageAutomationFunctionSettingsDialog_AutomationRevisionFunction\n }\n }\n }\n": types.ProjectPageAutomationFunctions_AutomationFragmentDoc,
|
||||
"\n fragment ProjectPageAutomationFunctions_Automation on Automation {\n id\n currentRevision {\n id\n ...ProjectPageAutomationFunctionSettingsDialog_AutomationRevision\n functions {\n release {\n id\n inputSchema\n function {\n id\n ...AutomationsFunctionsCard_AutomateFunction\n releases(limit: 1) {\n items {\n id\n }\n }\n }\n }\n ...ProjectPageAutomationFunctionSettingsDialog_AutomationRevisionFunction\n }\n }\n }\n": types.ProjectPageAutomationFunctions_AutomationFragmentDoc,
|
||||
"\n fragment ProjectPageAutomationHeader_Automation on Automation {\n id\n name\n enabled\n isTestAutomation\n currentRevision {\n id\n triggerDefinitions {\n ... on VersionCreatedTriggerDefinition {\n model {\n ...ProjectPageLatestItemsModelItem\n }\n }\n }\n }\n }\n": types.ProjectPageAutomationHeader_AutomationFragmentDoc,
|
||||
"\n fragment ProjectPageAutomationHeader_Project on Project {\n id\n role\n workspaceId\n ...ProjectPageModelsCardProject\n }\n": types.ProjectPageAutomationHeader_ProjectFragmentDoc,
|
||||
"\n fragment ProjectPageAutomationModels_Project on Project {\n id\n ...ProjectPageModelsCardProject\n }\n": types.ProjectPageAutomationModels_ProjectFragmentDoc,
|
||||
@@ -125,6 +125,7 @@ const documents = {
|
||||
"\n fragment SettingsWorkspacesMembersGuestsTable_Workspace on Workspace {\n id\n ...SettingsWorkspacesMembersTableHeader_Workspace\n ...SettingsSharedDeleteUserDialog_Workspace\n ...SettingsWorkspacesMembersChangeRoleDialog_Workspace\n team {\n items {\n id\n ...SettingsWorkspacesMembersGuestsTable_WorkspaceCollaborator\n }\n }\n }\n": types.SettingsWorkspacesMembersGuestsTable_WorkspaceFragmentDoc,
|
||||
"\n fragment SettingsWorkspacesMembersInvitesTable_PendingWorkspaceCollaborator on PendingWorkspaceCollaborator {\n id\n inviteId\n role\n title\n updatedAt\n user {\n id\n ...LimitedUserAvatar\n }\n invitedBy {\n id\n ...LimitedUserAvatar\n }\n }\n": types.SettingsWorkspacesMembersInvitesTable_PendingWorkspaceCollaboratorFragmentDoc,
|
||||
"\n fragment SettingsWorkspacesMembersInvitesTable_Workspace on Workspace {\n id\n ...SettingsWorkspacesMembersTableHeader_Workspace\n invitedTeam(filter: $invitesFilter) {\n ...SettingsWorkspacesMembersInvitesTable_PendingWorkspaceCollaborator\n }\n }\n": types.SettingsWorkspacesMembersInvitesTable_WorkspaceFragmentDoc,
|
||||
"\n fragment SettingsWorkspacesMembersRequestsTable_Workspace on Workspace {\n ...SettingsWorkspacesMembersTableHeader_Workspace\n id\n adminWorkspacesJoinRequests(filter: $joinRequestsFilter) {\n totalCount\n items {\n ...WorkspaceJoinRequestApproveDialog_WorkspaceJoinRequest\n id\n createdAt\n status\n user {\n id\n avatar\n name\n }\n }\n }\n }\n": types.SettingsWorkspacesMembersRequestsTable_WorkspaceFragmentDoc,
|
||||
"\n fragment SettingsWorkspacesMembersMembersTable_WorkspaceCollaborator on WorkspaceCollaborator {\n id\n role\n user {\n id\n avatar\n name\n company\n verified\n workspaceDomainPolicyCompliant\n }\n }\n": types.SettingsWorkspacesMembersMembersTable_WorkspaceCollaboratorFragmentDoc,
|
||||
"\n fragment SettingsWorkspacesMembersMembersTable_Workspace on Workspace {\n id\n name\n ...SettingsSharedDeleteUserDialog_Workspace\n ...SettingsWorkspacesMembersTableHeader_Workspace\n ...SettingsWorkspacesMembersChangeRoleDialog_Workspace\n team {\n items {\n id\n ...SettingsWorkspacesMembersMembersTable_WorkspaceCollaborator\n }\n }\n }\n": types.SettingsWorkspacesMembersMembersTable_WorkspaceFragmentDoc,
|
||||
"\n fragment SettingsWorkspacesMembersTableHeader_Workspace on Workspace {\n id\n role\n ...InviteDialogWorkspace_Workspace\n }\n": types.SettingsWorkspacesMembersTableHeader_WorkspaceFragmentDoc,
|
||||
@@ -143,7 +144,8 @@ const documents = {
|
||||
"\n fragment WorkspaceHeader_Workspace on Workspace {\n ...WorkspaceBase_Workspace\n ...WorkspaceTeam_Workspace\n ...BillingAlert_Workspace\n slug\n readOnly\n }\n": types.WorkspaceHeader_WorkspaceFragmentDoc,
|
||||
"\n fragment WorkspaceInviteBanner_PendingWorkspaceCollaborator on PendingWorkspaceCollaborator {\n id\n invitedBy {\n id\n ...LimitedUserAvatar\n }\n workspaceId\n workspaceName\n token\n user {\n id\n }\n ...UseWorkspaceInviteManager_PendingWorkspaceCollaborator\n }\n": types.WorkspaceInviteBanner_PendingWorkspaceCollaboratorFragmentDoc,
|
||||
"\n fragment WorkspaceInviteBlock_PendingWorkspaceCollaborator on PendingWorkspaceCollaborator {\n id\n workspaceId\n workspaceName\n token\n user {\n id\n name\n ...LimitedUserAvatar\n }\n title\n email\n ...UseWorkspaceInviteManager_PendingWorkspaceCollaborator\n }\n": types.WorkspaceInviteBlock_PendingWorkspaceCollaboratorFragmentDoc,
|
||||
"\n fragment WorkspaceInviteDiscoverableWorkspaceBanner_LimitedWorkspace on LimitedWorkspace {\n id\n name\n slug\n description\n logo\n }\n fragment WorkspaceInviteDiscoverableWorkspaceBanner_Workspace on Workspace {\n id\n name\n description\n createdAt\n updatedAt\n logo\n domainBasedMembershipProtectionEnabled\n discoverabilityEnabled\n }\n": types.WorkspaceInviteDiscoverableWorkspaceBanner_LimitedWorkspaceFragmentDoc,
|
||||
"\n fragment WorkspaceInviteDiscoverableWorkspaceBanner_LimitedWorkspace on LimitedWorkspace {\n id\n name\n slug\n description\n logo\n }\n": types.WorkspaceInviteDiscoverableWorkspaceBanner_LimitedWorkspaceFragmentDoc,
|
||||
"\n fragment WorkspaceJoinRequestApproveDialog_WorkspaceJoinRequest on WorkspaceJoinRequest {\n id\n user {\n id\n name\n }\n workspace {\n id\n }\n }\n": types.WorkspaceJoinRequestApproveDialog_WorkspaceJoinRequestFragmentDoc,
|
||||
"\n fragment WorkspaceSidebarAbout_Workspace on Workspace {\n ...WorkspaceDashboardAbout_Workspace\n }\n": types.WorkspaceSidebarAbout_WorkspaceFragmentDoc,
|
||||
"\n fragment WorkspaceSidebarMembers_Workspace on Workspace {\n ...WorkspaceTeam_Workspace\n }\n": types.WorkspaceSidebarMembers_WorkspaceFragmentDoc,
|
||||
"\n fragment WorkspaceSidebarSecurity_Workspace on Workspace {\n ...WorkspaceSecurity_Workspace\n }\n": types.WorkspaceSidebarSecurity_WorkspaceFragmentDoc,
|
||||
@@ -179,7 +181,7 @@ const documents = {
|
||||
"\n query ServerInfoAllScopes {\n serverInfo {\n scopes {\n name\n description\n }\n }\n }\n": types.ServerInfoAllScopesDocument,
|
||||
"\n query ProjectModelsSelectorValues($projectId: String!, $cursor: String) {\n project(id: $projectId) {\n id\n models(limit: 100, cursor: $cursor) {\n cursor\n totalCount\n items {\n ...CommonModelSelectorModel\n }\n }\n }\n }\n": types.ProjectModelsSelectorValuesDocument,
|
||||
"\n query MainServerInfoData {\n serverInfo {\n adminContact\n canonicalUrl\n company\n description\n guestModeEnabled\n inviteOnly\n name\n termsOfService\n version\n automateUrl\n }\n }\n": types.MainServerInfoDataDocument,
|
||||
"\n mutation DashboardJoinWorkspace($input: JoinWorkspaceInput!) {\n workspaceMutations {\n join(input: $input) {\n ...WorkspaceInviteDiscoverableWorkspaceBanner_Workspace\n }\n }\n }\n": types.DashboardJoinWorkspaceDocument,
|
||||
"\n mutation DashboardRequestToJoinWorkspace($input: WorkspaceRequestToJoinInput!) {\n workspaceMutations {\n requestToJoin(input: $input)\n }\n }\n": types.DashboardRequestToJoinWorkspaceDocument,
|
||||
"\n query DashboardProjectsPageQuery {\n activeUser {\n id\n projects(limit: 3) {\n items {\n ...DashboardProjectCard_Project\n }\n }\n ...ProjectsDashboardHeaderProjects_User\n }\n }\n": types.DashboardProjectsPageQueryDocument,
|
||||
"\n query DashboardProjectsPageWorkspaceQuery {\n activeUser {\n id\n ...ProjectsDashboardHeaderWorkspaces_User\n }\n }\n": types.DashboardProjectsPageWorkspaceQueryDocument,
|
||||
"\n mutation DeleteAccessToken($token: String!) {\n apiTokenRevoke(token: $token)\n }\n": types.DeleteAccessTokenDocument,
|
||||
@@ -306,8 +308,9 @@ const documents = {
|
||||
"\n query SettingsWorkspaceBilling($slug: String!) {\n workspaceBySlug(slug: $slug) {\n id\n ...SettingsWorkspacesBilling_Workspace\n }\n }\n": types.SettingsWorkspaceBillingDocument,
|
||||
"\n query SettingsWorkspaceBillingCustomerPortal($workspaceId: String!) {\n workspace(id: $workspaceId) {\n customerPortalUrl\n }\n }\n": types.SettingsWorkspaceBillingCustomerPortalDocument,
|
||||
"\n query SettingsWorkspaceRegions($slug: String!) {\n workspaceBySlug(slug: $slug) {\n id\n ...SettingsWorkspacesRegions_Workspace\n }\n serverInfo {\n ...SettingsWorkspacesRegions_ServerInfo\n }\n }\n": types.SettingsWorkspaceRegionsDocument,
|
||||
"\n query SettingsWorkspacesMembers(\n $slug: String!\n $invitesFilter: PendingWorkspaceCollaboratorsFilter\n ) {\n workspaceBySlug(slug: $slug) {\n ...SettingsWorkspacesMembers_Workspace\n ...SettingsWorkspacesMembersMembersTable_Workspace\n ...SettingsWorkspacesMembersGuestsTable_Workspace\n ...SettingsWorkspacesMembersInvitesTable_Workspace\n }\n }\n": types.SettingsWorkspacesMembersDocument,
|
||||
"\n query SettingsWorkspacesMembers(\n $slug: String!\n $invitesFilter: PendingWorkspaceCollaboratorsFilter\n $joinRequestsFilter: AdminWorkspaceJoinRequestFilter\n ) {\n workspaceBySlug(slug: $slug) {\n ...SettingsWorkspacesMembers_Workspace\n ...SettingsWorkspacesMembersMembersTable_Workspace\n ...SettingsWorkspacesMembersGuestsTable_Workspace\n ...SettingsWorkspacesMembersInvitesTable_Workspace\n ...SettingsWorkspacesMembersRequestsTable_Workspace\n }\n }\n": types.SettingsWorkspacesMembersDocument,
|
||||
"\n query SettingsWorkspacesMembersSearch($slug: String!, $filter: WorkspaceTeamFilter) {\n workspaceBySlug(slug: $slug) {\n id\n team(filter: $filter) {\n items {\n id\n ...SettingsWorkspacesMembersMembersTable_WorkspaceCollaborator\n }\n }\n }\n }\n": types.SettingsWorkspacesMembersSearchDocument,
|
||||
"\n query SettingsWorkspacesJoinRequestsSearch(\n $slug: String!\n $joinRequestsFilter: AdminWorkspaceJoinRequestFilter\n ) {\n workspaceBySlug(slug: $slug) {\n id\n ...SettingsWorkspacesMembersRequestsTable_Workspace\n }\n }\n": types.SettingsWorkspacesJoinRequestsSearchDocument,
|
||||
"\n query SettingsWorkspacesInvitesSearch(\n $slug: String!\n $invitesFilter: PendingWorkspaceCollaboratorsFilter\n ) {\n workspaceBySlug(slug: $slug) {\n ...SettingsWorkspacesMembersInvitesTable_Workspace\n }\n }\n": types.SettingsWorkspacesInvitesSearchDocument,
|
||||
"\n query SettingsUserEmailsQuery {\n activeUser {\n ...SettingsUserEmails_User\n }\n }\n": types.SettingsUserEmailsQueryDocument,
|
||||
"\n query SettingsWorkspacesProjects(\n $slug: String!\n $limit: Int!\n $cursor: String\n $filter: WorkspaceProjectsFilter\n ) {\n workspaceBySlug(slug: $slug) {\n id\n slug\n readOnly\n projects(limit: $limit, cursor: $cursor, filter: $filter) {\n cursor\n ...SettingsWorkspacesProjects_ProjectCollection\n }\n }\n }\n": types.SettingsWorkspacesProjectsDocument,
|
||||
@@ -357,6 +360,8 @@ const documents = {
|
||||
"\n mutation SetWorkspaceCreationState($input: WorkspaceCreationStateInput!) {\n workspaceMutations {\n updateCreationState(input: $input)\n }\n }\n": types.SetWorkspaceCreationStateDocument,
|
||||
"\n mutation WorkspaceUpdateDomainProtectionMutation($input: WorkspaceUpdateInput!) {\n workspaceMutations {\n update(input: $input) {\n id\n domainBasedMembershipProtectionEnabled\n }\n }\n }\n": types.WorkspaceUpdateDomainProtectionMutationDocument,
|
||||
"\n mutation WorkspaceUpdateDiscoverabilityMutation($input: WorkspaceUpdateInput!) {\n workspaceMutations {\n update(input: $input) {\n id\n discoverabilityEnabled\n }\n }\n }\n": types.WorkspaceUpdateDiscoverabilityMutationDocument,
|
||||
"\n mutation ApproveWorkspaceJoinRequest($input: ApproveWorkspaceJoinRequestInput!) {\n workspaceJoinRequestMutations {\n approve(input: $input)\n }\n }\n": types.ApproveWorkspaceJoinRequestDocument,
|
||||
"\n mutation DenyWorkspaceJoinRequest($input: DenyWorkspaceJoinRequestInput!) {\n workspaceJoinRequestMutations {\n deny(input: $input)\n }\n }\n": types.DenyWorkspaceJoinRequestDocument,
|
||||
"\n query WorkspaceAccessCheck($slug: String!) {\n workspaceBySlug(slug: $slug) {\n id\n }\n }\n": types.WorkspaceAccessCheckDocument,
|
||||
"\n query WorkspacePageQuery(\n $workspaceSlug: String!\n $invitesFilter: PendingWorkspaceCollaboratorsFilter\n $token: String\n ) {\n workspaceBySlug(slug: $workspaceSlug) {\n ...WorkspaceProjectList_Workspace\n }\n workspaceInvite(\n workspaceId: $workspaceSlug\n token: $token\n options: { useSlug: true }\n ) {\n id\n ...WorkspaceInviteBanner_PendingWorkspaceCollaborator\n ...WorkspaceInviteBlock_PendingWorkspaceCollaborator\n }\n }\n": types.WorkspacePageQueryDocument,
|
||||
"\n query WorkspaceProjectsQuery(\n $workspaceSlug: String!\n $filter: WorkspaceProjectsFilter\n $cursor: String\n ) {\n workspaceBySlug(slug: $workspaceSlug) {\n id\n projects(filter: $filter, cursor: $cursor, limit: 10) {\n ...WorkspaceProjectList_ProjectCollection\n }\n }\n }\n": types.WorkspaceProjectsQueryDocument,
|
||||
@@ -386,7 +391,7 @@ const documents = {
|
||||
"\n fragment SettingsUserEmails_User on User {\n id\n emails {\n ...SettingsUserEmailCards_UserEmail\n }\n }\n": types.SettingsUserEmails_UserFragmentDoc,
|
||||
"\n fragment SettingsWorkspacesBilling_Workspace on Workspace {\n ...BillingAlert_Workspace\n id\n role\n plan {\n name\n status\n createdAt\n paymentMethod\n }\n subscription {\n billingInterval\n currentBillingCycleEnd\n seats {\n guest\n plan\n }\n }\n team {\n items {\n id\n role\n }\n }\n }\n": types.SettingsWorkspacesBilling_WorkspaceFragmentDoc,
|
||||
"\n fragment SettingsWorkspacesGeneral_Workspace on Workspace {\n ...SettingsWorkspacesGeneralEditAvatar_Workspace\n ...SettingsWorkspaceGeneralDeleteDialog_Workspace\n ...SettingsWorkspacesGeneralEditSlugDialog_Workspace\n id\n name\n slug\n description\n logo\n role\n defaultProjectRole\n plan {\n status\n name\n }\n }\n": types.SettingsWorkspacesGeneral_WorkspaceFragmentDoc,
|
||||
"\n fragment SettingsWorkspacesMembers_Workspace on Workspace {\n id\n role\n team {\n items {\n id\n role\n }\n }\n invitedTeam(filter: $invitesFilter) {\n user {\n id\n }\n }\n }\n": types.SettingsWorkspacesMembers_WorkspaceFragmentDoc,
|
||||
"\n fragment SettingsWorkspacesMembers_Workspace on Workspace {\n id\n role\n team {\n items {\n id\n }\n }\n invitedTeam(filter: $invitesFilter) {\n user {\n id\n }\n }\n adminWorkspacesJoinRequests(filter: $joinRequestsFilter) {\n totalCount\n }\n }\n": types.SettingsWorkspacesMembers_WorkspaceFragmentDoc,
|
||||
"\n fragment SettingsWorkspacesProjects_ProjectCollection on ProjectCollection {\n totalCount\n items {\n ...SettingsSharedProjects_Project\n }\n }\n": types.SettingsWorkspacesProjects_ProjectCollectionFragmentDoc,
|
||||
"\n fragment SettingsWorkspacesRegions_Workspace on Workspace {\n id\n role\n defaultRegion {\n id\n ...SettingsWorkspacesRegionsSelect_ServerRegionItem\n }\n hasAccessToMultiRegion: hasAccessToFeature(\n featureName: workspaceDataRegionSpecificity\n )\n hasProjects: projects(limit: 0) {\n totalCount\n }\n }\n": types.SettingsWorkspacesRegions_WorkspaceFragmentDoc,
|
||||
"\n fragment SettingsWorkspacesRegions_ServerInfo on ServerInfo {\n multiRegion {\n regions {\n id\n ...SettingsWorkspacesRegionsSelect_ServerRegionItem\n }\n }\n }\n": types.SettingsWorkspacesRegions_ServerInfoFragmentDoc,
|
||||
@@ -598,7 +603,7 @@ export function graphql(source: "\n fragment ProjectPageInviteDialog_Project on
|
||||
/**
|
||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||
*/
|
||||
export function graphql(source: "\n fragment ProjectPageAutomationFunctionSettingsDialog_AutomationRevisionFunction on AutomationRevisionFunction {\n parameters\n release {\n id\n inputSchema\n function {\n id\n }\n }\n }\n"): (typeof documents)["\n fragment ProjectPageAutomationFunctionSettingsDialog_AutomationRevisionFunction on AutomationRevisionFunction {\n parameters\n release {\n id\n inputSchema\n function {\n id\n }\n }\n }\n"];
|
||||
export function graphql(source: "\n fragment ProjectPageAutomationFunctionSettingsDialog_AutomationRevisionFunction on AutomationRevisionFunction {\n parameters\n release {\n id\n versionTag\n createdAt\n inputSchema\n function {\n id\n }\n }\n }\n"): (typeof documents)["\n fragment ProjectPageAutomationFunctionSettingsDialog_AutomationRevisionFunction on AutomationRevisionFunction {\n parameters\n release {\n id\n versionTag\n createdAt\n inputSchema\n function {\n id\n }\n }\n }\n"];
|
||||
/**
|
||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||
*/
|
||||
@@ -606,7 +611,7 @@ export function graphql(source: "\n fragment ProjectPageAutomationFunctionSetti
|
||||
/**
|
||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||
*/
|
||||
export function graphql(source: "\n fragment ProjectPageAutomationFunctions_Automation on Automation {\n id\n currentRevision {\n id\n ...ProjectPageAutomationFunctionSettingsDialog_AutomationRevision\n functions {\n release {\n id\n function {\n id\n ...AutomationsFunctionsCard_AutomateFunction\n releases(limit: 1) {\n items {\n id\n }\n }\n }\n }\n ...ProjectPageAutomationFunctionSettingsDialog_AutomationRevisionFunction\n }\n }\n }\n"): (typeof documents)["\n fragment ProjectPageAutomationFunctions_Automation on Automation {\n id\n currentRevision {\n id\n ...ProjectPageAutomationFunctionSettingsDialog_AutomationRevision\n functions {\n release {\n id\n function {\n id\n ...AutomationsFunctionsCard_AutomateFunction\n releases(limit: 1) {\n items {\n id\n }\n }\n }\n }\n ...ProjectPageAutomationFunctionSettingsDialog_AutomationRevisionFunction\n }\n }\n }\n"];
|
||||
export function graphql(source: "\n fragment ProjectPageAutomationFunctions_Automation on Automation {\n id\n currentRevision {\n id\n ...ProjectPageAutomationFunctionSettingsDialog_AutomationRevision\n functions {\n release {\n id\n inputSchema\n function {\n id\n ...AutomationsFunctionsCard_AutomateFunction\n releases(limit: 1) {\n items {\n id\n }\n }\n }\n }\n ...ProjectPageAutomationFunctionSettingsDialog_AutomationRevisionFunction\n }\n }\n }\n"): (typeof documents)["\n fragment ProjectPageAutomationFunctions_Automation on Automation {\n id\n currentRevision {\n id\n ...ProjectPageAutomationFunctionSettingsDialog_AutomationRevision\n functions {\n release {\n id\n inputSchema\n function {\n id\n ...AutomationsFunctionsCard_AutomateFunction\n releases(limit: 1) {\n items {\n id\n }\n }\n }\n }\n ...ProjectPageAutomationFunctionSettingsDialog_AutomationRevisionFunction\n }\n }\n }\n"];
|
||||
/**
|
||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||
*/
|
||||
@@ -851,6 +856,10 @@ export function graphql(source: "\n fragment SettingsWorkspacesMembersInvitesTa
|
||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||
*/
|
||||
export function graphql(source: "\n fragment SettingsWorkspacesMembersInvitesTable_Workspace on Workspace {\n id\n ...SettingsWorkspacesMembersTableHeader_Workspace\n invitedTeam(filter: $invitesFilter) {\n ...SettingsWorkspacesMembersInvitesTable_PendingWorkspaceCollaborator\n }\n }\n"): (typeof documents)["\n fragment SettingsWorkspacesMembersInvitesTable_Workspace on Workspace {\n id\n ...SettingsWorkspacesMembersTableHeader_Workspace\n invitedTeam(filter: $invitesFilter) {\n ...SettingsWorkspacesMembersInvitesTable_PendingWorkspaceCollaborator\n }\n }\n"];
|
||||
/**
|
||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||
*/
|
||||
export function graphql(source: "\n fragment SettingsWorkspacesMembersRequestsTable_Workspace on Workspace {\n ...SettingsWorkspacesMembersTableHeader_Workspace\n id\n adminWorkspacesJoinRequests(filter: $joinRequestsFilter) {\n totalCount\n items {\n ...WorkspaceJoinRequestApproveDialog_WorkspaceJoinRequest\n id\n createdAt\n status\n user {\n id\n avatar\n name\n }\n }\n }\n }\n"): (typeof documents)["\n fragment SettingsWorkspacesMembersRequestsTable_Workspace on Workspace {\n ...SettingsWorkspacesMembersTableHeader_Workspace\n id\n adminWorkspacesJoinRequests(filter: $joinRequestsFilter) {\n totalCount\n items {\n ...WorkspaceJoinRequestApproveDialog_WorkspaceJoinRequest\n id\n createdAt\n status\n user {\n id\n avatar\n name\n }\n }\n }\n }\n"];
|
||||
/**
|
||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||
*/
|
||||
@@ -926,7 +935,11 @@ export function graphql(source: "\n fragment WorkspaceInviteBlock_PendingWorksp
|
||||
/**
|
||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||
*/
|
||||
export function graphql(source: "\n fragment WorkspaceInviteDiscoverableWorkspaceBanner_LimitedWorkspace on LimitedWorkspace {\n id\n name\n slug\n description\n logo\n }\n fragment WorkspaceInviteDiscoverableWorkspaceBanner_Workspace on Workspace {\n id\n name\n description\n createdAt\n updatedAt\n logo\n domainBasedMembershipProtectionEnabled\n discoverabilityEnabled\n }\n"): (typeof documents)["\n fragment WorkspaceInviteDiscoverableWorkspaceBanner_LimitedWorkspace on LimitedWorkspace {\n id\n name\n slug\n description\n logo\n }\n fragment WorkspaceInviteDiscoverableWorkspaceBanner_Workspace on Workspace {\n id\n name\n description\n createdAt\n updatedAt\n logo\n domainBasedMembershipProtectionEnabled\n discoverabilityEnabled\n }\n"];
|
||||
export function graphql(source: "\n fragment WorkspaceInviteDiscoverableWorkspaceBanner_LimitedWorkspace on LimitedWorkspace {\n id\n name\n slug\n description\n logo\n }\n"): (typeof documents)["\n fragment WorkspaceInviteDiscoverableWorkspaceBanner_LimitedWorkspace on LimitedWorkspace {\n id\n name\n slug\n description\n logo\n }\n"];
|
||||
/**
|
||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||
*/
|
||||
export function graphql(source: "\n fragment WorkspaceJoinRequestApproveDialog_WorkspaceJoinRequest on WorkspaceJoinRequest {\n id\n user {\n id\n name\n }\n workspace {\n id\n }\n }\n"): (typeof documents)["\n fragment WorkspaceJoinRequestApproveDialog_WorkspaceJoinRequest on WorkspaceJoinRequest {\n id\n user {\n id\n name\n }\n workspace {\n id\n }\n }\n"];
|
||||
/**
|
||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||
*/
|
||||
@@ -1070,7 +1083,7 @@ export function graphql(source: "\n query MainServerInfoData {\n serverInfo
|
||||
/**
|
||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||
*/
|
||||
export function graphql(source: "\n mutation DashboardJoinWorkspace($input: JoinWorkspaceInput!) {\n workspaceMutations {\n join(input: $input) {\n ...WorkspaceInviteDiscoverableWorkspaceBanner_Workspace\n }\n }\n }\n"): (typeof documents)["\n mutation DashboardJoinWorkspace($input: JoinWorkspaceInput!) {\n workspaceMutations {\n join(input: $input) {\n ...WorkspaceInviteDiscoverableWorkspaceBanner_Workspace\n }\n }\n }\n"];
|
||||
export function graphql(source: "\n mutation DashboardRequestToJoinWorkspace($input: WorkspaceRequestToJoinInput!) {\n workspaceMutations {\n requestToJoin(input: $input)\n }\n }\n"): (typeof documents)["\n mutation DashboardRequestToJoinWorkspace($input: WorkspaceRequestToJoinInput!) {\n workspaceMutations {\n requestToJoin(input: $input)\n }\n }\n"];
|
||||
/**
|
||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||
*/
|
||||
@@ -1578,11 +1591,15 @@ export function graphql(source: "\n query SettingsWorkspaceRegions($slug: Strin
|
||||
/**
|
||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||
*/
|
||||
export function graphql(source: "\n query SettingsWorkspacesMembers(\n $slug: String!\n $invitesFilter: PendingWorkspaceCollaboratorsFilter\n ) {\n workspaceBySlug(slug: $slug) {\n ...SettingsWorkspacesMembers_Workspace\n ...SettingsWorkspacesMembersMembersTable_Workspace\n ...SettingsWorkspacesMembersGuestsTable_Workspace\n ...SettingsWorkspacesMembersInvitesTable_Workspace\n }\n }\n"): (typeof documents)["\n query SettingsWorkspacesMembers(\n $slug: String!\n $invitesFilter: PendingWorkspaceCollaboratorsFilter\n ) {\n workspaceBySlug(slug: $slug) {\n ...SettingsWorkspacesMembers_Workspace\n ...SettingsWorkspacesMembersMembersTable_Workspace\n ...SettingsWorkspacesMembersGuestsTable_Workspace\n ...SettingsWorkspacesMembersInvitesTable_Workspace\n }\n }\n"];
|
||||
export function graphql(source: "\n query SettingsWorkspacesMembers(\n $slug: String!\n $invitesFilter: PendingWorkspaceCollaboratorsFilter\n $joinRequestsFilter: AdminWorkspaceJoinRequestFilter\n ) {\n workspaceBySlug(slug: $slug) {\n ...SettingsWorkspacesMembers_Workspace\n ...SettingsWorkspacesMembersMembersTable_Workspace\n ...SettingsWorkspacesMembersGuestsTable_Workspace\n ...SettingsWorkspacesMembersInvitesTable_Workspace\n ...SettingsWorkspacesMembersRequestsTable_Workspace\n }\n }\n"): (typeof documents)["\n query SettingsWorkspacesMembers(\n $slug: String!\n $invitesFilter: PendingWorkspaceCollaboratorsFilter\n $joinRequestsFilter: AdminWorkspaceJoinRequestFilter\n ) {\n workspaceBySlug(slug: $slug) {\n ...SettingsWorkspacesMembers_Workspace\n ...SettingsWorkspacesMembersMembersTable_Workspace\n ...SettingsWorkspacesMembersGuestsTable_Workspace\n ...SettingsWorkspacesMembersInvitesTable_Workspace\n ...SettingsWorkspacesMembersRequestsTable_Workspace\n }\n }\n"];
|
||||
/**
|
||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||
*/
|
||||
export function graphql(source: "\n query SettingsWorkspacesMembersSearch($slug: String!, $filter: WorkspaceTeamFilter) {\n workspaceBySlug(slug: $slug) {\n id\n team(filter: $filter) {\n items {\n id\n ...SettingsWorkspacesMembersMembersTable_WorkspaceCollaborator\n }\n }\n }\n }\n"): (typeof documents)["\n query SettingsWorkspacesMembersSearch($slug: String!, $filter: WorkspaceTeamFilter) {\n workspaceBySlug(slug: $slug) {\n id\n team(filter: $filter) {\n items {\n id\n ...SettingsWorkspacesMembersMembersTable_WorkspaceCollaborator\n }\n }\n }\n }\n"];
|
||||
/**
|
||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||
*/
|
||||
export function graphql(source: "\n query SettingsWorkspacesJoinRequestsSearch(\n $slug: String!\n $joinRequestsFilter: AdminWorkspaceJoinRequestFilter\n ) {\n workspaceBySlug(slug: $slug) {\n id\n ...SettingsWorkspacesMembersRequestsTable_Workspace\n }\n }\n"): (typeof documents)["\n query SettingsWorkspacesJoinRequestsSearch(\n $slug: String!\n $joinRequestsFilter: AdminWorkspaceJoinRequestFilter\n ) {\n workspaceBySlug(slug: $slug) {\n id\n ...SettingsWorkspacesMembersRequestsTable_Workspace\n }\n }\n"];
|
||||
/**
|
||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||
*/
|
||||
@@ -1779,6 +1796,14 @@ export function graphql(source: "\n mutation WorkspaceUpdateDomainProtectionMut
|
||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||
*/
|
||||
export function graphql(source: "\n mutation WorkspaceUpdateDiscoverabilityMutation($input: WorkspaceUpdateInput!) {\n workspaceMutations {\n update(input: $input) {\n id\n discoverabilityEnabled\n }\n }\n }\n"): (typeof documents)["\n mutation WorkspaceUpdateDiscoverabilityMutation($input: WorkspaceUpdateInput!) {\n workspaceMutations {\n update(input: $input) {\n id\n discoverabilityEnabled\n }\n }\n }\n"];
|
||||
/**
|
||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||
*/
|
||||
export function graphql(source: "\n mutation ApproveWorkspaceJoinRequest($input: ApproveWorkspaceJoinRequestInput!) {\n workspaceJoinRequestMutations {\n approve(input: $input)\n }\n }\n"): (typeof documents)["\n mutation ApproveWorkspaceJoinRequest($input: ApproveWorkspaceJoinRequestInput!) {\n workspaceJoinRequestMutations {\n approve(input: $input)\n }\n }\n"];
|
||||
/**
|
||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||
*/
|
||||
export function graphql(source: "\n mutation DenyWorkspaceJoinRequest($input: DenyWorkspaceJoinRequestInput!) {\n workspaceJoinRequestMutations {\n deny(input: $input)\n }\n }\n"): (typeof documents)["\n mutation DenyWorkspaceJoinRequest($input: DenyWorkspaceJoinRequestInput!) {\n workspaceJoinRequestMutations {\n deny(input: $input)\n }\n }\n"];
|
||||
/**
|
||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||
*/
|
||||
@@ -1898,7 +1923,7 @@ export function graphql(source: "\n fragment SettingsWorkspacesGeneral_Workspac
|
||||
/**
|
||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||
*/
|
||||
export function graphql(source: "\n fragment SettingsWorkspacesMembers_Workspace on Workspace {\n id\n role\n team {\n items {\n id\n role\n }\n }\n invitedTeam(filter: $invitesFilter) {\n user {\n id\n }\n }\n }\n"): (typeof documents)["\n fragment SettingsWorkspacesMembers_Workspace on Workspace {\n id\n role\n team {\n items {\n id\n role\n }\n }\n invitedTeam(filter: $invitesFilter) {\n user {\n id\n }\n }\n }\n"];
|
||||
export function graphql(source: "\n fragment SettingsWorkspacesMembers_Workspace on Workspace {\n id\n role\n team {\n items {\n id\n }\n }\n invitedTeam(filter: $invitesFilter) {\n user {\n id\n }\n }\n adminWorkspacesJoinRequests(filter: $joinRequestsFilter) {\n totalCount\n }\n }\n"): (typeof documents)["\n fragment SettingsWorkspacesMembers_Workspace on Workspace {\n id\n role\n team {\n items {\n id\n }\n }\n invitedTeam(filter: $invitesFilter) {\n user {\n id\n }\n }\n adminWorkspacesJoinRequests(filter: $joinRequestsFilter) {\n totalCount\n }\n }\n"];
|
||||
/**
|
||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||
*/
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1,11 +1,9 @@
|
||||
import { graphql } from '~~/lib/common/generated/gql'
|
||||
|
||||
export const dashboardJoinWorkspaceMutation = graphql(`
|
||||
mutation DashboardJoinWorkspace($input: JoinWorkspaceInput!) {
|
||||
export const dashboardRequestToJoinWorkspaceMutation = graphql(`
|
||||
mutation DashboardRequestToJoinWorkspace($input: WorkspaceRequestToJoinInput!) {
|
||||
workspaceMutations {
|
||||
join(input: $input) {
|
||||
...WorkspaceInviteDiscoverableWorkspaceBanner_Workspace
|
||||
}
|
||||
requestToJoin(input: $input)
|
||||
}
|
||||
}
|
||||
`)
|
||||
|
||||
@@ -57,12 +57,14 @@ export const settingsWorkspacesMembersQuery = graphql(`
|
||||
query SettingsWorkspacesMembers(
|
||||
$slug: String!
|
||||
$invitesFilter: PendingWorkspaceCollaboratorsFilter
|
||||
$joinRequestsFilter: AdminWorkspaceJoinRequestFilter
|
||||
) {
|
||||
workspaceBySlug(slug: $slug) {
|
||||
...SettingsWorkspacesMembers_Workspace
|
||||
...SettingsWorkspacesMembersMembersTable_Workspace
|
||||
...SettingsWorkspacesMembersGuestsTable_Workspace
|
||||
...SettingsWorkspacesMembersInvitesTable_Workspace
|
||||
...SettingsWorkspacesMembersRequestsTable_Workspace
|
||||
}
|
||||
}
|
||||
`)
|
||||
@@ -81,6 +83,18 @@ export const settingsWorkspacesMembersSearchQuery = graphql(`
|
||||
}
|
||||
`)
|
||||
|
||||
export const settingsWorkspacesJoinRequestsSearchQuery = graphql(`
|
||||
query SettingsWorkspacesJoinRequestsSearch(
|
||||
$slug: String!
|
||||
$joinRequestsFilter: AdminWorkspaceJoinRequestFilter
|
||||
) {
|
||||
workspaceBySlug(slug: $slug) {
|
||||
id
|
||||
...SettingsWorkspacesMembersRequestsTable_Workspace
|
||||
}
|
||||
}
|
||||
`)
|
||||
|
||||
export const settingsWorkspacesInvitesSearchQuery = graphql(`
|
||||
query SettingsWorkspacesInvitesSearch(
|
||||
$slug: String!
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
import { useMutation } from '@vue/apollo-composable'
|
||||
import {
|
||||
approveWorkspaceJoinRequestMutation,
|
||||
denyWorkspaceJoinRequestMutation
|
||||
} from '~/lib/workspaces/graphql/mutations'
|
||||
import type {
|
||||
ApproveWorkspaceJoinRequestInput,
|
||||
DenyWorkspaceJoinRequestInput
|
||||
} from '~~/lib/common/generated/gql/graphql'
|
||||
import { ToastNotificationType, useGlobalToast } from '~~/lib/common/composables/toast'
|
||||
import {
|
||||
convertThrowIntoFetchResult,
|
||||
getFirstErrorMessage,
|
||||
modifyObjectField,
|
||||
getCacheId
|
||||
} from '~~/lib/common/helpers/graphql'
|
||||
|
||||
export const useWorkspaceJoinRequest = () => {
|
||||
const { mutate: approveMutation } = useMutation(approveWorkspaceJoinRequestMutation)
|
||||
const { mutate: denyMutation } = useMutation(denyWorkspaceJoinRequestMutation)
|
||||
const { triggerNotification } = useGlobalToast()
|
||||
|
||||
const approve = async (
|
||||
input: ApproveWorkspaceJoinRequestInput,
|
||||
requestId: string
|
||||
) => {
|
||||
const result = await approveMutation(
|
||||
{ input },
|
||||
{
|
||||
update: (cache) => {
|
||||
cache.evict({
|
||||
id: getCacheId('WorkspaceJoinRequest', requestId)
|
||||
})
|
||||
|
||||
modifyObjectField(
|
||||
cache,
|
||||
getCacheId('Workspace', input.workspaceId),
|
||||
'adminWorkspacesJoinRequests',
|
||||
({ helpers: { createUpdatedValue } }) => {
|
||||
return createUpdatedValue(({ update }) => {
|
||||
update('totalCount', (totalCount) => totalCount - 1)
|
||||
})
|
||||
},
|
||||
{
|
||||
autoEvictFiltered: true
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
).catch(convertThrowIntoFetchResult)
|
||||
|
||||
if (result?.data) {
|
||||
triggerNotification({
|
||||
type: ToastNotificationType.Success,
|
||||
title: 'Workspace join request approved'
|
||||
})
|
||||
} else {
|
||||
const errorMessage = getFirstErrorMessage(result?.errors)
|
||||
triggerNotification({
|
||||
type: ToastNotificationType.Danger,
|
||||
title: 'Workspace join request approval failed',
|
||||
description: errorMessage
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const deny = async (input: DenyWorkspaceJoinRequestInput, requestId: string) => {
|
||||
const result = await denyMutation(
|
||||
{ input },
|
||||
{
|
||||
update: (cache) => {
|
||||
cache.evict({
|
||||
id: getCacheId('WorkspaceJoinRequest', requestId)
|
||||
})
|
||||
|
||||
modifyObjectField(
|
||||
cache,
|
||||
getCacheId('Workspace', input.workspaceId),
|
||||
'adminWorkspacesJoinRequests',
|
||||
({ helpers: { createUpdatedValue } }) => {
|
||||
return createUpdatedValue(({ update }) => {
|
||||
update('totalCount', (totalCount) => totalCount - 1)
|
||||
})
|
||||
},
|
||||
{
|
||||
autoEvictFiltered: true
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
).catch(convertThrowIntoFetchResult)
|
||||
|
||||
if (result?.data) {
|
||||
triggerNotification({
|
||||
type: ToastNotificationType.Success,
|
||||
title: 'Workspace join request denied'
|
||||
})
|
||||
} else {
|
||||
const errorMessage = getFirstErrorMessage(result?.errors)
|
||||
triggerNotification({
|
||||
type: ToastNotificationType.Danger,
|
||||
title: 'Workspace join request denial failed',
|
||||
description: errorMessage
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return { approve, deny }
|
||||
}
|
||||
@@ -105,3 +105,19 @@ export const workspaceUpdateDiscoverabilityMutation = graphql(`
|
||||
}
|
||||
}
|
||||
`)
|
||||
|
||||
export const approveWorkspaceJoinRequestMutation = graphql(`
|
||||
mutation ApproveWorkspaceJoinRequest($input: ApproveWorkspaceJoinRequestInput!) {
|
||||
workspaceJoinRequestMutations {
|
||||
approve(input: $input)
|
||||
}
|
||||
}
|
||||
`)
|
||||
|
||||
export const denyWorkspaceJoinRequestMutation = graphql(`
|
||||
mutation DenyWorkspaceJoinRequest($input: DenyWorkspaceJoinRequestInput!) {
|
||||
workspaceJoinRequestMutations {
|
||||
deny(input: $input)
|
||||
}
|
||||
}
|
||||
`)
|
||||
|
||||
@@ -24,6 +24,11 @@
|
||||
:workspace="workspace"
|
||||
:workspace-slug="slug"
|
||||
/>
|
||||
<SettingsWorkspacesMembersJoinRequestsTable
|
||||
v-if="activeItem.id === 'joinRequests'"
|
||||
:workspace="workspace"
|
||||
:workspace-slug="slug"
|
||||
/>
|
||||
</template>
|
||||
</LayoutTabsHorizontal>
|
||||
</div>
|
||||
@@ -36,6 +41,8 @@ import { useQuery } from '@vue/apollo-composable'
|
||||
import { graphql } from '~/lib/common/generated/gql'
|
||||
import { settingsWorkspacesMembersQuery } from '~/lib/settings/graphql/queries'
|
||||
import type { LayoutPageTabItem } from '~~/lib/layout/helpers/components'
|
||||
import { useOnWorkspaceUpdated } from '~/lib/workspaces/composables/management'
|
||||
import { WorkspaceJoinRequestStatus } from '~/lib/common/generated/gql/graphql'
|
||||
|
||||
graphql(`
|
||||
fragment SettingsWorkspacesMembers_Workspace on Workspace {
|
||||
@@ -44,7 +51,6 @@ graphql(`
|
||||
team {
|
||||
items {
|
||||
id
|
||||
role
|
||||
}
|
||||
}
|
||||
invitedTeam(filter: $invitesFilter) {
|
||||
@@ -52,6 +58,9 @@ graphql(`
|
||||
id
|
||||
}
|
||||
}
|
||||
adminWorkspacesJoinRequests(filter: $joinRequestsFilter) {
|
||||
totalCount
|
||||
}
|
||||
}
|
||||
`)
|
||||
|
||||
@@ -63,11 +72,14 @@ useHead({
|
||||
title: 'Settings | Workspace - Members'
|
||||
})
|
||||
|
||||
const route = useRoute()
|
||||
const slug = computed(() => (route.params.slug as string) || '')
|
||||
|
||||
const route = useRoute()
|
||||
const { result } = useQuery(settingsWorkspacesMembersQuery, () => ({
|
||||
slug: slug.value
|
||||
slug: slug.value,
|
||||
joinRequestsFilter: {
|
||||
status: WorkspaceJoinRequestStatus.Pending
|
||||
}
|
||||
}))
|
||||
|
||||
const workspace = computed(() => result.value?.workspaceBySlug)
|
||||
@@ -83,6 +95,9 @@ const guestCount = computed(
|
||||
.length
|
||||
)
|
||||
const invitedCount = computed(() => workspace.value?.invitedTeam?.length)
|
||||
const joinRequestCount = computed(
|
||||
() => workspace.value?.adminWorkspacesJoinRequests?.totalCount
|
||||
)
|
||||
const tabItems = computed<LayoutPageTabItem[]>(() => [
|
||||
{ title: 'Members', id: 'members', count: memberCount.value },
|
||||
{ title: 'Guests', id: 'guests', count: guestCount.value },
|
||||
@@ -92,8 +107,17 @@ const tabItems = computed<LayoutPageTabItem[]>(() => [
|
||||
disabled: !isAdmin.value,
|
||||
disabledMessage: 'Only workspace admins can manage invites',
|
||||
count: invitedCount.value
|
||||
},
|
||||
{
|
||||
title: 'Join requests',
|
||||
id: 'joinRequests',
|
||||
disabled: !isAdmin.value,
|
||||
disabledMessage: 'Only workspace admins can manage join requests',
|
||||
count: joinRequestCount.value
|
||||
}
|
||||
])
|
||||
|
||||
const activeTab = ref(tabItems.value[0])
|
||||
|
||||
useOnWorkspaceUpdated({ workspaceSlug: slug })
|
||||
</script>
|
||||
|
||||
@@ -123,7 +123,6 @@ import type { ShallowRef } from 'vue'
|
||||
import { useQuery, useMutation } from '@vue/apollo-composable'
|
||||
import { graphql } from '~/lib/common/generated/gql'
|
||||
import type { SettingsWorkspacesSecurityDomainRemoveDialog_WorkspaceDomainFragment } from '~/lib/common/generated/gql/graphql'
|
||||
import { getFirstErrorMessage } from '~/lib/common/helpers/graphql'
|
||||
import { settingsWorkspacesSecurityQuery } from '~/lib/settings/graphql/queries'
|
||||
import { useAddWorkspaceDomain } from '~/lib/settings/composables/management'
|
||||
import { useMixpanel } from '~/lib/core/composables/mp'
|
||||
@@ -170,7 +169,6 @@ const slug = computed(() => (route.params.slug as string) || '')
|
||||
|
||||
const route = useRoute()
|
||||
const addWorkspaceDomain = useAddWorkspaceDomain()
|
||||
const { triggerNotification } = useGlobalToast()
|
||||
const isSsoEnabled = useIsWorkspacesSsoEnabled()
|
||||
const mixpanel = useMixpanel()
|
||||
const { mutate: updateDomainProtection } = useMutation(
|
||||
@@ -226,17 +224,6 @@ const isDomainProtectionEnabled = computed({
|
||||
// eslint-disable-next-line camelcase
|
||||
workspace_id: workspace.value?.id
|
||||
})
|
||||
|
||||
triggerNotification({
|
||||
type: ToastNotificationType.Success,
|
||||
title: `Domain protection ${newVal ? 'enabled' : 'disabled'}`
|
||||
})
|
||||
} else {
|
||||
triggerNotification({
|
||||
type: ToastNotificationType.Danger,
|
||||
title: 'Failed to update',
|
||||
description: getFirstErrorMessage(result?.errors)
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -259,17 +246,6 @@ const isDomainDiscoverabilityEnabled = computed({
|
||||
// eslint-disable-next-line camelcase
|
||||
workspace_id: workspace.value?.id
|
||||
})
|
||||
|
||||
triggerNotification({
|
||||
type: ToastNotificationType.Success,
|
||||
title: `Discoverability ${newVal ? 'enabled' : 'disabled'}`
|
||||
})
|
||||
} else {
|
||||
triggerNotification({
|
||||
type: ToastNotificationType.Danger,
|
||||
title: 'Failed to update',
|
||||
description: getFirstErrorMessage(result?.errors)
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -1,8 +1,17 @@
|
||||
import prometheusClient from 'prom-client'
|
||||
import { join } from 'lodash-es'
|
||||
import type { MetricInitializer } from '@/observability/types.js'
|
||||
import Environment from '@speckle/shared/dist/commonjs/environment/index.js'
|
||||
|
||||
const { FF_WORKSPACES_MULTI_REGION_ENABLED } = Environment.getFeatureFlags()
|
||||
|
||||
export const init: MetricInitializer = (config) => {
|
||||
if (!FF_WORKSPACES_MULTI_REGION_ENABLED) {
|
||||
return async () => {
|
||||
// Do nothing
|
||||
}
|
||||
}
|
||||
|
||||
const { labelNames, namePrefix, logger } = config
|
||||
const promMetric = new prometheusClient.Gauge({
|
||||
name: join([namePrefix, 'db_replication_slot_lag'], '_'),
|
||||
|
||||
@@ -462,8 +462,8 @@ export async function init() {
|
||||
|
||||
// Log errors
|
||||
app.use(errorLoggingMiddleware)
|
||||
app.use(createRateLimiterMiddleware()) // Rate limiting by IP address for all users
|
||||
app.use(authContextMiddleware)
|
||||
app.use(createRateLimiterMiddleware())
|
||||
app.use(
|
||||
async (
|
||||
_req: express.Request,
|
||||
|
||||
@@ -51,7 +51,7 @@ const getRandomModelVersion = async (offset?: number) => {
|
||||
|
||||
const mocks: SpeckleModuleMocksConfig = FF_AUTOMATE_MODULE_ENABLED
|
||||
? {
|
||||
resolvers: ({ store }) => ({
|
||||
resolvers: ({ store, helpers: { getMockRef, resolveAndCache } }) => ({
|
||||
AutomationRevisionTriggerDefinition: {
|
||||
__resolveType: () => 'VersionCreatedTriggerDefinition'
|
||||
},
|
||||
@@ -246,14 +246,10 @@ const mocks: SpeckleModuleMocksConfig = FF_AUTOMATE_MODULE_ENABLED
|
||||
}
|
||||
},
|
||||
AutomateFunction: {
|
||||
// creator: async (_parent, args, ctx) => {
|
||||
// const rand = faker.datatype.boolean()
|
||||
// const activeUser = ctx.userId
|
||||
// ? await ctx.loaders.users.getUser.load(ctx.userId)
|
||||
// : null
|
||||
|
||||
// return rand ? (store.get('LimitedUser') as any) : activeUser
|
||||
// }
|
||||
creator: resolveAndCache((parent, args, ctx) => {
|
||||
const rand = faker.datatype.boolean()
|
||||
return getMockRef('LimitedUser', { id: !rand ? ctx.userId : undefined })
|
||||
}),
|
||||
releases: () => store.get('AutomateFunctionReleaseCollection') as any
|
||||
},
|
||||
AutomateFunctionRelease: {
|
||||
@@ -338,30 +334,39 @@ const mocks: SpeckleModuleMocksConfig = FF_AUTOMATE_MODULE_ENABLED
|
||||
},
|
||||
commitId: () => '0c259d384a4df3cce3f24667560e5124e68f202f',
|
||||
inputSchema: () => {
|
||||
// random fro 1 to 3
|
||||
const rand = faker.number.int({ min: 1, max: 3 })
|
||||
switch (rand) {
|
||||
case 1:
|
||||
return {
|
||||
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
||||
$id: 'https://example.com/product.schema.json',
|
||||
title: 'Product',
|
||||
description: "A product from Acme's catalog",
|
||||
type: 'object',
|
||||
properties: {
|
||||
name: {
|
||||
desciption: 'Random name',
|
||||
type: 'string'
|
||||
},
|
||||
productId: {
|
||||
description: 'The unique identifier for a product',
|
||||
type: 'integer'
|
||||
}
|
||||
},
|
||||
required: ['productId']
|
||||
return {
|
||||
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
||||
$id: 'https://example.com/product.schema.json',
|
||||
title: 'Product',
|
||||
description: "A product from Acme's catalog",
|
||||
type: 'object',
|
||||
properties: {
|
||||
Boolean: {
|
||||
description: faker.lorem.sentence(5),
|
||||
type: 'boolean'
|
||||
},
|
||||
'Required Boolean': {
|
||||
description: faker.lorem.sentence(5),
|
||||
type: 'boolean'
|
||||
},
|
||||
Integer: {
|
||||
description: faker.lorem.sentence(5),
|
||||
type: 'integer'
|
||||
},
|
||||
'Required Integer': {
|
||||
description: faker.lorem.sentence(5),
|
||||
type: 'integer'
|
||||
},
|
||||
String: {
|
||||
description: faker.lorem.sentence(5),
|
||||
type: 'string'
|
||||
},
|
||||
'Required String': {
|
||||
description: faker.lorem.sentence(5),
|
||||
type: 'string'
|
||||
}
|
||||
default:
|
||||
return null
|
||||
},
|
||||
required: ['Required Boolean', 'Required Integer', 'Required String']
|
||||
}
|
||||
}
|
||||
}),
|
||||
|
||||
@@ -107,37 +107,44 @@ export const loggingPluginFactory: (deps: {
|
||||
apollo_query_duration_ms: Date.now() - apolloRequestStart
|
||||
})
|
||||
|
||||
for (const err of ctx.errors) {
|
||||
const operationName = ctx.request.operationName || null
|
||||
const query = ctx.request.query
|
||||
const variables = redactSensitiveVariables(ctx.request.variables)
|
||||
const operationName = ctx.request.operationName || null
|
||||
const query = ctx.request.query
|
||||
const variables = redactSensitiveVariables(ctx.request.variables)
|
||||
|
||||
const reqCtx = getRequestContext()
|
||||
if (reqCtx) {
|
||||
logger = logger.child({
|
||||
dbMetrics: reqCtx.dbMetrics
|
||||
})
|
||||
}
|
||||
const reqCtx = getRequestContext()
|
||||
if (reqCtx) {
|
||||
logger = logger.child({
|
||||
dbMetrics: reqCtx.dbMetrics
|
||||
})
|
||||
}
|
||||
|
||||
if (err.path) {
|
||||
logger = logger.child({
|
||||
'query-path': err.path.join(' > '),
|
||||
graphql_operation_name: operationName,
|
||||
graphql_query: query,
|
||||
graphql_variables: variables
|
||||
})
|
||||
}
|
||||
if (shouldLogAsInfoLevel(err)) {
|
||||
logger.info(
|
||||
{ err },
|
||||
'{graphql_operation_title} failed after {apollo_query_duration_ms} ms'
|
||||
)
|
||||
} else {
|
||||
logger.error(
|
||||
err,
|
||||
'{graphql_operation_title} failed after {apollo_query_duration_ms} ms'
|
||||
)
|
||||
}
|
||||
const importantError = ctx.errors.find((err) => !shouldLogAsInfoLevel(err))
|
||||
const firstError = ctx.errors[0]
|
||||
const loggableError = importantError || firstError
|
||||
|
||||
logger = logger.child({
|
||||
error_count: loggableError ? ctx.errors.length : undefined,
|
||||
first_error: loggableError
|
||||
? {
|
||||
message: loggableError.message,
|
||||
path: loggableError.path?.join(' > ')
|
||||
}
|
||||
: {},
|
||||
graphql_operation_name: operationName,
|
||||
graphql_query: query,
|
||||
graphql_variables: variables
|
||||
})
|
||||
|
||||
if (!importantError) {
|
||||
logger.info(
|
||||
{ err: firstError },
|
||||
'{graphql_operation_title} failed after {apollo_query_duration_ms} ms'
|
||||
)
|
||||
} else {
|
||||
logger.error(
|
||||
{ err: importantError },
|
||||
'{graphql_operation_title} failed after {apollo_query_duration_ms} ms'
|
||||
)
|
||||
}
|
||||
},
|
||||
willSendResponse: async (ctx) => {
|
||||
|
||||
@@ -306,8 +306,20 @@ export = {
|
||||
}
|
||||
},
|
||||
Branch: {
|
||||
async commits(parent, args) {
|
||||
async commits(parent, args, ctx) {
|
||||
const projectDB = await getProjectDbClient({ projectId: parent.streamId })
|
||||
|
||||
// If limit=0 & no filter, short-cut full execution and use data loader
|
||||
if (args.limit === 0) {
|
||||
return {
|
||||
totalCount: await ctx.loaders
|
||||
.forRegion({ db: projectDB })
|
||||
.branches.getCommitCount.load(parent.id),
|
||||
items: [],
|
||||
cursor: null
|
||||
}
|
||||
}
|
||||
|
||||
const getPaginatedBranchCommits = getPaginatedBranchCommitsFactory({
|
||||
getSpecificBranchCommits: getSpecificBranchCommitsFactory({ db: projectDB }),
|
||||
getPaginatedBranchCommitsItems: getPaginatedBranchCommitsItemsFactory({
|
||||
|
||||
@@ -206,6 +206,8 @@ export = {
|
||||
},
|
||||
|
||||
async streams(_, args, ctx) {
|
||||
const countOnly = args.limit === 0 && !args.query
|
||||
|
||||
const [totalCount, visibleCount, { cursor, streams }] = await Promise.all([
|
||||
getUserStreamsCount({
|
||||
userId: ctx.userId!,
|
||||
@@ -220,15 +222,17 @@ export = {
|
||||
streamIdWhitelist: toProjectIdWhitelist(ctx.resourceAccessRules),
|
||||
onlyWithActiveSsoSession: true
|
||||
}),
|
||||
getUserStreams({
|
||||
userId: ctx.userId!,
|
||||
limit: args.limit,
|
||||
cursor: args.cursor || undefined,
|
||||
searchQuery: args.query || undefined,
|
||||
forOtherUser: false,
|
||||
streamIdWhitelist: toProjectIdWhitelist(ctx.resourceAccessRules),
|
||||
onlyWithActiveSsoSession: true
|
||||
})
|
||||
!countOnly
|
||||
? getUserStreams({
|
||||
userId: ctx.userId!,
|
||||
limit: args.limit,
|
||||
cursor: args.cursor || undefined,
|
||||
searchQuery: args.query || undefined,
|
||||
forOtherUser: false,
|
||||
streamIdWhitelist: toProjectIdWhitelist(ctx.resourceAccessRules),
|
||||
onlyWithActiveSsoSession: true
|
||||
})
|
||||
: { cursor: null, streams: [] }
|
||||
])
|
||||
|
||||
return {
|
||||
|
||||
@@ -17,6 +17,7 @@ import { RateLimitError } from '@/modules/core/errors/ratelimit'
|
||||
import { rateLimiterLogger } from '@/logging/logging'
|
||||
import { createRedisClient } from '@/modules/shared/redis/redis'
|
||||
import { getRequestPath } from '@/modules/core/helpers/server'
|
||||
import { getTokenFromRequest } from '@/modules/shared/middleware'
|
||||
|
||||
export interface RateLimitResult {
|
||||
isWithinLimits: boolean
|
||||
@@ -297,7 +298,10 @@ export const getActionForPath = (path: string, verb: string): RateLimitAction =>
|
||||
}
|
||||
|
||||
export const getSourceFromRequest = (req: express.Request): string => {
|
||||
let source: string | null = req?.context?.userId || getIpFromRequest(req)
|
||||
let source: string | null =
|
||||
req?.context?.userId ||
|
||||
getTokenFromRequest(req)?.substring(10) || // token ID
|
||||
getIpFromRequest(req)
|
||||
|
||||
if (!source) source = 'unknown'
|
||||
return source
|
||||
|
||||
-1
@@ -5,7 +5,6 @@ const TABLE_NAME = 'email_verifications'
|
||||
export async function up(knex: Knex): Promise<void> {
|
||||
await knex.schema.alterTable(TABLE_NAME, (table) => {
|
||||
table.string('code')
|
||||
table.unique('email')
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
import { Knex } from 'knex'
|
||||
|
||||
const TABLE_NAME = 'email_verifications'
|
||||
|
||||
export async function up(knex: Knex): Promise<void> {
|
||||
await knex.raw(
|
||||
`ALTER TABLE ${TABLE_NAME} DROP CONSTRAINT IF EXISTS email_verifications_email_unique`
|
||||
)
|
||||
}
|
||||
|
||||
export async function down(): Promise<void> {}
|
||||
@@ -6,6 +6,7 @@ import { getUserFactory } from '@/modules/core/repositories/users'
|
||||
import { renderEmail } from '@/modules/emails/services/emailRendering'
|
||||
import { sendEmail } from '@/modules/emails/services/sending'
|
||||
import { commandFactory } from '@/modules/shared/command'
|
||||
import { getEventBus } from '@/modules/shared/services/eventBus'
|
||||
import { getPaginatedItemsFactory } from '@/modules/shared/services/paginatedItems'
|
||||
import {
|
||||
ApproveWorkspaceJoinRequest,
|
||||
@@ -30,6 +31,8 @@ import {
|
||||
import { WorkspaceJoinRequestStatus } from '@/modules/workspacesCore/domain/types'
|
||||
import { WorkspaceJoinRequestGraphQLReturn } from '@/modules/workspacesCore/helpers/graphTypes'
|
||||
|
||||
const eventBus = getEventBus()
|
||||
|
||||
export default {
|
||||
Workspace: {
|
||||
adminWorkspacesJoinRequests: async (parent, args, ctx) => {
|
||||
@@ -78,7 +81,8 @@ export default {
|
||||
approve: async (_parent, args) => {
|
||||
const approveWorkspaceJoinRequest = commandFactory<ApproveWorkspaceJoinRequest>({
|
||||
db,
|
||||
operationFactory: ({ db }) => {
|
||||
eventBus,
|
||||
operationFactory: ({ db, emit }) => {
|
||||
const updateWorkspaceJoinRequestStatus =
|
||||
updateWorkspaceJoinRequestStatusFactory({
|
||||
db
|
||||
@@ -98,7 +102,8 @@ export default {
|
||||
getWorkspaceJoinRequest: getWorkspaceJoinRequestFactory({
|
||||
db
|
||||
}),
|
||||
upsertWorkspaceRole: upsertWorkspaceRoleFactory({ db })
|
||||
upsertWorkspaceRole: upsertWorkspaceRoleFactory({ db }),
|
||||
emit
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
@@ -1423,12 +1423,12 @@ export = FF_WORKSPACES_MODULE_ENABLED
|
||||
context.resourceAccessRules
|
||||
)
|
||||
|
||||
const userId = parent.id
|
||||
|
||||
return await getWorkspaceRoleForUserFactory({ db })({
|
||||
userId,
|
||||
const role = await getWorkspaceRoleForUserFactory({ db })({
|
||||
userId: parent.id,
|
||||
workspaceId
|
||||
})
|
||||
|
||||
return role?.role ?? null
|
||||
}
|
||||
},
|
||||
ServerInfo: {
|
||||
|
||||
@@ -21,6 +21,8 @@ import {
|
||||
import { Roles } from '@speckle/shared'
|
||||
import { FindEmailsByUserId } from '@/modules/core/domain/userEmails/operations'
|
||||
import { userEmailsCompliantWithWorkspaceDomains } from '@/modules/workspaces/domain/logic'
|
||||
import { EventBus } from '@/modules/shared/services/eventBus'
|
||||
import { WorkspaceEvents } from '@/modules/workspacesCore/domain/events'
|
||||
|
||||
export const dismissWorkspaceJoinRequestFactory =
|
||||
({
|
||||
@@ -104,7 +106,8 @@ export const approveWorkspaceJoinRequestFactory =
|
||||
getUserById,
|
||||
getWorkspace,
|
||||
getWorkspaceJoinRequest,
|
||||
upsertWorkspaceRole
|
||||
upsertWorkspaceRole,
|
||||
emit
|
||||
}: {
|
||||
updateWorkspaceJoinRequestStatus: UpdateWorkspaceJoinRequestStatus
|
||||
sendWorkspaceJoinRequestApprovedEmail: SendWorkspaceJoinRequestApprovedEmail
|
||||
@@ -112,6 +115,7 @@ export const approveWorkspaceJoinRequestFactory =
|
||||
getWorkspace: GetWorkspace
|
||||
getWorkspaceJoinRequest: GetWorkspaceJoinRequest
|
||||
upsertWorkspaceRole: UpsertWorkspaceRole
|
||||
emit: EventBus['emit']
|
||||
}) =>
|
||||
async ({ userId, workspaceId }: { userId: string; workspaceId: string }) => {
|
||||
const requester = await getUserById(userId)
|
||||
@@ -142,6 +146,8 @@ export const approveWorkspaceJoinRequestFactory =
|
||||
const role = Roles.Workspace.Member
|
||||
await upsertWorkspaceRole({ userId, workspaceId, role, createdAt: new Date() })
|
||||
|
||||
await emit({ eventName: WorkspaceEvents.Updated, payload: { workspace } })
|
||||
|
||||
await sendWorkspaceJoinRequestApprovedEmail({
|
||||
workspace,
|
||||
requester
|
||||
|
||||
@@ -245,7 +245,8 @@ const { FF_WORKSPACES_MODULE_ENABLED } = getFeatureFlags()
|
||||
getUserById: async () => null,
|
||||
getWorkspace: async () => null,
|
||||
getWorkspaceJoinRequest: async () => undefined,
|
||||
upsertWorkspaceRole: async () => Promise.resolve()
|
||||
upsertWorkspaceRole: async () => Promise.resolve(),
|
||||
emit: async () => Promise.resolve()
|
||||
})({ workspaceId: createRandomString(), userId: createRandomString() })
|
||||
)
|
||||
|
||||
@@ -261,7 +262,8 @@ const { FF_WORKSPACES_MODULE_ENABLED } = getFeatureFlags()
|
||||
getUserById: async () => user as unknown as UserWithOptionalRole,
|
||||
getWorkspace: async () => null,
|
||||
getWorkspaceJoinRequest: async () => undefined,
|
||||
upsertWorkspaceRole: async () => Promise.resolve()
|
||||
upsertWorkspaceRole: async () => Promise.resolve(),
|
||||
emit: async () => Promise.resolve()
|
||||
})({ workspaceId: createRandomString(), userId: createRandomString() })
|
||||
)
|
||||
|
||||
@@ -285,7 +287,8 @@ const { FF_WORKSPACES_MODULE_ENABLED } = getFeatureFlags()
|
||||
getUserById: async () => user as unknown as UserWithOptionalRole,
|
||||
getWorkspace: async () => workspace as unknown as Workspace,
|
||||
getWorkspaceJoinRequest: async () => undefined,
|
||||
upsertWorkspaceRole: async () => Promise.resolve()
|
||||
upsertWorkspaceRole: async () => Promise.resolve(),
|
||||
emit: async () => Promise.resolve()
|
||||
})({ workspaceId: createRandomString(), userId: createRandomString() })
|
||||
)
|
||||
|
||||
@@ -343,7 +346,8 @@ const { FF_WORKSPACES_MODULE_ENABLED } = getFeatureFlags()
|
||||
getUserById: async () => user as unknown as UserWithOptionalRole,
|
||||
getWorkspace: async () => workspace as unknown as Workspace,
|
||||
getWorkspaceJoinRequest: async () => request,
|
||||
upsertWorkspaceRole
|
||||
upsertWorkspaceRole,
|
||||
emit: async () => Promise.resolve()
|
||||
})({ workspaceId: workspace.id, userId: user.id })
|
||||
).to.equal(true)
|
||||
|
||||
|
||||
@@ -113,7 +113,7 @@ const getStream = () => {
|
||||
// prettier-ignore
|
||||
// 'https://app.speckle.systems/streams/da9e320dad/commits/5388ef24b8?c=%5B-7.66134,10.82932,6.41935,-0.07739,-13.88552,1.8697,0,1%5D'
|
||||
// Revit sample house (good for bim-like stuff with many display meshes)
|
||||
'https://app.speckle.systems/streams/da9e320dad/commits/5388ef24b8'
|
||||
// 'https://app.speckle.systems/streams/da9e320dad/commits/5388ef24b8'
|
||||
// 'https://latest.speckle.systems/streams/c1faab5c62/commits/ab1a1ab2b6'
|
||||
// 'https://app.speckle.systems/streams/da9e320dad/commits/5388ef24b8'
|
||||
// 'https://latest.speckle.systems/streams/58b5648c4d/commits/60371ecb2d'
|
||||
@@ -470,6 +470,9 @@ const getStream = () => {
|
||||
|
||||
// 'https://latest.speckle.systems/projects/3fe1880c36/models/65bb4287a8'
|
||||
// 'https://latest.speckle.systems/projects/db06488e1c/models/21f3930771'
|
||||
|
||||
// FAR OFF
|
||||
'https://app.speckle.systems/projects/bdd828221e/models/eb99326dc3'
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -127,6 +127,29 @@ export const speckleBasicVert = /* glsl */ `
|
||||
{
|
||||
return position + 2.0 * cross(quat.xyz, cross(quat.xyz, position) + quat.w * position);
|
||||
}
|
||||
|
||||
highp vec3 rotate_vertex_position_delta(highp vec4 v0, highp vec4 v1, highp vec4 quat)
|
||||
{
|
||||
/** !!! WORKAROUND FOR Intel IrisXe CARDS !!! */
|
||||
/** The code below will not produce correct results in intel IrisXE integrated GPUs.
|
||||
* The geometry will turn mangled, albeit stable
|
||||
* I can't know for sure what is going on, but rotating the difference seems to
|
||||
* force the result into a lower precision?
|
||||
*/
|
||||
// highp vec4 position = v0 - v1;
|
||||
// return position.xyz + 2.0 * cross(quat.xyz, cross(quat.xyz, position.xyz) + quat.w * position.xyz);
|
||||
|
||||
/** Subtracting the rotated vectors works. */
|
||||
return rotate_vertex_position(v0.xyz, quat) - rotate_vertex_position(v1.xyz, quat);
|
||||
|
||||
/** An alternate workaround is
|
||||
* highp vec3 position = (v0.xyz * (1. + 1e-7)) - (v1.xyz * (1. _ 1e-7));
|
||||
return position + 2.0 * cross(quat.xyz, cross(quat.xyz, position) + quat.w * position);
|
||||
|
||||
However I'm not such a fan of the (1. + 1e-7) part
|
||||
*/
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#if defined(BILLBOARD) || defined(BILLBOARD_FIXED)
|
||||
@@ -165,7 +188,7 @@ void main() {
|
||||
vec4 rteLocalPosition = computeRelativePositionSeparate(position_lowT.xyz, position_highT.xyz, uViewer_low, uViewer_high);
|
||||
#ifdef TRANSFORM_STORAGE
|
||||
vec4 rtePivot = computeRelativePositionSeparate(tPivotLow.xyz, tPivotHigh.xyz, uViewer_low, uViewer_high);
|
||||
rteLocalPosition.xyz = rotate_vertex_position((rteLocalPosition - rtePivot).xyz, tQuaternion) * tScale.xyz + rtePivot.xyz + tTranslation.xyz;
|
||||
rteLocalPosition.xyz = rotate_vertex_position_delta(rteLocalPosition, rtePivot, tQuaternion) * tScale.xyz + rtePivot.xyz + tTranslation.xyz;
|
||||
#endif
|
||||
#ifdef USE_INSTANCING
|
||||
vec4 instancePivot = computeRelativePositionSeparate(ZERO3, ZERO3, uViewer_low, uViewer_high);
|
||||
|
||||
@@ -83,6 +83,30 @@ varying vec2 vHighPrecisionZW;
|
||||
{
|
||||
return position + 2.0 * cross(quat.xyz, cross(quat.xyz, position) + quat.w * position);
|
||||
}
|
||||
|
||||
highp vec3 rotate_vertex_position_delta(highp vec4 v0, highp vec4 v1, highp vec4 quat)
|
||||
{
|
||||
/** !!! WORKAROUND FOR Intel IrisXe CARDS !!! */
|
||||
/** The code below will not produce correct results in intel IrisXE integrated GPUs.
|
||||
* The geometry will turn mangled, albeit stable
|
||||
* I can't know for sure what is going on, but rotating the difference seems to
|
||||
* force the result into a lower precision?
|
||||
*/
|
||||
// highp vec4 position = v0 - v1;
|
||||
// return position.xyz + 2.0 * cross(quat.xyz, cross(quat.xyz, position.xyz) + quat.w * position.xyz);
|
||||
|
||||
/** Subtracting the rotated vectors works. */
|
||||
return rotate_vertex_position(v0.xyz, quat) - rotate_vertex_position(v1.xyz, quat);
|
||||
|
||||
/** An alternate workaround is
|
||||
* highp vec3 position = (v0.xyz * (1. + 1e-7)) - (v1.xyz * (1. _ 1e-7));
|
||||
return position + 2.0 * cross(quat.xyz, cross(quat.xyz, position) + quat.w * position);
|
||||
|
||||
However I'm not such a fan of the (1. + 1e-7) part
|
||||
*/
|
||||
}
|
||||
|
||||
|
||||
#endif
|
||||
|
||||
#ifdef USE_RTE
|
||||
@@ -161,7 +185,7 @@ void main() {
|
||||
vec4 rteLocalPosition = computeRelativePositionSeparate(position_lowT.xyz, position_highT.xyz, uViewer_low, uViewer_high);
|
||||
#ifdef TRANSFORM_STORAGE
|
||||
vec4 rtePivot = computeRelativePositionSeparate(tPivotLow.xyz, tPivotHigh.xyz, uViewer_low, uViewer_high);
|
||||
rteLocalPosition.xyz = rotate_vertex_position((rteLocalPosition - rtePivot).xyz, tQuaternion) * tScale.xyz + rtePivot.xyz + tTranslation.xyz;
|
||||
rteLocalPosition.xyz = rotate_vertex_position_delta(rteLocalPosition, rtePivot, tQuaternion) * tScale.xyz + rtePivot.xyz + tTranslation.xyz;
|
||||
#endif
|
||||
#ifdef USE_INSTANCING
|
||||
vec4 instancePivot = computeRelativePositionSeparate(ZERO3, ZERO3, uViewer_low, uViewer_high);
|
||||
|
||||
@@ -79,6 +79,29 @@ uniform float displacement;
|
||||
{
|
||||
return position + 2.0 * cross(quat.xyz, cross(quat.xyz, position) + quat.w * position);
|
||||
}
|
||||
|
||||
highp vec3 rotate_vertex_position_delta(highp vec4 v0, highp vec4 v1, highp vec4 quat)
|
||||
{
|
||||
/** !!! WORKAROUND FOR Intel IrisXe CARDS !!! */
|
||||
/** The code below will not produce correct results in intel IrisXE integrated GPUs.
|
||||
* The geometry will turn mangled, albeit stable
|
||||
* I can't know for sure what is going on, but rotating the difference seems to
|
||||
* force the result into a lower precision?
|
||||
*/
|
||||
// highp vec4 position = v0 - v1;
|
||||
// return position.xyz + 2.0 * cross(quat.xyz, cross(quat.xyz, position.xyz) + quat.w * position.xyz);
|
||||
|
||||
/** Subtracting the rotated vectors works. */
|
||||
return rotate_vertex_position(v0.xyz, quat) - rotate_vertex_position(v1.xyz, quat);
|
||||
|
||||
/** An alternate workaround is
|
||||
* highp vec3 position = (v0.xyz * (1. + 1e-7)) - (v1.xyz * (1. _ 1e-7));
|
||||
return position + 2.0 * cross(quat.xyz, cross(quat.xyz, position) + quat.w * position);
|
||||
|
||||
However I'm not such a fan of the (1. + 1e-7) part
|
||||
*/
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#ifdef USE_RTE
|
||||
@@ -159,7 +182,7 @@ void main() {
|
||||
vec4 rteLocalPosition = computeRelativePositionSeparate(position_lowT.xyz, position_highT.xyz, uViewer_low, uViewer_high);
|
||||
#ifdef TRANSFORM_STORAGE
|
||||
vec4 rtePivot = computeRelativePositionSeparate(tPivotLow.xyz, tPivotHigh.xyz, uViewer_low, uViewer_high);
|
||||
rteLocalPosition.xyz = rotate_vertex_position((rteLocalPosition - rtePivot).xyz, tQuaternion) * tScale.xyz + rtePivot.xyz + tTranslation.xyz;
|
||||
rteLocalPosition.xyz = rotate_vertex_position_delta(rteLocalPosition, rtePivot, tQuaternion) * tScale.xyz + rtePivot.xyz + tTranslation.xyz;
|
||||
#endif
|
||||
#ifdef USE_INSTANCING
|
||||
vec4 instancePivot = computeRelativePositionSeparate(ZERO3, ZERO3, uViewer_low, uViewer_high);
|
||||
|
||||
@@ -77,6 +77,28 @@ export const speckleGhostVert = /* glsl */ `
|
||||
{
|
||||
return position + 2.0 * cross(quat.xyz, cross(quat.xyz, position) + quat.w * position);
|
||||
}
|
||||
|
||||
highp vec3 rotate_vertex_position_delta(highp vec4 v0, highp vec4 v1, highp vec4 quat)
|
||||
{
|
||||
/** !!! WORKAROUND FOR Intel IrisXe CARDS !!! */
|
||||
/** The code below will not produce correct results in intel IrisXE integrated GPUs.
|
||||
* The geometry will turn mangled, albeit stable
|
||||
* I can't know for sure what is going on, but rotating the difference seems to
|
||||
* force the result into a lower precision?
|
||||
*/
|
||||
// highp vec4 position = v0 - v1;
|
||||
// return position.xyz + 2.0 * cross(quat.xyz, cross(quat.xyz, position.xyz) + quat.w * position.xyz);
|
||||
|
||||
/** Subtracting the rotated vectors works. */
|
||||
return rotate_vertex_position(v0.xyz, quat) - rotate_vertex_position(v1.xyz, quat);
|
||||
|
||||
/** An alternate workaround is
|
||||
* highp vec3 position = (v0.xyz * (1. + 1e-7)) - (v1.xyz * (1. _ 1e-7));
|
||||
return position + 2.0 * cross(quat.xyz, cross(quat.xyz, position) + quat.w * position);
|
||||
|
||||
However I'm not such a fan of the (1. + 1e-7) part
|
||||
*/
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_RTE
|
||||
@@ -157,7 +179,7 @@ void main() {
|
||||
vec4 rteLocalPosition = computeRelativePositionSeparate(position_lowT.xyz, position_highT.xyz, uViewer_low, uViewer_high);
|
||||
#ifdef TRANSFORM_STORAGE
|
||||
vec4 rtePivot = computeRelativePositionSeparate(tPivotLow.xyz, tPivotHigh.xyz, uViewer_low, uViewer_high);
|
||||
rteLocalPosition.xyz = rotate_vertex_position((rteLocalPosition - rtePivot).xyz, tQuaternion) * tScale.xyz + rtePivot.xyz + tTranslation.xyz;
|
||||
rteLocalPosition.xyz = rotate_vertex_position_delta(rteLocalPosition, rtePivot, tQuaternion) * tScale.xyz + rtePivot.xyz + tTranslation.xyz;
|
||||
#endif
|
||||
#ifdef USE_INSTANCING
|
||||
vec4 instancePivot = computeRelativePositionSeparate(ZERO3, ZERO3, uViewer_low, uViewer_high);
|
||||
|
||||
@@ -129,6 +129,29 @@ export const speckleNormalVert = /* glsl */ `
|
||||
{
|
||||
return position + 2.0 * cross(quat.xyz, cross(quat.xyz, position) + quat.w * position);
|
||||
}
|
||||
|
||||
highp vec3 rotate_vertex_position_delta(highp vec4 v0, highp vec4 v1, highp vec4 quat)
|
||||
{
|
||||
/** !!! WORKAROUND FOR Intel IrisXe CARDS !!! */
|
||||
/** The code below will not produce correct results in intel IrisXE integrated GPUs.
|
||||
* The geometry will turn mangled, albeit stable
|
||||
* I can't know for sure what is going on, but rotating the difference seems to
|
||||
* force the result into a lower precision?
|
||||
*/
|
||||
// highp vec4 position = v0 - v1;
|
||||
// return position.xyz + 2.0 * cross(quat.xyz, cross(quat.xyz, position.xyz) + quat.w * position.xyz);
|
||||
|
||||
/** Subtracting the rotated vectors works. */
|
||||
return rotate_vertex_position(v0.xyz, quat) - rotate_vertex_position(v1.xyz, quat);
|
||||
|
||||
/** An alternate workaround is
|
||||
* highp vec3 position = (v0.xyz * (1. + 1e-7)) - (v1.xyz * (1. _ 1e-7));
|
||||
return position + 2.0 * cross(quat.xyz, cross(quat.xyz, position) + quat.w * position);
|
||||
|
||||
However I'm not such a fan of the (1. + 1e-7) part
|
||||
*/
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
void main() {
|
||||
@@ -156,7 +179,7 @@ void main() {
|
||||
vec4 rteLocalPosition = computeRelativePositionSeparate(position_lowT.xyz, position_highT.xyz, uViewer_low, uViewer_high);
|
||||
#ifdef TRANSFORM_STORAGE
|
||||
vec4 rtePivot = computeRelativePositionSeparate(tPivotLow.xyz, tPivotHigh.xyz, uViewer_low, uViewer_high);
|
||||
rteLocalPosition.xyz = rotate_vertex_position((rteLocalPosition - rtePivot).xyz, tQuaternion) * tScale.xyz + rtePivot.xyz + tTranslation.xyz;
|
||||
rteLocalPosition.xyz = rotate_vertex_position_delta(rteLocalPosition, rtePivot, tQuaternion) * tScale.xyz + rtePivot.xyz + tTranslation.xyz;
|
||||
#endif
|
||||
#ifdef USE_INSTANCING
|
||||
vec4 instancePivot = computeRelativePositionSeparate(ZERO3, ZERO3, uViewer_low, uViewer_high);
|
||||
|
||||
@@ -91,6 +91,29 @@ varying vec3 vViewPosition;
|
||||
{
|
||||
return position + 2.0 * cross(quat.xyz, cross(quat.xyz, position) + quat.w * position);
|
||||
}
|
||||
|
||||
highp vec3 rotate_vertex_position_delta(highp vec4 v0, highp vec4 v1, highp vec4 quat)
|
||||
{
|
||||
/** !!! WORKAROUND FOR Intel IrisXe CARDS !!! */
|
||||
/** The code below will not produce correct results in intel IrisXE integrated GPUs.
|
||||
* The geometry will turn mangled, albeit stable
|
||||
* I can't know for sure what is going on, but rotating the difference seems to
|
||||
* force the result into a lower precision?
|
||||
*/
|
||||
// highp vec4 position = v0 - v1;
|
||||
// return position.xyz + 2.0 * cross(quat.xyz, cross(quat.xyz, position.xyz) + quat.w * position.xyz);
|
||||
|
||||
/** Subtracting the rotated vectors works. */
|
||||
return rotate_vertex_position(v0.xyz, quat) - rotate_vertex_position(v1.xyz, quat);
|
||||
|
||||
/** An alternate workaround is
|
||||
* highp vec3 position = (v0.xyz * (1. + 1e-7)) - (v1.xyz * (1. _ 1e-7));
|
||||
return position + 2.0 * cross(quat.xyz, cross(quat.xyz, position) + quat.w * position);
|
||||
|
||||
However I'm not such a fan of the (1. + 1e-7) part
|
||||
*/
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
attribute float gradientIndex;
|
||||
@@ -176,7 +199,7 @@ void main() {
|
||||
vec4 rteLocalPosition = computeRelativePositionSeparate(position_lowT.xyz, position_highT.xyz, uViewer_low, uViewer_high);
|
||||
#ifdef TRANSFORM_STORAGE
|
||||
vec4 rtePivot = computeRelativePositionSeparate(tPivotLow.xyz, tPivotHigh.xyz, uViewer_low, uViewer_high);
|
||||
rteLocalPosition.xyz = rotate_vertex_position((rteLocalPosition - rtePivot).xyz, tQuaternion) * tScale.xyz + rtePivot.xyz + tTranslation.xyz;
|
||||
rteLocalPosition.xyz = rotate_vertex_position_delta(rteLocalPosition, rtePivot, tQuaternion) * tScale.xyz + rtePivot.xyz + tTranslation.xyz;
|
||||
#endif
|
||||
#ifdef USE_INSTANCING
|
||||
vec4 instancePivot = computeRelativePositionSeparate(ZERO3, ZERO3, uViewer_low, uViewer_high);
|
||||
@@ -224,7 +247,7 @@ void main() {
|
||||
#endif
|
||||
#ifdef TRANSFORM_STORAGE
|
||||
vec4 rtePivotShadow = computeRelativePositionSeparate(tPivotLow.xyz, tPivotHigh.xyz, uShadowViewer_low, uShadowViewer_high);
|
||||
shadowPosition.xyz = rotate_vertex_position((shadowPosition - rtePivotShadow).xyz, tQuaternion) * tScale.xyz + rtePivotShadow.xyz + tTranslation.xyz;
|
||||
shadowPosition.xyz = rotate_vertex_position_delta(shadowPosition, rtePivotShadow, tQuaternion) * tScale.xyz + rtePivotShadow.xyz + tTranslation.xyz;
|
||||
#endif
|
||||
#ifdef USE_INSTANCING
|
||||
vec4 rtePivotShadow = computeRelativePositionSeparate(ZERO3, ZERO3, uShadowViewer_low, uShadowViewer_high);
|
||||
|
||||
@@ -87,10 +87,34 @@ varying vec3 vViewPosition;
|
||||
#endif
|
||||
}
|
||||
|
||||
vec3 rotate_vertex_position(vec3 position, vec4 quat)
|
||||
{
|
||||
|
||||
highp vec3 rotate_vertex_position(highp vec3 position, highp vec4 quat)
|
||||
{
|
||||
return position + 2.0 * cross(quat.xyz, cross(quat.xyz, position) + quat.w * position);
|
||||
}
|
||||
|
||||
highp vec3 rotate_vertex_position_delta(highp vec4 v0, highp vec4 v1, highp vec4 quat)
|
||||
{
|
||||
/** !!! WORKAROUND FOR Intel IrisXe CARDS !!! */
|
||||
/** The code below will not produce correct results in intel IrisXE integrated GPUs.
|
||||
* The geometry will turn mangled, albeit stable
|
||||
* I can't know for sure what is going on, but rotating the difference seems to
|
||||
* force the result into a lower precision?
|
||||
*/
|
||||
// highp vec4 position = v0 - v1;
|
||||
// return position.xyz + 2.0 * cross(quat.xyz, cross(quat.xyz, position.xyz) + quat.w * position.xyz);
|
||||
|
||||
/** Subtracting the rotated vectors works. */
|
||||
return rotate_vertex_position(v0.xyz, quat) - rotate_vertex_position(v1.xyz, quat);
|
||||
|
||||
/** An alternate workaround is
|
||||
* highp vec3 position = (v0.xyz * (1. + 1e-7)) - (v1.xyz * (1. _ 1e-7));
|
||||
return position + 2.0 * cross(quat.xyz, cross(quat.xyz, position) + quat.w * position);
|
||||
|
||||
However I'm not such a fan of the (1. + 1e-7) part
|
||||
*/
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#ifdef USE_RTE
|
||||
@@ -143,8 +167,6 @@ varying vec3 vViewPosition;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
void main() {
|
||||
|
||||
#include <uv_vertex>
|
||||
@@ -165,7 +187,7 @@ void main() {
|
||||
//#include <project_vertex> // EDITED CHUNK
|
||||
|
||||
#ifdef TRANSFORM_STORAGE
|
||||
vec4 tQuaternion, tPivotLow, tPivotHigh, tTranslation, tScale;
|
||||
highp vec4 tQuaternion, tPivotLow, tPivotHigh, tTranslation, tScale;
|
||||
objectTransform(tQuaternion, tPivotLow, tPivotHigh, tTranslation, tScale);
|
||||
#endif
|
||||
#ifdef USE_RTE
|
||||
@@ -176,7 +198,7 @@ void main() {
|
||||
vec4 rteLocalPosition = computeRelativePositionSeparate(position_lowT.xyz, position_highT.xyz, uViewer_low, uViewer_high);
|
||||
#ifdef TRANSFORM_STORAGE
|
||||
vec4 rtePivot = computeRelativePositionSeparate(tPivotLow.xyz, tPivotHigh.xyz, uViewer_low, uViewer_high);
|
||||
rteLocalPosition.xyz = rotate_vertex_position((rteLocalPosition - rtePivot).xyz, tQuaternion) * tScale.xyz + rtePivot.xyz + tTranslation.xyz;
|
||||
rteLocalPosition.xyz = rotate_vertex_position_delta(rteLocalPosition, rtePivot, tQuaternion) * tScale.xyz + rtePivot.xyz + tTranslation.xyz;
|
||||
#endif
|
||||
#ifdef USE_INSTANCING
|
||||
vec4 instancePivot = computeRelativePositionSeparate(ZERO3, ZERO3, uViewer_low, uViewer_high);
|
||||
@@ -223,7 +245,7 @@ void main() {
|
||||
#endif
|
||||
#ifdef TRANSFORM_STORAGE
|
||||
vec4 rtePivotShadow = computeRelativePositionSeparate(tPivotLow.xyz, tPivotHigh.xyz, uShadowViewer_low, uShadowViewer_high);
|
||||
shadowPosition.xyz = rotate_vertex_position((shadowPosition - rtePivotShadow).xyz, tQuaternion) * tScale.xyz + rtePivotShadow.xyz + tTranslation.xyz;
|
||||
shadowPosition.xyz = rotate_vertex_position_delta(shadowPosition, rtePivotShadow, tQuaternion) * tScale.xyz + rtePivotShadow.xyz + tTranslation.xyz;
|
||||
#endif
|
||||
#ifdef USE_INSTANCING
|
||||
vec4 rtePivotShadow = computeRelativePositionSeparate(ZERO3, ZERO3, uShadowViewer_low, uShadowViewer_high);
|
||||
|
||||
@@ -127,6 +127,29 @@ export const speckleTextVert = /* glsl */ `
|
||||
{
|
||||
return position + 2.0 * cross(quat.xyz, cross(quat.xyz, position) + quat.w * position);
|
||||
}
|
||||
|
||||
highp vec3 rotate_vertex_position_delta(highp vec4 v0, highp vec4 v1, highp vec4 quat)
|
||||
{
|
||||
/** !!! WORKAROUND FOR Intel IrisXe CARDS !!! */
|
||||
/** The code below will not produce correct results in intel IrisXE integrated GPUs.
|
||||
* The geometry will turn mangled, albeit stable
|
||||
* I can't know for sure what is going on, but rotating the difference seems to
|
||||
* force the result into a lower precision?
|
||||
*/
|
||||
// highp vec4 position = v0 - v1;
|
||||
// return position.xyz + 2.0 * cross(quat.xyz, cross(quat.xyz, position.xyz) + quat.w * position.xyz);
|
||||
|
||||
/** Subtracting the rotated vectors works. */
|
||||
return rotate_vertex_position(v0.xyz, quat) - rotate_vertex_position(v1.xyz, quat);
|
||||
|
||||
/** An alternate workaround is
|
||||
* highp vec3 position = (v0.xyz * (1. + 1e-7)) - (v1.xyz * (1. _ 1e-7));
|
||||
return position + 2.0 * cross(quat.xyz, cross(quat.xyz, position) + quat.w * position);
|
||||
|
||||
However I'm not such a fan of the (1. + 1e-7) part
|
||||
*/
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#if defined(BILLBOARD) || defined(BILLBOARD_FIXED)
|
||||
@@ -165,7 +188,7 @@ void main() {
|
||||
vec4 rteLocalPosition = computeRelativePositionSeparate(position_lowT.xyz, position_highT.xyz, uViewer_low, uViewer_high);
|
||||
#ifdef TRANSFORM_STORAGE
|
||||
vec4 rtePivot = computeRelativePositionSeparate(tPivotLow.xyz, tPivotHigh.xyz, uViewer_low, uViewer_high);
|
||||
rteLocalPosition.xyz = rotate_vertex_position((rteLocalPosition - rtePivot).xyz, tQuaternion) * tScale.xyz + rtePivot.xyz + tTranslation.xyz;
|
||||
rteLocalPosition.xyz = rotate_vertex_position_delta(rteLocalPosition, rtePivot, tQuaternion) * tScale.xyz + rtePivot.xyz + tTranslation.xyz;
|
||||
#endif
|
||||
#ifdef USE_INSTANCING
|
||||
vec4 instancePivot = computeRelativePositionSeparate(ZERO3, ZERO3, uViewer_low, uViewer_high);
|
||||
|
||||
@@ -130,6 +130,29 @@ varying vec3 vViewPosition;
|
||||
{
|
||||
return position + 2.0 * cross(quat.xyz, cross(quat.xyz, position) + quat.w * position);
|
||||
}
|
||||
|
||||
highp vec3 rotate_vertex_position_delta(highp vec4 v0, highp vec4 v1, highp vec4 quat)
|
||||
{
|
||||
/** !!! WORKAROUND FOR Intel IrisXe CARDS !!! */
|
||||
/** The code below will not produce correct results in intel IrisXE integrated GPUs.
|
||||
* The geometry will turn mangled, albeit stable
|
||||
* I can't know for sure what is going on, but rotating the difference seems to
|
||||
* force the result into a lower precision?
|
||||
*/
|
||||
// highp vec4 position = v0 - v1;
|
||||
// return position.xyz + 2.0 * cross(quat.xyz, cross(quat.xyz, position.xyz) + quat.w * position.xyz);
|
||||
|
||||
/** Subtracting the rotated vectors works. */
|
||||
return rotate_vertex_position(v0.xyz, quat) - rotate_vertex_position(v1.xyz, quat);
|
||||
|
||||
/** An alternate workaround is
|
||||
* highp vec3 position = (v0.xyz * (1. + 1e-7)) - (v1.xyz * (1. _ 1e-7));
|
||||
return position + 2.0 * cross(quat.xyz, cross(quat.xyz, position) + quat.w * position);
|
||||
|
||||
However I'm not such a fan of the (1. + 1e-7) part
|
||||
*/
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#if defined(BILLBOARD) || defined(BILLBOARD_FIXED)
|
||||
@@ -169,7 +192,7 @@ void main() {
|
||||
vec4 rteLocalPosition = computeRelativePositionSeparate(position_lowT.xyz, position_highT.xyz, uViewer_low, uViewer_high);
|
||||
#ifdef TRANSFORM_STORAGE
|
||||
vec4 rtePivot = computeRelativePositionSeparate(tPivotLow.xyz, tPivotHigh.xyz, uViewer_low, uViewer_high);
|
||||
rteLocalPosition.xyz = rotate_vertex_position((rteLocalPosition - rtePivot).xyz, tQuaternion) * tScale.xyz + rtePivot.xyz + tTranslation.xyz;
|
||||
rteLocalPosition.xyz = rotate_vertex_position_delta(rteLocalPosition, rtePivot, tQuaternion) * tScale.xyz + rtePivot.xyz + tTranslation.xyz;
|
||||
#endif
|
||||
#ifdef USE_INSTANCING
|
||||
vec4 instancePivot = computeRelativePositionSeparate(ZERO3, ZERO3, uViewer_low, uViewer_high);
|
||||
|
||||
Reference in New Issue
Block a user