Automate Public Beta (#3472)
* feat(automate): query active user functions * fix(automate): show automations to non-stream-owners * feat(automate): associate function with workspace * fix(automate): split functions page between user and example functions * fix(automate): ugh * fix(functions): use correct query type in different places * fix(automate): workspace functions page * feat(automate): query specific categories of functions * fix(automate): checkpoint * fix(workspaces): successful queries w local env * fix(automate): createFunctionWithoutVersion * fix(automate): successful associate function with workspace * fix(automate): query and return workspaces on functions * fix(automate): show current function workspace * fix(automate): query functions in automation create dialog * fix(automate): audit non-owner automation access * refactor(automate): logs api can get the projectId from the path * fix(automate): multiregion gql resolvers * fix(automate): multiregion event listeners * fix(automate): drop automationCount * fix(automate): multiregion run status * fix(automate): correctness * fix(automate): successful usage of multiregion results * fix(automate): actually finish event listeners * chore(automate): fix tests fix tests * fix(automate): fix tests but make it multiregion flavor * fix(automate): logs endpoint * fix(automate): inject projectid correctly * fix(automate): drop user-source functions * fix(automate): owners edit, others can view * fix(automate): simplify queries, auto workspace association * chore(automate): appease * chore(automate): fix function types * fix(automate): get to workspace functions from empty state * chore(automate): death to all slugs * fix(automate): no create automation from function * fix(automate): hide workspace change, tweak role access --------- Co-authored-by: Gergő Jedlicska <gergo@jedlicska.com>
This commit is contained in:
@@ -43,6 +43,7 @@
|
||||
:show-label="false"
|
||||
:show-required="false"
|
||||
:preselected-function="validatedPreselectedFunction"
|
||||
:workspace-id="workspaceId"
|
||||
/>
|
||||
<AutomateAutomationCreateDialogFunctionParametersStep
|
||||
v-else-if="
|
||||
@@ -66,6 +67,7 @@
|
||||
v-model:selected-function="selectedFunction"
|
||||
:preselected-function="validatedPreselectedFunction"
|
||||
:page-size="2"
|
||||
:workspace-id="workspaceId"
|
||||
/>
|
||||
</template>
|
||||
</div>
|
||||
@@ -124,6 +126,7 @@ graphql(`
|
||||
`)
|
||||
|
||||
const props = defineProps<{
|
||||
workspaceId?: string
|
||||
preselectedFunction?: Optional<CreateAutomationSelectableFunction>
|
||||
preselectedProject?: Optional<FormSelectProjects_ProjectFragment>
|
||||
}>()
|
||||
|
||||
+20
-10
@@ -43,13 +43,19 @@ import type { Optional } from '@speckle/shared'
|
||||
import { usePaginatedQuery } from '~/lib/common/composables/graphql'
|
||||
|
||||
const searchQuery = graphql(`
|
||||
query AutomationCreateDialogFunctionsSearch($search: String, $cursor: String = null) {
|
||||
automateFunctions(limit: 20, filter: { search: $search }, cursor: $cursor) {
|
||||
cursor
|
||||
totalCount
|
||||
items {
|
||||
id
|
||||
...AutomateAutomationCreateDialog_AutomateFunction
|
||||
query AutomationCreateDialogFunctionsSearch(
|
||||
$workspaceId: String!
|
||||
$search: String
|
||||
$cursor: String = null
|
||||
) {
|
||||
workspace(id: $workspaceId) {
|
||||
automateFunctions(limit: 20, filter: { search: $search }, cursor: $cursor) {
|
||||
cursor
|
||||
totalCount
|
||||
items {
|
||||
id
|
||||
...AutomateAutomationCreateDialog_AutomateFunction
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -57,6 +63,7 @@ const searchQuery = graphql(`
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
workspaceId?: string
|
||||
preselectedFunction: Optional<CreateAutomationSelectableFunction>
|
||||
pageSize?: Optional<number>
|
||||
showLabel?: Optional<boolean>
|
||||
@@ -83,10 +90,11 @@ const {
|
||||
} = usePaginatedQuery({
|
||||
query: searchQuery,
|
||||
baseVariables: computed(() => ({
|
||||
search: search.value?.length ? search.value : null
|
||||
workspaceId: props.workspaceId ?? '',
|
||||
search: search.value?.length ? search.value : ''
|
||||
})),
|
||||
resolveKey: (vars) => [vars.search || ''],
|
||||
resolveCurrentResult: (res) => res?.automateFunctions,
|
||||
resolveCurrentResult: (res) => res?.workspace?.automateFunctions,
|
||||
resolveNextPageVariables: (baseVars, cursor) => ({
|
||||
...baseVars,
|
||||
cursor
|
||||
@@ -94,7 +102,9 @@ const {
|
||||
resolveCursorFromVariables: (vars) => vars.cursor
|
||||
})
|
||||
|
||||
const queryItems = computed(() => result.value?.automateFunctions.items)
|
||||
const queryItems = computed(() => {
|
||||
return result.value?.workspace?.automateFunctions.items
|
||||
})
|
||||
const items = computed(() => {
|
||||
const baseItems = (queryItems.value || []).slice(0, props.pageSize)
|
||||
const preselectedFn = props.preselectedFunction
|
||||
|
||||
@@ -56,7 +56,10 @@ import {
|
||||
} from '~/lib/common/helpers/route'
|
||||
import { useEnumSteps, useEnumStepsWidgetSetup } from '~/lib/form/composables/steps'
|
||||
import { useForm } from 'vee-validate'
|
||||
import { useCreateAutomateFunction } from '~/lib/automate/composables/management'
|
||||
import {
|
||||
useCreateAutomateFunction,
|
||||
useUpdateAutomateFunction
|
||||
} from '~/lib/automate/composables/management'
|
||||
import { useMutationLoading } from '@vue/apollo-composable'
|
||||
import type { AutomateFunctionCreateDialogDoneStep_AutomateFunctionFragment } from '~~/lib/common/generated/gql/graphql'
|
||||
import { useMixpanel } from '~/lib/core/composables/mp'
|
||||
@@ -74,6 +77,7 @@ const props = defineProps<{
|
||||
isAuthorized: boolean
|
||||
templates: CreatableFunctionTemplate[]
|
||||
githubOrgs: string[]
|
||||
workspaceId?: string
|
||||
}>()
|
||||
const open = defineModel<boolean>('open', { required: true })
|
||||
|
||||
@@ -81,6 +85,7 @@ const mixpanel = useMixpanel()
|
||||
const logger = useLogger()
|
||||
const mutationLoading = useMutationLoading()
|
||||
const createFunction = useCreateAutomateFunction()
|
||||
const updateFunction = useUpdateAutomateFunction()
|
||||
const { handleSubmit: handleDetailsSubmit } = useForm<DetailsFormValues>()
|
||||
const onDetailsSubmit = handleDetailsSubmit(async (values) => {
|
||||
if (!selectedTemplate.value) {
|
||||
@@ -99,15 +104,29 @@ const onDetailsSubmit = handleDetailsSubmit(async (values) => {
|
||||
}
|
||||
})
|
||||
|
||||
if (res?.id) {
|
||||
mixpanel.track('Automate Function Created', {
|
||||
functionId: res.id,
|
||||
templateId: selectedTemplate.value.id,
|
||||
name: values.name
|
||||
})
|
||||
createdFunction.value = res
|
||||
step.value++
|
||||
if (!res?.id) {
|
||||
// TODO: Error toast with butter
|
||||
return
|
||||
}
|
||||
|
||||
mixpanel.track('Automate Function Created', {
|
||||
functionId: res.id,
|
||||
templateId: selectedTemplate.value.id,
|
||||
name: values.name
|
||||
})
|
||||
createdFunction.value = res
|
||||
step.value++
|
||||
|
||||
if (!props.workspaceId) {
|
||||
return
|
||||
}
|
||||
|
||||
await updateFunction({
|
||||
input: {
|
||||
id: res.id,
|
||||
workspaceIds: [props.workspaceId]
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
const onSubmit = computed(() => {
|
||||
|
||||
@@ -18,10 +18,12 @@ import { difference, differenceBy } from 'lodash-es'
|
||||
import { useForm } from 'vee-validate'
|
||||
import { useUpdateAutomateFunction } from '~/lib/automate/composables/management'
|
||||
import type { FunctionDetailsFormValues } from '~/lib/automate/helpers/functions'
|
||||
import type { Workspace } from '~/lib/common/generated/gql/graphql'
|
||||
|
||||
const props = defineProps<{
|
||||
model: FunctionDetailsFormValues
|
||||
fnId: string
|
||||
workspaces?: Pick<Workspace, 'id' | 'name'>[]
|
||||
}>()
|
||||
const open = defineModel<boolean>('open', { required: true })
|
||||
const { handleSubmit, setValues } = useForm<FunctionDetailsFormValues>()
|
||||
@@ -53,6 +55,7 @@ const onSubmit = handleSubmit(async (values) => {
|
||||
values.description !== props.model.description ? values.description : null,
|
||||
logo: values.image !== props.model.image ? values.image : null,
|
||||
tags: difference(values.tags, props.model.tags || []).length ? values.tags : null,
|
||||
workspaceIds: values.workspace ? [values.workspace.id] : [],
|
||||
supportedSourceApps: differenceBy(
|
||||
values.allowedSourceApps,
|
||||
props.model.allowedSourceApps || [],
|
||||
|
||||
@@ -33,6 +33,30 @@
|
||||
:rules="descriptionRules"
|
||||
validate-on-value-update
|
||||
/>
|
||||
<FormSelectBase
|
||||
v-if="workspaces?.length"
|
||||
name="workspace"
|
||||
label="Workspace"
|
||||
placeholder="Select a workspace"
|
||||
show-label
|
||||
allow-unset
|
||||
clearable
|
||||
help="Allow automations in one of your workspaces to use this function."
|
||||
:items="workspaces"
|
||||
>
|
||||
<template #something-selected="{ value }">
|
||||
<div class="label label--light">
|
||||
{{ isArray(value) ? value[0].name : value.name }}
|
||||
</div>
|
||||
</template>
|
||||
<template #option="{ item, selected }">
|
||||
<div class="flex flex-col">
|
||||
<div :class="['label label--light', selected ? 'text-primary' : '']">
|
||||
{{ item.name }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</FormSelectBase>
|
||||
<FormSelectSourceApps
|
||||
name="allowedSourceApps"
|
||||
label="Supported source apps"
|
||||
@@ -85,9 +109,19 @@
|
||||
<script setup lang="ts">
|
||||
import { ValidationHelpers } from '@speckle/ui-components'
|
||||
import { isArray } from 'lodash-es'
|
||||
import { graphql } from '~/lib/common/generated/gql'
|
||||
import type { Workspace } from '~/lib/common/generated/gql/graphql'
|
||||
|
||||
graphql(`
|
||||
fragment AutomateFunctionCreateDialog_Workspace on Workspace {
|
||||
id
|
||||
name
|
||||
}
|
||||
`)
|
||||
|
||||
defineProps<{
|
||||
githubOrgs?: string[]
|
||||
workspaces?: Pick<Workspace, 'id' | 'name'>[]
|
||||
}>()
|
||||
|
||||
const avatarEditMode = ref(false)
|
||||
|
||||
@@ -11,23 +11,10 @@
|
||||
<div class="flex items-center gap-4">
|
||||
<AutomateFunctionLogo :logo="fn.logo" />
|
||||
<h1 class="text-heading-lg">{{ fn.name }}</h1>
|
||||
<FormButton v-if="isOwner" size="sm" text class="mt-1" @click="$emit('edit')">
|
||||
Edit
|
||||
</FormButton>
|
||||
</div>
|
||||
<div
|
||||
v-tippy="
|
||||
hasReleases ? undefined : 'Your function needs to have at least one release'
|
||||
"
|
||||
class="flex gap-2 shrink-0"
|
||||
>
|
||||
<FormButton
|
||||
class="shrink-0"
|
||||
full-width
|
||||
:disabled="!hasReleases"
|
||||
@click="$emit('createAutomation')"
|
||||
>
|
||||
Use in automation
|
||||
<div class="flex items-center align-center gap-2">
|
||||
<FormButton v-if="isOwner" color="outline" @click="$emit('edit')">
|
||||
Edit
|
||||
</FormButton>
|
||||
</div>
|
||||
</div>
|
||||
@@ -40,11 +27,6 @@ import {
|
||||
automationFunctionsRoute
|
||||
} from '~/lib/common/helpers/route'
|
||||
|
||||
defineEmits<{
|
||||
createAutomation: []
|
||||
edit: []
|
||||
}>()
|
||||
|
||||
graphql(`
|
||||
fragment AutomateFunctionPageHeader_Function on AutomateFunction {
|
||||
id
|
||||
@@ -59,13 +41,17 @@ graphql(`
|
||||
releases(limit: 1) {
|
||||
totalCount
|
||||
}
|
||||
workspaceIds
|
||||
}
|
||||
`)
|
||||
|
||||
const props = defineProps<{
|
||||
defineProps<{
|
||||
fn: AutomateFunctionPageHeader_FunctionFragment
|
||||
isOwner: boolean
|
||||
}>()
|
||||
|
||||
const hasReleases = computed(() => props.fn.releases.totalCount > 0)
|
||||
defineEmits<{
|
||||
createAutomation: []
|
||||
edit: []
|
||||
}>()
|
||||
</script>
|
||||
|
||||
@@ -1,27 +1,30 @@
|
||||
<template>
|
||||
<div>
|
||||
<Portal to="navigation">
|
||||
<template v-if="!!workspace?.slug && !!workspace?.name">
|
||||
<HeaderNavLink
|
||||
:to="workspaceRoute(workspace.slug)"
|
||||
:name="workspace.name"
|
||||
:separator="false"
|
||||
/>
|
||||
</template>
|
||||
<HeaderNavLink
|
||||
:separator="false"
|
||||
:to="automationFunctionsRoute"
|
||||
name="Automate functions"
|
||||
:seperator="false"
|
||||
:to="workspaceFunctionsRoute(workspace?.slug!)"
|
||||
name="Functions"
|
||||
:separator="!!workspace"
|
||||
/>
|
||||
</Portal>
|
||||
<div class="pt-4 flex flex-col md:flex-row gap-y-2 md:gap-x-4 md:justify-between">
|
||||
<h1 class="text-heading-lg">Automate functions</h1>
|
||||
<div class="flex flex-row gap-2">
|
||||
<div class="flex-1">
|
||||
<FormTextInput
|
||||
name="search"
|
||||
placeholder="Search functions..."
|
||||
show-clear
|
||||
color="foundation"
|
||||
class="grow"
|
||||
v-bind="bind"
|
||||
v-on="on"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex flex-col md:flex-row gap-y-2 md:gap-x-4 md:justify-between">
|
||||
<div class="w-full flex flex-row justify-between gap-2">
|
||||
<FormTextInput
|
||||
name="search"
|
||||
placeholder="Search..."
|
||||
show-clear
|
||||
color="foundation"
|
||||
class="grow"
|
||||
v-bind="bind"
|
||||
v-on="on"
|
||||
/>
|
||||
<FormButton :disabled="!canCreateFunction" @click="createDialogOpen = true">
|
||||
New function
|
||||
</FormButton>
|
||||
@@ -32,20 +35,26 @@
|
||||
:is-authorized="!!activeUser?.automateInfo.hasAutomateGithubApp"
|
||||
:github-orgs="activeUser?.automateInfo.availableGithubOrgs || []"
|
||||
:templates="availableTemplates"
|
||||
:workspace-id="workspace?.id"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { Nullable, Optional } from '@speckle/shared'
|
||||
import { Roles, type Nullable, type Optional } from '@speckle/shared'
|
||||
import { useDebouncedTextInput } from '@speckle/ui-components'
|
||||
import { graphql } from '~/lib/common/generated/gql'
|
||||
import type { AutomateFunctionsPageHeader_QueryFragment } from '~/lib/common/generated/gql/graphql'
|
||||
import { automationFunctionsRoute } from '~/lib/common/helpers/route'
|
||||
import type {
|
||||
AutomateFunctionsPageHeader_QueryFragment,
|
||||
Workspace
|
||||
} from '~/lib/common/generated/gql/graphql'
|
||||
import { workspaceFunctionsRoute, workspaceRoute } from '~/lib/common/helpers/route'
|
||||
|
||||
graphql(`
|
||||
fragment AutomateFunctionsPageHeader_Query on Query {
|
||||
activeUser {
|
||||
id
|
||||
role
|
||||
automateInfo {
|
||||
hasAutomateGithubApp
|
||||
availableGithubOrgs
|
||||
@@ -64,6 +73,7 @@ graphql(`
|
||||
const props = defineProps<{
|
||||
activeUser: Optional<AutomateFunctionsPageHeader_QueryFragment['activeUser']>
|
||||
serverInfo: Optional<AutomateFunctionsPageHeader_QueryFragment['serverInfo']>
|
||||
workspace?: Pick<Workspace, 'id' | 'slug' | 'name'>
|
||||
}>()
|
||||
const search = defineModel<string>('search')
|
||||
|
||||
@@ -77,9 +87,11 @@ const createDialogOpen = ref(false)
|
||||
const availableTemplates = computed(
|
||||
() => props.serverInfo?.automate.availableFunctionTemplates || []
|
||||
)
|
||||
const canCreateFunction = computed(
|
||||
() => !!props.activeUser?.id && !!availableTemplates.value.length
|
||||
)
|
||||
const canCreateFunction = computed(() => {
|
||||
return props.workspace
|
||||
? !!props.activeUser?.id && !!availableTemplates.value.length
|
||||
: props.activeUser?.role === Roles.Server.Admin
|
||||
})
|
||||
|
||||
if (import.meta.client) {
|
||||
watch(
|
||||
|
||||
@@ -18,7 +18,10 @@
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { graphql } from '~/lib/common/generated/gql'
|
||||
import type { AutomateFunctionsPageItems_QueryFragment } from '~/lib/common/generated/gql/graphql'
|
||||
import type {
|
||||
AutomateAutomationCreateDialog_AutomateFunctionFragment,
|
||||
AutomationsFunctionsCard_AutomateFunctionFragment
|
||||
} from '~/lib/common/generated/gql/graphql'
|
||||
import type { CreateAutomationSelectableFunction } from '~/lib/automate/helpers/automations'
|
||||
|
||||
defineEmits<{
|
||||
@@ -28,7 +31,7 @@ defineEmits<{
|
||||
|
||||
graphql(`
|
||||
fragment AutomateFunctionsPageItems_Query on Query {
|
||||
automateFunctions(limit: 20, filter: { search: $search }, cursor: $cursor) {
|
||||
automateFunctions(limit: 6, filter: { search: $search }, cursor: $cursor) {
|
||||
totalCount
|
||||
items {
|
||||
id
|
||||
@@ -41,10 +44,11 @@ graphql(`
|
||||
`)
|
||||
|
||||
const props = defineProps<{
|
||||
functions?: AutomateFunctionsPageItems_QueryFragment
|
||||
functions?: (AutomationsFunctionsCard_AutomateFunctionFragment &
|
||||
AutomateAutomationCreateDialog_AutomateFunctionFragment)[]
|
||||
search?: boolean
|
||||
loading?: boolean
|
||||
}>()
|
||||
|
||||
const fns = computed(() => props.functions?.automateFunctions.items || [])
|
||||
const fns = computed(() => props.functions || [])
|
||||
</script>
|
||||
|
||||
@@ -193,8 +193,23 @@ import { useActiveUser } from '~~/lib/auth/composables/activeUser'
|
||||
import { HomeIcon } from '@heroicons/vue/24/outline'
|
||||
import { useMixpanel } from '~~/lib/core/composables/mp'
|
||||
import { Roles } from '@speckle/shared'
|
||||
import { graphql } from '~/lib/common/generated/gql'
|
||||
import { WorkspacePlanStatuses } from '~/lib/common/generated/gql/graphql'
|
||||
|
||||
graphql(`
|
||||
fragment Sidebar_User on User {
|
||||
id
|
||||
automateFunctions {
|
||||
items {
|
||||
id
|
||||
name
|
||||
description
|
||||
logo
|
||||
}
|
||||
}
|
||||
}
|
||||
`)
|
||||
|
||||
const { isLoggedIn } = useActiveUser()
|
||||
const isWorkspacesEnabled = useIsWorkspacesEnabled()
|
||||
const route = useRoute()
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
<template>
|
||||
<svg
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="m3.75 13.5 10.5-11.25L12 10.5h8.25L9.75 21.75 12 13.5H3.75Z"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.5"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
vector-effect="non-scaling-stroke"
|
||||
/>
|
||||
</svg>
|
||||
</template>
|
||||
@@ -7,7 +7,7 @@
|
||||
:key="fn.fn.id"
|
||||
:fn="fn.fn"
|
||||
:is-outdated="isOutdated(fn)"
|
||||
show-edit
|
||||
:show-edit="isEditable"
|
||||
@edit="onEdit(fn.fn)"
|
||||
/>
|
||||
</AutomateFunctionCardView>
|
||||
@@ -65,6 +65,7 @@ graphql(`
|
||||
const props = defineProps<{
|
||||
projectId: string
|
||||
automation: ProjectPageAutomationFunctions_AutomationFragment
|
||||
isEditable: boolean
|
||||
}>()
|
||||
|
||||
const dialogOpen = ref(false)
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
<div class="flex flow-row justify-start items-center z-20">
|
||||
<CommonEditableTitle
|
||||
v-model="name"
|
||||
:disabled="loading"
|
||||
:disabled="loading || !isEditable"
|
||||
:custom-classes="{
|
||||
input: 'h4',
|
||||
pencil: 'ml-2 mt-2 w-4 h-4'
|
||||
@@ -24,7 +24,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<FormSwitch
|
||||
v-if="!automation.isTestAutomation"
|
||||
v-if="!automation.isTestAutomation && isEditable"
|
||||
:id="switchId"
|
||||
v-model="enabled"
|
||||
name="enable"
|
||||
@@ -69,6 +69,7 @@ graphql(`
|
||||
graphql(`
|
||||
fragment ProjectPageAutomationHeader_Project on Project {
|
||||
id
|
||||
role
|
||||
...ProjectPageModelsCardProject
|
||||
}
|
||||
`)
|
||||
@@ -76,6 +77,7 @@ graphql(`
|
||||
const props = defineProps<{
|
||||
project: ProjectPageAutomationHeader_ProjectFragment
|
||||
automation: ProjectPageAutomationHeader_AutomationFragment
|
||||
isEditable: boolean
|
||||
}>()
|
||||
|
||||
const switchId = useId()
|
||||
|
||||
@@ -44,7 +44,7 @@ graphql(`
|
||||
`)
|
||||
|
||||
graphql(`
|
||||
fragment ProjectPageAutomationHeader_Project on Project {
|
||||
fragment ProjectPageAutomationModels_Project on Project {
|
||||
id
|
||||
...ProjectPageModelsCardProject
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<div class="flex items-center justify-between h-6 mb-6">
|
||||
<h2 class="h6 font-medium">Runs</h2>
|
||||
<FormButton
|
||||
v-if="!automation.isTestAutomation"
|
||||
v-if="!automation.isTestAutomation && isEditable"
|
||||
:disabled="!automation.enabled"
|
||||
@click="onTrigger"
|
||||
>
|
||||
@@ -47,6 +47,7 @@ graphql(`
|
||||
const props = defineProps<{
|
||||
automation: ProjectPageAutomationRuns_AutomationFragment
|
||||
projectId: string
|
||||
isEditable: boolean
|
||||
}>()
|
||||
|
||||
const { identifier, onInfiniteLoad } = usePaginatedQuery({
|
||||
|
||||
@@ -9,9 +9,9 @@
|
||||
faults, and effortlessly creating delivery artifacts.
|
||||
</div>
|
||||
<div class="flex gap-x-4">
|
||||
<div v-if="isAutomateEnabled" v-tippy="creationDisabledReason">
|
||||
<div v-if="isAutomateEnabled" v-tippy="creationDisabledMessage">
|
||||
<FormButton
|
||||
:disabled="!!creationDisabledReason"
|
||||
:disabled="!!creationDisabledMessage"
|
||||
@click="$emit('new-automation')"
|
||||
>
|
||||
New automation
|
||||
@@ -38,9 +38,9 @@
|
||||
</div>
|
||||
<div v-if="isAutomateEnabled" class="flex flex-col gap-6">
|
||||
<div class="flex gap-2 flex-row justify-between items-center">
|
||||
<h2 class="text-heading-lg text-foreground">Featured functions</h2>
|
||||
<FormButton color="outline" class="shrink-0" :to="automationFunctionsRoute">
|
||||
Explore all
|
||||
<h2 class="text-heading-lg text-foreground">Public functions</h2>
|
||||
<FormButton color="outline" class="shrink-0" :to="functionsGalleryRoute">
|
||||
{{ functionGalleryLabel }}
|
||||
</FormButton>
|
||||
</div>
|
||||
<AutomateFunctionCardView v-if="functions.length">
|
||||
@@ -58,12 +58,15 @@
|
||||
<script setup lang="ts">
|
||||
import { graphql } from '~/lib/common/generated/gql'
|
||||
import type { ProjectPageAutomationsEmptyState_QueryFragment } from '~/lib/common/generated/gql/graphql'
|
||||
import { automationFunctionsRoute } from '~/lib/common/helpers/route'
|
||||
import {
|
||||
automationFunctionsRoute,
|
||||
workspaceFunctionsRoute
|
||||
} from '~/lib/common/helpers/route'
|
||||
import type { CreateAutomationSelectableFunction } from '~/lib/automate/helpers/automations'
|
||||
|
||||
graphql(`
|
||||
fragment ProjectPageAutomationsEmptyState_Query on Query {
|
||||
automateFunctions(limit: 9, filter: { featuredFunctionsOnly: true }) {
|
||||
automateFunctions(limit: 9) {
|
||||
items {
|
||||
...AutomationsFunctionsCard_AutomateFunction
|
||||
...AutomateAutomationCreateDialog_AutomateFunction
|
||||
@@ -78,9 +81,19 @@ defineEmits<{
|
||||
|
||||
const props = defineProps<{
|
||||
functions?: ProjectPageAutomationsEmptyState_QueryFragment
|
||||
workspaceSlug?: string
|
||||
isAutomateEnabled: boolean
|
||||
creationDisabledReason?: string
|
||||
creationDisabledMessage?: string
|
||||
}>()
|
||||
|
||||
const functions = computed(() => props.functions?.automateFunctions.items || [])
|
||||
|
||||
const functionGalleryLabel = computed(() =>
|
||||
props.workspaceSlug ? 'View workspace functions' : 'Explore all'
|
||||
)
|
||||
const functionsGalleryRoute = computed(() =>
|
||||
props.workspaceSlug
|
||||
? workspaceFunctionsRoute(props.workspaceSlug)
|
||||
: automationFunctionsRoute
|
||||
)
|
||||
</script>
|
||||
|
||||
@@ -13,34 +13,47 @@
|
||||
v-bind="bind"
|
||||
v-on="on"
|
||||
/>
|
||||
<div v-tippy="creationDisabledReason" class="shrink-0">
|
||||
<FormButton color="outline" class="shrink-0" :to="exploreFunctionsRoute">
|
||||
{{ exploreFunctionsMessage }}
|
||||
</FormButton>
|
||||
<div v-tippy="creationDisabledMessage" class="shrink-0">
|
||||
<FormButton
|
||||
class="shrink-0"
|
||||
:disabled="!!creationDisabledReason"
|
||||
:disabled="!!creationDisabledMessage"
|
||||
@click="$emit('new-automation')"
|
||||
>
|
||||
New automation
|
||||
</FormButton>
|
||||
</div>
|
||||
<FormButton color="outline" class="shrink-0" :to="automationFunctionsRoute">
|
||||
Explore functions
|
||||
</FormButton>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { useDebouncedTextInput } from '@speckle/ui-components'
|
||||
import { automationFunctionsRoute } from '~/lib/common/helpers/route'
|
||||
import {
|
||||
automationFunctionsRoute,
|
||||
workspaceFunctionsRoute
|
||||
} from '~/lib/common/helpers/route'
|
||||
|
||||
defineEmits<{
|
||||
'new-automation': []
|
||||
}>()
|
||||
|
||||
defineProps<{
|
||||
const props = defineProps<{
|
||||
workspaceSlug?: string
|
||||
showEmptyState?: boolean
|
||||
creationDisabledReason?: string
|
||||
creationDisabledMessage?: string
|
||||
}>()
|
||||
|
||||
const exploreFunctionsMessage = computed(() =>
|
||||
props.workspaceSlug ? 'View functions' : 'Explore functions'
|
||||
)
|
||||
const exploreFunctionsRoute = computed(() =>
|
||||
props.workspaceSlug
|
||||
? workspaceFunctionsRoute(props.workspaceSlug)
|
||||
: automationFunctionsRoute
|
||||
)
|
||||
|
||||
const search = defineModel<string>('search')
|
||||
const { on, bind } = useDebouncedTextInput({ model: search })
|
||||
</script>
|
||||
|
||||
@@ -2,10 +2,9 @@
|
||||
<div class="flex flex-col gap-y-4 md:gap-y-6">
|
||||
<ProjectPageAutomationsHeader
|
||||
v-model:search="search"
|
||||
:workspace-slug="workspaceSlug"
|
||||
:show-empty-state="shouldShowEmptyState"
|
||||
:creation-disabled-reason="
|
||||
allowNewCreation !== true ? allowNewCreation : undefined
|
||||
"
|
||||
:creation-disabled-message="disableCreateMessage"
|
||||
@new-automation="onNewAutomation"
|
||||
/>
|
||||
<template v-if="loading">
|
||||
@@ -14,11 +13,10 @@
|
||||
<template v-else>
|
||||
<ProjectPageAutomationsEmptyState
|
||||
v-if="shouldShowEmptyState"
|
||||
:workspace-slug="workspaceSlug"
|
||||
:functions="result"
|
||||
:is-automate-enabled="isAutomateEnabled"
|
||||
:creation-disabled-reason="
|
||||
allowNewCreation !== true ? allowNewCreation : undefined
|
||||
"
|
||||
:creation-disabled-message="disableCreateMessage"
|
||||
@new-automation="onNewAutomation"
|
||||
/>
|
||||
<template v-else>
|
||||
@@ -39,7 +37,9 @@
|
||||
</template>
|
||||
</template>
|
||||
<AutomateAutomationCreateDialog
|
||||
v-if="workspaceId"
|
||||
v-model:open="showNewAutomationDialog"
|
||||
:workspace-id="workspaceId"
|
||||
:preselected-project="project"
|
||||
:preselected-function="newAutomationTargetFn"
|
||||
/>
|
||||
@@ -53,6 +53,7 @@ import {
|
||||
} from '~/lib/projects/graphql/queries'
|
||||
import type { CreateAutomationSelectableFunction } from '~/lib/automate/helpers/automations'
|
||||
import { usePaginatedQuery } from '~/lib/common/composables/graphql'
|
||||
import { Roles } from '@speckle/shared'
|
||||
|
||||
const route = useRoute()
|
||||
const projectId = computed(() => route.params.id as string)
|
||||
@@ -74,6 +75,9 @@ const { result, loading } = useQuery(
|
||||
})
|
||||
)
|
||||
|
||||
const workspaceId = computed(() => result.value?.project?.workspace?.id)
|
||||
const workspaceSlug = computed(() => result.value?.project?.workspace?.slug)
|
||||
|
||||
// Pagination query
|
||||
const {
|
||||
identifier,
|
||||
@@ -114,10 +118,18 @@ const shouldShowEmptyState = computed(() => {
|
||||
return false
|
||||
})
|
||||
|
||||
const allowNewCreation = computed(() => {
|
||||
return (result.value?.project?.models?.items.length || 0) > 0
|
||||
? true
|
||||
: 'Your project should have at least 1 model before you can create an automation.'
|
||||
const disableCreateMessage = computed(() => {
|
||||
const allowedRoles: string[] = [Roles.Stream.Owner]
|
||||
|
||||
if (!allowedRoles.includes(result.value?.project?.role ?? '')) {
|
||||
return 'You must be a project owner to create automations.'
|
||||
}
|
||||
|
||||
if ((result.value?.project?.models?.items.length || 0) === 0) {
|
||||
return 'Your project should have at least 1 model before you can create an automation.'
|
||||
}
|
||||
|
||||
return undefined
|
||||
})
|
||||
|
||||
const onNewAutomation = (fn?: CreateAutomationSelectableFunction) => {
|
||||
|
||||
@@ -48,3 +48,16 @@ export const automateFunctionsPagePaginationQuery = graphql(`
|
||||
...AutomateFunctionsPageItems_Query
|
||||
}
|
||||
`)
|
||||
|
||||
export const activeUserFunctionsQuery = graphql(`
|
||||
query ActiveUserFunctions {
|
||||
activeUser {
|
||||
automateFunctions(limit: 2) {
|
||||
items {
|
||||
id
|
||||
...AutomationsFunctionsCard_AutomateFunction
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`)
|
||||
|
||||
@@ -3,7 +3,10 @@ import type {
|
||||
Nullable,
|
||||
SourceAppDefinition
|
||||
} from '@speckle/shared'
|
||||
import type { AutomateFunctionCreateDialogTemplateStep_AutomateFunctionTemplateFragment } from '~/lib/common/generated/gql/graphql'
|
||||
import type {
|
||||
AutomateFunctionCreateDialogTemplateStep_AutomateFunctionTemplateFragment,
|
||||
Workspace
|
||||
} from '~/lib/common/generated/gql/graphql'
|
||||
|
||||
export type CreatableFunctionTemplate =
|
||||
AutomateFunctionCreateDialogTemplateStep_AutomateFunctionTemplateFragment
|
||||
@@ -15,6 +18,7 @@ export type FunctionDetailsFormValues = {
|
||||
allowedSourceApps?: SourceAppDefinition[]
|
||||
tags?: string[]
|
||||
org?: string
|
||||
workspace?: Pick<Workspace, 'id' | 'name'>
|
||||
}
|
||||
|
||||
export const cleanFunctionLogo = (
|
||||
|
||||
@@ -25,15 +25,16 @@ const documents = {
|
||||
"\n fragment AuthThirdPartyLoginButtonOIDC_ServerInfo on ServerInfo {\n authStrategies {\n id\n name\n }\n }\n": types.AuthThirdPartyLoginButtonOidc_ServerInfoFragmentDoc,
|
||||
"\n fragment AutomateAutomationCreateDialog_AutomateFunction on AutomateFunction {\n id\n ...AutomationsFunctionsCard_AutomateFunction\n ...AutomateAutomationCreateDialogFunctionParametersStep_AutomateFunction\n }\n": types.AutomateAutomationCreateDialog_AutomateFunctionFragmentDoc,
|
||||
"\n fragment AutomateAutomationCreateDialogFunctionParametersStep_AutomateFunction on AutomateFunction {\n id\n releases(limit: 1) {\n items {\n id\n inputSchema\n }\n }\n }\n": types.AutomateAutomationCreateDialogFunctionParametersStep_AutomateFunctionFragmentDoc,
|
||||
"\n query AutomationCreateDialogFunctionsSearch($search: String, $cursor: String = null) {\n automateFunctions(limit: 20, filter: { search: $search }, cursor: $cursor) {\n cursor\n totalCount\n items {\n id\n ...AutomateAutomationCreateDialog_AutomateFunction\n }\n }\n }\n": types.AutomationCreateDialogFunctionsSearchDocument,
|
||||
"\n query AutomationCreateDialogFunctionsSearch(\n $workspaceId: String!\n $search: String\n $cursor: String = null\n ) {\n workspace(id: $workspaceId) {\n automateFunctions(limit: 20, filter: { search: $search }, cursor: $cursor) {\n cursor\n totalCount\n items {\n id\n ...AutomateAutomationCreateDialog_AutomateFunction\n }\n }\n }\n }\n": types.AutomationCreateDialogFunctionsSearchDocument,
|
||||
"\n fragment AutomationsFunctionsCard_AutomateFunction on AutomateFunction {\n id\n name\n isFeatured\n description\n logo\n repo {\n id\n url\n owner\n name\n }\n }\n": types.AutomationsFunctionsCard_AutomateFunctionFragmentDoc,
|
||||
"\n fragment AutomateFunctionCreateDialog_Workspace on Workspace {\n id\n name\n }\n": types.AutomateFunctionCreateDialog_WorkspaceFragmentDoc,
|
||||
"\n fragment AutomateFunctionCreateDialogDoneStep_AutomateFunction on AutomateFunction {\n id\n repo {\n id\n url\n owner\n name\n }\n ...AutomationsFunctionsCard_AutomateFunction\n }\n": types.AutomateFunctionCreateDialogDoneStep_AutomateFunctionFragmentDoc,
|
||||
"\n fragment AutomateFunctionCreateDialogTemplateStep_AutomateFunctionTemplate on AutomateFunctionTemplate {\n id\n title\n logo\n url\n }\n": types.AutomateFunctionCreateDialogTemplateStep_AutomateFunctionTemplateFragmentDoc,
|
||||
"\n fragment AutomateFunctionPageHeader_Function on AutomateFunction {\n id\n name\n logo\n repo {\n id\n url\n owner\n name\n }\n releases(limit: 1) {\n totalCount\n }\n }\n": types.AutomateFunctionPageHeader_FunctionFragmentDoc,
|
||||
"\n fragment AutomateFunctionPageHeader_Function on AutomateFunction {\n id\n name\n logo\n repo {\n id\n url\n owner\n name\n }\n releases(limit: 1) {\n totalCount\n }\n workspaceIds\n }\n": types.AutomateFunctionPageHeader_FunctionFragmentDoc,
|
||||
"\n fragment AutomateFunctionPageInfo_AutomateFunction on AutomateFunction {\n id\n repo {\n id\n url\n owner\n name\n }\n description\n releases(limit: 1) {\n items {\n id\n inputSchema\n createdAt\n commitId\n ...AutomateFunctionPageParametersDialog_AutomateFunctionRelease\n }\n }\n }\n": types.AutomateFunctionPageInfo_AutomateFunctionFragmentDoc,
|
||||
"\n fragment AutomateFunctionPageParametersDialog_AutomateFunctionRelease on AutomateFunctionRelease {\n id\n inputSchema\n }\n": types.AutomateFunctionPageParametersDialog_AutomateFunctionReleaseFragmentDoc,
|
||||
"\n fragment AutomateFunctionsPageHeader_Query on Query {\n activeUser {\n id\n automateInfo {\n hasAutomateGithubApp\n availableGithubOrgs\n }\n }\n serverInfo {\n automate {\n availableFunctionTemplates {\n ...AutomateFunctionCreateDialogTemplateStep_AutomateFunctionTemplate\n }\n }\n }\n }\n": types.AutomateFunctionsPageHeader_QueryFragmentDoc,
|
||||
"\n fragment AutomateFunctionsPageItems_Query on Query {\n automateFunctions(limit: 20, filter: { search: $search }, cursor: $cursor) {\n totalCount\n items {\n id\n ...AutomationsFunctionsCard_AutomateFunction\n ...AutomateAutomationCreateDialog_AutomateFunction\n }\n cursor\n }\n }\n": types.AutomateFunctionsPageItems_QueryFragmentDoc,
|
||||
"\n fragment AutomateFunctionsPageHeader_Query on Query {\n activeUser {\n id\n role\n automateInfo {\n hasAutomateGithubApp\n availableGithubOrgs\n }\n }\n serverInfo {\n automate {\n availableFunctionTemplates {\n ...AutomateFunctionCreateDialogTemplateStep_AutomateFunctionTemplate\n }\n }\n }\n }\n": types.AutomateFunctionsPageHeader_QueryFragmentDoc,
|
||||
"\n fragment AutomateFunctionsPageItems_Query on Query {\n automateFunctions(limit: 6, filter: { search: $search }, cursor: $cursor) {\n totalCount\n items {\n id\n ...AutomationsFunctionsCard_AutomateFunction\n ...AutomateAutomationCreateDialog_AutomateFunction\n }\n cursor\n }\n }\n": types.AutomateFunctionsPageItems_QueryFragmentDoc,
|
||||
"\n fragment AutomateRunsTriggerStatus_TriggeredAutomationsStatus on TriggeredAutomationsStatus {\n id\n ...TriggeredAutomationsStatusSummary\n ...AutomateRunsTriggerStatusDialog_TriggeredAutomationsStatus\n }\n": types.AutomateRunsTriggerStatus_TriggeredAutomationsStatusFragmentDoc,
|
||||
"\n fragment AutomateRunsTriggerStatusDialog_TriggeredAutomationsStatus on TriggeredAutomationsStatus {\n id\n automationRuns {\n id\n ...AutomateRunsTriggerStatusDialogRunsRows_AutomateRun\n }\n }\n": types.AutomateRunsTriggerStatusDialog_TriggeredAutomationsStatusFragmentDoc,
|
||||
"\n fragment AutomateRunsTriggerStatusDialogFunctionRun_AutomateFunctionRun on AutomateFunctionRun {\n id\n results\n status\n statusMessage\n contextView\n function {\n id\n logo\n name\n }\n createdAt\n updatedAt\n }\n": types.AutomateRunsTriggerStatusDialogFunctionRun_AutomateFunctionRunFragmentDoc,
|
||||
@@ -43,6 +44,7 @@ const documents = {
|
||||
"\n fragment BillingAlert_Workspace on Workspace {\n id\n plan {\n name\n status\n createdAt\n }\n subscription {\n billingInterval\n currentBillingCycleEnd\n }\n }\n": types.BillingAlert_WorkspaceFragmentDoc,
|
||||
"\n fragment CommonModelSelectorModel on Model {\n id\n name\n }\n": types.CommonModelSelectorModelFragmentDoc,
|
||||
"\n fragment DashboardProjectCard_Project on Project {\n id\n name\n role\n updatedAt\n models {\n totalCount\n }\n team {\n user {\n ...LimitedUserAvatar\n }\n }\n workspace {\n id\n slug\n name\n ...WorkspaceAvatar_Workspace\n }\n }\n": types.DashboardProjectCard_ProjectFragmentDoc,
|
||||
"\n fragment Sidebar_User on User {\n id\n automateFunctions {\n items {\n id\n name\n description\n logo\n }\n }\n }\n": types.Sidebar_UserFragmentDoc,
|
||||
"\n fragment FormSelectModels_Model on Model {\n id\n name\n }\n": types.FormSelectModels_ModelFragmentDoc,
|
||||
"\n fragment FormSelectProjects_Project on Project {\n id\n name\n }\n": types.FormSelectProjects_ProjectFragmentDoc,
|
||||
"\n fragment FormUsersSelectItem on LimitedUser {\n id\n name\n avatar\n }\n": types.FormUsersSelectItemFragmentDoc,
|
||||
@@ -61,9 +63,10 @@ const documents = {
|
||||
"\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 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 ...ProjectPageModelsCardProject\n }\n": types.ProjectPageAutomationHeader_ProjectFragmentDoc,
|
||||
"\n fragment ProjectPageAutomationHeader_Project on Project {\n id\n role\n ...ProjectPageModelsCardProject\n }\n": types.ProjectPageAutomationHeader_ProjectFragmentDoc,
|
||||
"\n fragment ProjectPageAutomationModels_Project on Project {\n id\n ...ProjectPageModelsCardProject\n }\n": types.ProjectPageAutomationModels_ProjectFragmentDoc,
|
||||
"\n fragment ProjectPageAutomationRuns_Automation on Automation {\n id\n name\n enabled\n isTestAutomation\n runs(limit: 10) {\n items {\n ...AutomationRunDetails\n }\n totalCount\n cursor\n }\n }\n": types.ProjectPageAutomationRuns_AutomationFragmentDoc,
|
||||
"\n fragment ProjectPageAutomationsEmptyState_Query on Query {\n automateFunctions(limit: 9, filter: { featuredFunctionsOnly: true }) {\n items {\n ...AutomationsFunctionsCard_AutomateFunction\n ...AutomateAutomationCreateDialog_AutomateFunction\n }\n }\n }\n": types.ProjectPageAutomationsEmptyState_QueryFragmentDoc,
|
||||
"\n fragment ProjectPageAutomationsEmptyState_Query on Query {\n automateFunctions(limit: 9) {\n items {\n ...AutomationsFunctionsCard_AutomateFunction\n ...AutomateAutomationCreateDialog_AutomateFunction\n }\n }\n }\n": types.ProjectPageAutomationsEmptyState_QueryFragmentDoc,
|
||||
"\n fragment ProjectPageAutomationsRow_Automation on Automation {\n id\n name\n enabled\n isTestAutomation\n currentRevision {\n id\n triggerDefinitions {\n ... on VersionCreatedTriggerDefinition {\n model {\n id\n name\n }\n }\n }\n }\n runs(limit: 10) {\n totalCount\n items {\n ...AutomationRunDetails\n }\n cursor\n }\n }\n": types.ProjectPageAutomationsRow_AutomationFragmentDoc,
|
||||
"\n fragment ProjectDiscussionsPageHeader_Project on Project {\n id\n name\n }\n": types.ProjectDiscussionsPageHeader_ProjectFragmentDoc,
|
||||
"\n fragment ProjectDiscussionsPageResults_Project on Project {\n id\n }\n": types.ProjectDiscussionsPageResults_ProjectFragmentDoc,
|
||||
@@ -168,6 +171,7 @@ const documents = {
|
||||
"\n query FunctionAccessCheck($id: ID!) {\n automateFunction(id: $id) {\n id\n }\n }\n": types.FunctionAccessCheckDocument,
|
||||
"\n query ProjectAutomationCreationPublicKeys(\n $projectId: String!\n $automationId: String!\n ) {\n project(id: $projectId) {\n id\n automation(id: $automationId) {\n id\n creationPublicKeys\n }\n }\n }\n": types.ProjectAutomationCreationPublicKeysDocument,
|
||||
"\n query AutomateFunctionsPagePagination($search: String, $cursor: String) {\n ...AutomateFunctionsPageItems_Query\n }\n": types.AutomateFunctionsPagePaginationDocument,
|
||||
"\n query ActiveUserFunctions {\n activeUser {\n automateFunctions(limit: 2) {\n items {\n id\n ...AutomationsFunctionsCard_AutomateFunction\n }\n }\n }\n }\n": types.ActiveUserFunctionsDocument,
|
||||
"\n mutation BillingCreateCheckoutSession($input: CheckoutSessionInput!) {\n workspaceMutations {\n billing {\n createCheckoutSession(input: $input) {\n url\n id\n }\n }\n }\n }\n": types.BillingCreateCheckoutSessionDocument,
|
||||
"\n mutation BillingUpgradePlan($input: UpgradePlanInput!) {\n workspaceMutations {\n billing {\n upgradePlan(input: $input)\n }\n }\n }\n": types.BillingUpgradePlanDocument,
|
||||
"\n query MentionsUserSearch($query: String!, $emailOnly: Boolean = false) {\n userSearch(\n query: $query\n limit: 5\n cursor: null\n archived: false\n emailOnly: $emailOnly\n ) {\n items {\n id\n name\n company\n }\n }\n }\n": types.MentionsUserSearchDocument,
|
||||
@@ -251,7 +255,7 @@ const documents = {
|
||||
"\n query ProjectModelVersions(\n $projectId: String!\n $modelId: String!\n $versionsCursor: String\n ) {\n project(id: $projectId) {\n id\n ...ProjectModelPageVersionsPagination\n }\n }\n": types.ProjectModelVersionsDocument,
|
||||
"\n query ProjectModelsPage($projectId: String!) {\n project(id: $projectId) {\n id\n ...ProjectModelsPageHeader_Project\n ...ProjectModelsPageResults_Project\n }\n }\n": types.ProjectModelsPageDocument,
|
||||
"\n query ProjectDiscussionsPage($projectId: String!) {\n project(id: $projectId) {\n id\n ...ProjectDiscussionsPageHeader_Project\n ...ProjectDiscussionsPageResults_Project\n }\n }\n": types.ProjectDiscussionsPageDocument,
|
||||
"\n query ProjectAutomationsTab($projectId: String!) {\n project(id: $projectId) {\n id\n models(limit: 1) {\n items {\n id\n }\n }\n automations(filter: null, cursor: null, limit: 5) {\n totalCount\n items {\n id\n ...ProjectPageAutomationsRow_Automation\n }\n cursor\n }\n ...FormSelectProjects_Project\n }\n ...ProjectPageAutomationsEmptyState_Query\n }\n": types.ProjectAutomationsTabDocument,
|
||||
"\n query ProjectAutomationsTab($projectId: String!) {\n project(id: $projectId) {\n id\n role\n models(limit: 1) {\n items {\n id\n }\n }\n automations(filter: null, cursor: null, limit: 5) {\n totalCount\n items {\n id\n ...ProjectPageAutomationsRow_Automation\n }\n cursor\n }\n workspace {\n id\n slug\n }\n ...FormSelectProjects_Project\n }\n ...ProjectPageAutomationsEmptyState_Query\n }\n": types.ProjectAutomationsTabDocument,
|
||||
"\n query ProjectAutomationsTabAutomationsPagination(\n $projectId: String!\n $search: String = null\n $cursor: String = null\n ) {\n project(id: $projectId) {\n id\n automations(filter: $search, cursor: $cursor, limit: 5) {\n totalCount\n cursor\n items {\n id\n ...ProjectPageAutomationsRow_Automation\n }\n }\n }\n }\n": types.ProjectAutomationsTabAutomationsPaginationDocument,
|
||||
"\n query ProjectAutomationPage($projectId: String!, $automationId: String!) {\n project(id: $projectId) {\n id\n ...ProjectPageAutomationPage_Project\n automation(id: $automationId) {\n id\n ...ProjectPageAutomationPage_Automation\n }\n }\n }\n": types.ProjectAutomationPageDocument,
|
||||
"\n query ProjectAutomationPagePaginatedRuns(\n $projectId: String!\n $automationId: String!\n $cursor: String = null\n ) {\n project(id: $projectId) {\n id\n automation(id: $automationId) {\n id\n runs(cursor: $cursor, limit: 10) {\n totalCount\n cursor\n items {\n id\n ...AutomationRunDetails\n }\n }\n }\n }\n }\n": types.ProjectAutomationPagePaginatedRunsDocument,
|
||||
@@ -296,6 +300,7 @@ const documents = {
|
||||
"\n mutation SettingsLeaveWorkspace($leaveId: ID!) {\n workspaceMutations {\n leave(id: $leaveId)\n }\n }\n": types.SettingsLeaveWorkspaceDocument,
|
||||
"\n mutation SettingsBillingCancelCheckoutSession($input: CancelCheckoutSessionInput!) {\n workspaceMutations {\n billing {\n cancelCheckoutSession(input: $input)\n }\n }\n }\n": types.SettingsBillingCancelCheckoutSessionDocument,
|
||||
"\n query SettingsSidebar {\n activeUser {\n ...SettingsDialog_User\n }\n }\n": types.SettingsSidebarDocument,
|
||||
"\n query SettingsSidebarAutomateFunctions {\n activeUser {\n ...Sidebar_User\n }\n }\n": types.SettingsSidebarAutomateFunctionsDocument,
|
||||
"\n query SettingsWorkspaceGeneral($id: String!) {\n workspace(id: $id) {\n ...SettingsWorkspacesGeneral_Workspace\n }\n }\n": types.SettingsWorkspaceGeneralDocument,
|
||||
"\n query SettingsWorkspaceBilling($workspaceId: String!) {\n workspace(id: $workspaceId) {\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,
|
||||
@@ -345,6 +350,7 @@ const documents = {
|
||||
"\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,
|
||||
"\n query WorkspaceFunctionsQuery($workspaceSlug: String!) {\n ...AutomateFunctionsPageHeader_Query\n workspaceBySlug(slug: $workspaceSlug) {\n id\n name\n automateFunctions {\n items {\n id\n ...AutomationsFunctionsCard_AutomateFunction\n ...AutomateAutomationCreateDialog_AutomateFunction\n }\n }\n }\n }\n": types.WorkspaceFunctionsQueryDocument,
|
||||
"\n query WorkspaceInvite(\n $workspaceId: String\n $token: String\n $options: WorkspaceInviteLookupOptions\n ) {\n workspaceInvite(workspaceId: $workspaceId, token: $token, options: $options) {\n ...WorkspaceInviteBanner_PendingWorkspaceCollaborator\n ...WorkspaceInviteBlock_PendingWorkspaceCollaborator\n }\n }\n": types.WorkspaceInviteDocument,
|
||||
"\n query MoveProjectsDialog {\n activeUser {\n ...MoveProjectsDialog_User\n }\n }\n": types.MoveProjectsDialogDocument,
|
||||
"\n query ValidateWorkspaceSlug($slug: String!) {\n validateWorkspaceSlug(slug: $slug)\n }\n": types.ValidateWorkspaceSlugDocument,
|
||||
@@ -357,7 +363,7 @@ const documents = {
|
||||
"\n query AutoAcceptableWorkspaceInvite(\n $token: String!\n $workspaceId: String!\n $options: WorkspaceInviteLookupOptions\n ) {\n workspaceInvite(token: $token, workspaceId: $workspaceId, options: $options) {\n id\n ...UseWorkspaceInviteManager_PendingWorkspaceCollaborator\n }\n }\n": types.AutoAcceptableWorkspaceInviteDocument,
|
||||
"\n query ResolveCommentLink($commentId: String!, $projectId: String!) {\n project(id: $projectId) {\n comment(id: $commentId) {\n id\n ...LinkableComment\n }\n }\n }\n": types.ResolveCommentLinkDocument,
|
||||
"\n fragment AutomateFunctionPage_AutomateFunction on AutomateFunction {\n id\n name\n description\n logo\n supportedSourceApps\n tags\n ...AutomateFunctionPageHeader_Function\n ...AutomateFunctionPageInfo_AutomateFunction\n ...AutomateAutomationCreateDialog_AutomateFunction\n creator {\n id\n }\n }\n": types.AutomateFunctionPage_AutomateFunctionFragmentDoc,
|
||||
"\n query AutomateFunctionPage($functionId: ID!) {\n automateFunction(id: $functionId) {\n ...AutomateFunctionPage_AutomateFunction\n }\n }\n": types.AutomateFunctionPageDocument,
|
||||
"\n query AutomateFunctionPage($functionId: ID!) {\n automateFunction(id: $functionId) {\n ...AutomateFunctionPage_AutomateFunction\n }\n activeUser {\n workspaces {\n items {\n ...AutomateFunctionCreateDialog_Workspace\n }\n }\n }\n }\n": types.AutomateFunctionPageDocument,
|
||||
"\n query AutomateFunctionsPage($search: String, $cursor: String = null) {\n ...AutomateFunctionsPageItems_Query\n ...AutomateFunctionsPageHeader_Query\n }\n": types.AutomateFunctionsPageDocument,
|
||||
"\n fragment ProjectPageProject on Project {\n id\n createdAt\n modelCount: models(limit: 0) {\n totalCount\n }\n commentThreadCount: commentThreads(limit: 0) {\n totalCount\n }\n workspace {\n id\n }\n ...ProjectPageTeamInternals_Project\n ...ProjectPageProjectHeader\n ...ProjectPageTeamDialog\n ...ProjectsMoveToWorkspaceDialog_Project\n }\n": types.ProjectPageProjectFragmentDoc,
|
||||
"\n fragment ProjectPageAutomationPage_Automation on Automation {\n id\n ...ProjectPageAutomationHeader_Automation\n ...ProjectPageAutomationFunctions_Automation\n ...ProjectPageAutomationRuns_Automation\n }\n": types.ProjectPageAutomationPage_AutomationFragmentDoc,
|
||||
@@ -426,11 +432,15 @@ export function graphql(source: "\n fragment AutomateAutomationCreateDialogFunc
|
||||
/**
|
||||
* 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 AutomationCreateDialogFunctionsSearch($search: String, $cursor: String = null) {\n automateFunctions(limit: 20, filter: { search: $search }, cursor: $cursor) {\n cursor\n totalCount\n items {\n id\n ...AutomateAutomationCreateDialog_AutomateFunction\n }\n }\n }\n"): (typeof documents)["\n query AutomationCreateDialogFunctionsSearch($search: String, $cursor: String = null) {\n automateFunctions(limit: 20, filter: { search: $search }, cursor: $cursor) {\n cursor\n totalCount\n items {\n id\n ...AutomateAutomationCreateDialog_AutomateFunction\n }\n }\n }\n"];
|
||||
export function graphql(source: "\n query AutomationCreateDialogFunctionsSearch(\n $workspaceId: String!\n $search: String\n $cursor: String = null\n ) {\n workspace(id: $workspaceId) {\n automateFunctions(limit: 20, filter: { search: $search }, cursor: $cursor) {\n cursor\n totalCount\n items {\n id\n ...AutomateAutomationCreateDialog_AutomateFunction\n }\n }\n }\n }\n"): (typeof documents)["\n query AutomationCreateDialogFunctionsSearch(\n $workspaceId: String!\n $search: String\n $cursor: String = null\n ) {\n workspace(id: $workspaceId) {\n automateFunctions(limit: 20, filter: { search: $search }, cursor: $cursor) {\n cursor\n totalCount\n items {\n id\n ...AutomateAutomationCreateDialog_AutomateFunction\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 fragment AutomationsFunctionsCard_AutomateFunction on AutomateFunction {\n id\n name\n isFeatured\n description\n logo\n repo {\n id\n url\n owner\n name\n }\n }\n"): (typeof documents)["\n fragment AutomationsFunctionsCard_AutomateFunction on AutomateFunction {\n id\n name\n isFeatured\n description\n logo\n repo {\n id\n url\n owner\n name\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 AutomateFunctionCreateDialog_Workspace on Workspace {\n id\n name\n }\n"): (typeof documents)["\n fragment AutomateFunctionCreateDialog_Workspace on Workspace {\n id\n name\n }\n"];
|
||||
/**
|
||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||
*/
|
||||
@@ -442,7 +452,7 @@ export function graphql(source: "\n fragment AutomateFunctionCreateDialogTempla
|
||||
/**
|
||||
* 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 AutomateFunctionPageHeader_Function on AutomateFunction {\n id\n name\n logo\n repo {\n id\n url\n owner\n name\n }\n releases(limit: 1) {\n totalCount\n }\n }\n"): (typeof documents)["\n fragment AutomateFunctionPageHeader_Function on AutomateFunction {\n id\n name\n logo\n repo {\n id\n url\n owner\n name\n }\n releases(limit: 1) {\n totalCount\n }\n }\n"];
|
||||
export function graphql(source: "\n fragment AutomateFunctionPageHeader_Function on AutomateFunction {\n id\n name\n logo\n repo {\n id\n url\n owner\n name\n }\n releases(limit: 1) {\n totalCount\n }\n workspaceIds\n }\n"): (typeof documents)["\n fragment AutomateFunctionPageHeader_Function on AutomateFunction {\n id\n name\n logo\n repo {\n id\n url\n owner\n name\n }\n releases(limit: 1) {\n totalCount\n }\n workspaceIds\n }\n"];
|
||||
/**
|
||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||
*/
|
||||
@@ -454,11 +464,11 @@ export function graphql(source: "\n fragment AutomateFunctionPageParametersDial
|
||||
/**
|
||||
* 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 AutomateFunctionsPageHeader_Query on Query {\n activeUser {\n id\n automateInfo {\n hasAutomateGithubApp\n availableGithubOrgs\n }\n }\n serverInfo {\n automate {\n availableFunctionTemplates {\n ...AutomateFunctionCreateDialogTemplateStep_AutomateFunctionTemplate\n }\n }\n }\n }\n"): (typeof documents)["\n fragment AutomateFunctionsPageHeader_Query on Query {\n activeUser {\n id\n automateInfo {\n hasAutomateGithubApp\n availableGithubOrgs\n }\n }\n serverInfo {\n automate {\n availableFunctionTemplates {\n ...AutomateFunctionCreateDialogTemplateStep_AutomateFunctionTemplate\n }\n }\n }\n }\n"];
|
||||
export function graphql(source: "\n fragment AutomateFunctionsPageHeader_Query on Query {\n activeUser {\n id\n role\n automateInfo {\n hasAutomateGithubApp\n availableGithubOrgs\n }\n }\n serverInfo {\n automate {\n availableFunctionTemplates {\n ...AutomateFunctionCreateDialogTemplateStep_AutomateFunctionTemplate\n }\n }\n }\n }\n"): (typeof documents)["\n fragment AutomateFunctionsPageHeader_Query on Query {\n activeUser {\n id\n role\n automateInfo {\n hasAutomateGithubApp\n availableGithubOrgs\n }\n }\n serverInfo {\n automate {\n availableFunctionTemplates {\n ...AutomateFunctionCreateDialogTemplateStep_AutomateFunctionTemplate\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 fragment AutomateFunctionsPageItems_Query on Query {\n automateFunctions(limit: 20, filter: { search: $search }, cursor: $cursor) {\n totalCount\n items {\n id\n ...AutomationsFunctionsCard_AutomateFunction\n ...AutomateAutomationCreateDialog_AutomateFunction\n }\n cursor\n }\n }\n"): (typeof documents)["\n fragment AutomateFunctionsPageItems_Query on Query {\n automateFunctions(limit: 20, filter: { search: $search }, cursor: $cursor) {\n totalCount\n items {\n id\n ...AutomationsFunctionsCard_AutomateFunction\n ...AutomateAutomationCreateDialog_AutomateFunction\n }\n cursor\n }\n }\n"];
|
||||
export function graphql(source: "\n fragment AutomateFunctionsPageItems_Query on Query {\n automateFunctions(limit: 6, filter: { search: $search }, cursor: $cursor) {\n totalCount\n items {\n id\n ...AutomationsFunctionsCard_AutomateFunction\n ...AutomateAutomationCreateDialog_AutomateFunction\n }\n cursor\n }\n }\n"): (typeof documents)["\n fragment AutomateFunctionsPageItems_Query on Query {\n automateFunctions(limit: 6, filter: { search: $search }, cursor: $cursor) {\n totalCount\n items {\n id\n ...AutomationsFunctionsCard_AutomateFunction\n ...AutomateAutomationCreateDialog_AutomateFunction\n }\n cursor\n }\n }\n"];
|
||||
/**
|
||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||
*/
|
||||
@@ -495,6 +505,10 @@ export function graphql(source: "\n fragment CommonModelSelectorModel on Model
|
||||
* 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 DashboardProjectCard_Project on Project {\n id\n name\n role\n updatedAt\n models {\n totalCount\n }\n team {\n user {\n ...LimitedUserAvatar\n }\n }\n workspace {\n id\n slug\n name\n ...WorkspaceAvatar_Workspace\n }\n }\n"): (typeof documents)["\n fragment DashboardProjectCard_Project on Project {\n id\n name\n role\n updatedAt\n models {\n totalCount\n }\n team {\n user {\n ...LimitedUserAvatar\n }\n }\n workspace {\n id\n slug\n name\n ...WorkspaceAvatar_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 fragment Sidebar_User on User {\n id\n automateFunctions {\n items {\n id\n name\n description\n logo\n }\n }\n }\n"): (typeof documents)["\n fragment Sidebar_User on User {\n id\n automateFunctions {\n items {\n id\n name\n description\n logo\n }\n }\n }\n"];
|
||||
/**
|
||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||
*/
|
||||
@@ -570,7 +584,11 @@ export function graphql(source: "\n fragment ProjectPageAutomationHeader_Automa
|
||||
/**
|
||||
* 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 ProjectPageAutomationHeader_Project on Project {\n id\n ...ProjectPageModelsCardProject\n }\n"): (typeof documents)["\n fragment ProjectPageAutomationHeader_Project on Project {\n id\n ...ProjectPageModelsCardProject\n }\n"];
|
||||
export function graphql(source: "\n fragment ProjectPageAutomationHeader_Project on Project {\n id\n role\n ...ProjectPageModelsCardProject\n }\n"): (typeof documents)["\n fragment ProjectPageAutomationHeader_Project on Project {\n id\n role\n ...ProjectPageModelsCardProject\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 ProjectPageAutomationModels_Project on Project {\n id\n ...ProjectPageModelsCardProject\n }\n"): (typeof documents)["\n fragment ProjectPageAutomationModels_Project on Project {\n id\n ...ProjectPageModelsCardProject\n }\n"];
|
||||
/**
|
||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||
*/
|
||||
@@ -578,7 +596,7 @@ export function graphql(source: "\n fragment ProjectPageAutomationRuns_Automati
|
||||
/**
|
||||
* 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 ProjectPageAutomationsEmptyState_Query on Query {\n automateFunctions(limit: 9, filter: { featuredFunctionsOnly: true }) {\n items {\n ...AutomationsFunctionsCard_AutomateFunction\n ...AutomateAutomationCreateDialog_AutomateFunction\n }\n }\n }\n"): (typeof documents)["\n fragment ProjectPageAutomationsEmptyState_Query on Query {\n automateFunctions(limit: 9, filter: { featuredFunctionsOnly: true }) {\n items {\n ...AutomationsFunctionsCard_AutomateFunction\n ...AutomateAutomationCreateDialog_AutomateFunction\n }\n }\n }\n"];
|
||||
export function graphql(source: "\n fragment ProjectPageAutomationsEmptyState_Query on Query {\n automateFunctions(limit: 9) {\n items {\n ...AutomationsFunctionsCard_AutomateFunction\n ...AutomateAutomationCreateDialog_AutomateFunction\n }\n }\n }\n"): (typeof documents)["\n fragment ProjectPageAutomationsEmptyState_Query on Query {\n automateFunctions(limit: 9) {\n items {\n ...AutomationsFunctionsCard_AutomateFunction\n ...AutomateAutomationCreateDialog_AutomateFunction\n }\n }\n }\n"];
|
||||
/**
|
||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||
*/
|
||||
@@ -995,6 +1013,10 @@ export function graphql(source: "\n query ProjectAutomationCreationPublicKeys(\
|
||||
* 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 AutomateFunctionsPagePagination($search: String, $cursor: String) {\n ...AutomateFunctionsPageItems_Query\n }\n"): (typeof documents)["\n query AutomateFunctionsPagePagination($search: String, $cursor: String) {\n ...AutomateFunctionsPageItems_Query\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 ActiveUserFunctions {\n activeUser {\n automateFunctions(limit: 2) {\n items {\n id\n ...AutomationsFunctionsCard_AutomateFunction\n }\n }\n }\n }\n"): (typeof documents)["\n query ActiveUserFunctions {\n activeUser {\n automateFunctions(limit: 2) {\n items {\n id\n ...AutomationsFunctionsCard_AutomateFunction\n }\n }\n }\n }\n"];
|
||||
/**
|
||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||
*/
|
||||
@@ -1330,7 +1352,7 @@ export function graphql(source: "\n query ProjectDiscussionsPage($projectId: St
|
||||
/**
|
||||
* 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 ProjectAutomationsTab($projectId: String!) {\n project(id: $projectId) {\n id\n models(limit: 1) {\n items {\n id\n }\n }\n automations(filter: null, cursor: null, limit: 5) {\n totalCount\n items {\n id\n ...ProjectPageAutomationsRow_Automation\n }\n cursor\n }\n ...FormSelectProjects_Project\n }\n ...ProjectPageAutomationsEmptyState_Query\n }\n"): (typeof documents)["\n query ProjectAutomationsTab($projectId: String!) {\n project(id: $projectId) {\n id\n models(limit: 1) {\n items {\n id\n }\n }\n automations(filter: null, cursor: null, limit: 5) {\n totalCount\n items {\n id\n ...ProjectPageAutomationsRow_Automation\n }\n cursor\n }\n ...FormSelectProjects_Project\n }\n ...ProjectPageAutomationsEmptyState_Query\n }\n"];
|
||||
export function graphql(source: "\n query ProjectAutomationsTab($projectId: String!) {\n project(id: $projectId) {\n id\n role\n models(limit: 1) {\n items {\n id\n }\n }\n automations(filter: null, cursor: null, limit: 5) {\n totalCount\n items {\n id\n ...ProjectPageAutomationsRow_Automation\n }\n cursor\n }\n workspace {\n id\n slug\n }\n ...FormSelectProjects_Project\n }\n ...ProjectPageAutomationsEmptyState_Query\n }\n"): (typeof documents)["\n query ProjectAutomationsTab($projectId: String!) {\n project(id: $projectId) {\n id\n role\n models(limit: 1) {\n items {\n id\n }\n }\n automations(filter: null, cursor: null, limit: 5) {\n totalCount\n items {\n id\n ...ProjectPageAutomationsRow_Automation\n }\n cursor\n }\n workspace {\n id\n slug\n }\n ...FormSelectProjects_Project\n }\n ...ProjectPageAutomationsEmptyState_Query\n }\n"];
|
||||
/**
|
||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||
*/
|
||||
@@ -1507,6 +1529,10 @@ export function graphql(source: "\n mutation SettingsBillingCancelCheckoutSessi
|
||||
* 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 SettingsSidebar {\n activeUser {\n ...SettingsDialog_User\n }\n }\n"): (typeof documents)["\n query SettingsSidebar {\n activeUser {\n ...SettingsDialog_User\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 SettingsSidebarAutomateFunctions {\n activeUser {\n ...Sidebar_User\n }\n }\n"): (typeof documents)["\n query SettingsSidebarAutomateFunctions {\n activeUser {\n ...Sidebar_User\n }\n }\n"];
|
||||
/**
|
||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||
*/
|
||||
@@ -1703,6 +1729,10 @@ export function graphql(source: "\n query WorkspacePageQuery(\n $workspaceSl
|
||||
* 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 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"): (typeof documents)["\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"];
|
||||
/**
|
||||
* 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 WorkspaceFunctionsQuery($workspaceSlug: String!) {\n ...AutomateFunctionsPageHeader_Query\n workspaceBySlug(slug: $workspaceSlug) {\n id\n name\n automateFunctions {\n items {\n id\n ...AutomationsFunctionsCard_AutomateFunction\n ...AutomateAutomationCreateDialog_AutomateFunction\n }\n }\n }\n }\n"): (typeof documents)["\n query WorkspaceFunctionsQuery($workspaceSlug: String!) {\n ...AutomateFunctionsPageHeader_Query\n workspaceBySlug(slug: $workspaceSlug) {\n id\n name\n automateFunctions {\n items {\n id\n ...AutomationsFunctionsCard_AutomateFunction\n ...AutomateAutomationCreateDialog_AutomateFunction\n }\n }\n }\n }\n"];
|
||||
/**
|
||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||
*/
|
||||
@@ -1754,7 +1784,7 @@ export function graphql(source: "\n fragment AutomateFunctionPage_AutomateFunct
|
||||
/**
|
||||
* 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 AutomateFunctionPage($functionId: ID!) {\n automateFunction(id: $functionId) {\n ...AutomateFunctionPage_AutomateFunction\n }\n }\n"): (typeof documents)["\n query AutomateFunctionPage($functionId: ID!) {\n automateFunction(id: $functionId) {\n ...AutomateFunctionPage_AutomateFunction\n }\n }\n"];
|
||||
export function graphql(source: "\n query AutomateFunctionPage($functionId: ID!) {\n automateFunction(id: $functionId) {\n ...AutomateFunctionPage_AutomateFunction\n }\n activeUser {\n workspaces {\n items {\n ...AutomateFunctionCreateDialog_Workspace\n }\n }\n }\n }\n"): (typeof documents)["\n query AutomateFunctionPage($functionId: ID!) {\n automateFunction(id: $functionId) {\n ...AutomateFunctionPage_AutomateFunction\n }\n activeUser {\n workspaces {\n items {\n ...AutomateFunctionCreateDialog_Workspace\n }\n }\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
@@ -71,6 +71,8 @@ export const automationFunctionRoute = (functionId: string) =>
|
||||
|
||||
export const workspaceRoute = (slug: string) => `/workspaces/${slug}`
|
||||
|
||||
export const workspaceFunctionsRoute = (slug: string) => `/workspaces/${slug}/functions`
|
||||
|
||||
const buildNavigationComposable = (route: string) => () => {
|
||||
const router = useRouter()
|
||||
return (params?: { query?: LocationQueryRaw }) => {
|
||||
|
||||
@@ -235,6 +235,7 @@ export const projectAutomationsTabQuery = graphql(`
|
||||
query ProjectAutomationsTab($projectId: String!) {
|
||||
project(id: $projectId) {
|
||||
id
|
||||
role
|
||||
models(limit: 1) {
|
||||
items {
|
||||
id
|
||||
@@ -248,6 +249,10 @@ export const projectAutomationsTabQuery = graphql(`
|
||||
}
|
||||
cursor
|
||||
}
|
||||
workspace {
|
||||
id
|
||||
slug
|
||||
}
|
||||
...FormSelectProjects_Project
|
||||
}
|
||||
...ProjectPageAutomationsEmptyState_Query
|
||||
|
||||
@@ -8,6 +8,14 @@ export const settingsSidebarQuery = graphql(`
|
||||
}
|
||||
`)
|
||||
|
||||
export const settingsSidebarAutomateFunctionsQuery = graphql(`
|
||||
query SettingsSidebarAutomateFunctions {
|
||||
activeUser {
|
||||
...Sidebar_User
|
||||
}
|
||||
}
|
||||
`)
|
||||
|
||||
export const settingsWorkspaceGeneralQuery = graphql(`
|
||||
query SettingsWorkspaceGeneral($id: String!) {
|
||||
workspace(id: $id) {
|
||||
|
||||
@@ -44,6 +44,23 @@ export const workspaceProjectsQuery = graphql(`
|
||||
}
|
||||
`)
|
||||
|
||||
export const workspaceFunctionsQuery = graphql(`
|
||||
query WorkspaceFunctionsQuery($workspaceSlug: String!) {
|
||||
...AutomateFunctionsPageHeader_Query
|
||||
workspaceBySlug(slug: $workspaceSlug) {
|
||||
id
|
||||
name
|
||||
automateFunctions {
|
||||
items {
|
||||
id
|
||||
...AutomationsFunctionsCard_AutomateFunction
|
||||
...AutomateAutomationCreateDialog_AutomateFunction
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`)
|
||||
|
||||
export const workspaceInviteQuery = graphql(`
|
||||
query WorkspaceInvite(
|
||||
$workspaceId: String
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
v-if="editModel"
|
||||
v-model:open="showEditDialog"
|
||||
:model="editModel"
|
||||
:workspaces="activeUserWorkspaces"
|
||||
:fn-id="fn.id"
|
||||
/>
|
||||
</template>
|
||||
@@ -57,6 +58,13 @@ const pageQuery = graphql(`
|
||||
automateFunction(id: $functionId) {
|
||||
...AutomateFunctionPage_AutomateFunction
|
||||
}
|
||||
activeUser {
|
||||
workspaces {
|
||||
items {
|
||||
...AutomateFunctionCreateDialog_Workspace
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`)
|
||||
|
||||
@@ -92,6 +100,9 @@ const isOwner = computed(
|
||||
activeUser.value.id === fn.value.creator.id
|
||||
)
|
||||
)
|
||||
const activeUserWorkspaces = computed(
|
||||
() => result.value?.activeUser?.workspaces.items ?? []
|
||||
)
|
||||
|
||||
const { html: plaintextDescription } = useMarkdown(
|
||||
computed(() => fn.value?.description || ''),
|
||||
@@ -102,6 +113,8 @@ const editModel = computed((): Optional<FunctionDetailsFormValues> => {
|
||||
const func = fn.value
|
||||
if (!func) return undefined
|
||||
|
||||
const workspaceId = func.workspaceIds?.at(0)
|
||||
|
||||
return {
|
||||
name: func.name,
|
||||
description: func.description,
|
||||
@@ -109,7 +122,10 @@ const editModel = computed((): Optional<FunctionDetailsFormValues> => {
|
||||
allowedSourceApps: SourceApps.filter((app) =>
|
||||
func.supportedSourceApps.includes(app.name)
|
||||
),
|
||||
tags: func.tags
|
||||
tags: func.tags,
|
||||
workspace: activeUserWorkspaces.value.find(
|
||||
(workspace) => workspace.id === workspaceId
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@@ -1,12 +1,24 @@
|
||||
<template>
|
||||
<div>
|
||||
<AutomateFunctionsPageHeader
|
||||
v-model:search="search"
|
||||
:active-user="result?.activeUser"
|
||||
:server-info="result?.serverInfo"
|
||||
class="mb-6"
|
||||
/>
|
||||
<CommonLoadingBar :loading="pageQueryLoading" client-only class="mb-2" />
|
||||
<Portal to="navigation">
|
||||
<HeaderNavLink
|
||||
:to="automationFunctionsRoute"
|
||||
name="Functions"
|
||||
:separator="false"
|
||||
/>
|
||||
</Portal>
|
||||
<div class="flex flex-col gap-4">
|
||||
<div class="flex items-center gap-2 mb-2">
|
||||
<IconBolt class="h-5 w-5" />
|
||||
<h1 class="text-heading-lg">Functions</h1>
|
||||
</div>
|
||||
<AutomateFunctionsPageHeader
|
||||
v-model:search="search"
|
||||
:active-user="result?.activeUser"
|
||||
:server-info="result?.serverInfo"
|
||||
class="mb-6"
|
||||
/>
|
||||
</div>
|
||||
<AutomateFunctionsPageItems
|
||||
:functions="finalResult"
|
||||
:search="!!search"
|
||||
@@ -14,8 +26,8 @@
|
||||
@create-automation-from="openCreateNewAutomation"
|
||||
@clear-search="search = ''"
|
||||
/>
|
||||
<CommonLoadingBar :loading="pageQueryLoading" client-only class="mb-2" />
|
||||
<InfiniteLoading :settings="{ identifier }" @infinite="onInfiniteLoad" />
|
||||
|
||||
<AutomateAutomationCreateDialog
|
||||
v-model:open="showNewAutomationDialog"
|
||||
:preselected-function="newAutomationTargetFn"
|
||||
@@ -32,6 +44,7 @@ import {
|
||||
usePaginatedQuery
|
||||
} from '~/lib/common/composables/graphql'
|
||||
import { graphql } from '~/lib/common/generated/gql'
|
||||
import { automationFunctionsRoute } from '~/lib/common/helpers/route'
|
||||
|
||||
definePageMeta({
|
||||
middleware: ['auth', 'requires-automate-enabled']
|
||||
@@ -81,7 +94,11 @@ const {
|
||||
const showNewAutomationDialog = ref(false)
|
||||
const newAutomationTargetFn = ref<CreateAutomationSelectableFunction>()
|
||||
|
||||
const finalResult = computed(() => paginatedResult.value || result.value)
|
||||
const finalResult = computed(
|
||||
() =>
|
||||
paginatedResult.value?.automateFunctions.items ||
|
||||
result.value?.automateFunctions.items
|
||||
)
|
||||
|
||||
const openCreateNewAutomation = (fn: CreateAutomationSelectableFunction) => {
|
||||
newAutomationTargetFn.value = fn
|
||||
|
||||
@@ -212,7 +212,7 @@ const pageTabItems = computed((): LayoutPageTabItem[] => {
|
||||
}
|
||||
]
|
||||
|
||||
if (isOwner.value && isAutomateEnabled.value) {
|
||||
if (isAutomateEnabled.value && project.value?.workspace) {
|
||||
items.push({
|
||||
title: 'Automations',
|
||||
id: 'automations',
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
<template>
|
||||
<div v-if="automation && project" class="flex flex-col gap-8 items-start">
|
||||
<ProjectPageAutomationHeader :automation="automation" :project="project" />
|
||||
<ProjectPageAutomationHeader
|
||||
:automation="automation"
|
||||
:project="project"
|
||||
:is-editable="isEditable"
|
||||
/>
|
||||
|
||||
<div class="grid grid-cols-1 xl:grid-cols-4 gap-6 w-full">
|
||||
<div
|
||||
@@ -9,6 +13,7 @@
|
||||
<ProjectPageAutomationFunctions
|
||||
:automation="automation"
|
||||
:project-id="projectId"
|
||||
:is-editable="isEditable"
|
||||
/>
|
||||
<ProjectPageAutomationModels :automation="automation" :project="project" />
|
||||
</div>
|
||||
@@ -16,6 +21,7 @@
|
||||
class="col-span-1 xl:col-span-3"
|
||||
:project-id="projectId"
|
||||
:automation="automation"
|
||||
:is-editable="isEditable"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -23,6 +29,7 @@
|
||||
<div v-else />
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { Roles } from '@speckle/shared'
|
||||
import { useQuery } from '@vue/apollo-composable'
|
||||
import { graphql } from '~/lib/common/generated/gql'
|
||||
import { projectAutomationPageQuery } from '~/lib/projects/graphql/queries'
|
||||
@@ -60,4 +67,8 @@ const { result, loading } = useQuery(
|
||||
)
|
||||
const automation = computed(() => result.value?.project.automation || null)
|
||||
const project = computed(() => result.value?.project)
|
||||
const isEditable = computed(() => {
|
||||
const allowedRoles: string[] = [Roles.Stream.Owner]
|
||||
return allowedRoles.includes(result.value?.project.role ?? '')
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="flex flex-col gap-4">
|
||||
<div class="flex items-center gap-2 mb-2">
|
||||
<IconBolt class="h-5 w-5" />
|
||||
<h1 class="text-heading-lg">Workspace functions</h1>
|
||||
</div>
|
||||
<AutomateFunctionsPageHeader
|
||||
v-model:search="search"
|
||||
:active-user="workspaceFunctionsResult?.activeUser"
|
||||
:server-info="workspaceFunctionsResult?.serverInfo"
|
||||
:workspace="workspace"
|
||||
class="mb-6"
|
||||
/>
|
||||
</div>
|
||||
<AutomateFunctionsPageItems
|
||||
:functions="workspaceFunctions"
|
||||
:search="!!search"
|
||||
:loading="false"
|
||||
@create-automation-from="openCreateNewAutomation"
|
||||
@clear-search="search = ''"
|
||||
/>
|
||||
<CommonLoadingBar :loading="workspaceFunctionsLoading" client-only class="mb-2" />
|
||||
<AutomateAutomationCreateDialog
|
||||
v-model:open="showNewAutomationDialog"
|
||||
:workspace-id="workspace?.id"
|
||||
:preselected-function="newAutomationTargetFn"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { useQuery } from '@vue/apollo-composable'
|
||||
import type { CreateAutomationSelectableFunction } from '~/lib/automate/helpers/automations'
|
||||
import { usePageQueryStandardFetchPolicy } from '~/lib/common/composables/graphql'
|
||||
import { workspaceFunctionsQuery } from '~/lib/workspaces/graphql/queries'
|
||||
|
||||
definePageMeta({
|
||||
middleware: ['auth', 'requires-automate-enabled']
|
||||
})
|
||||
|
||||
const route = useRoute()
|
||||
const workspaceSlug = computed(() => route.params.slug as string)
|
||||
|
||||
const pageFetchPolicy = usePageQueryStandardFetchPolicy()
|
||||
|
||||
const { result: workspaceFunctionsResult, loading: workspaceFunctionsLoading } =
|
||||
useQuery(
|
||||
workspaceFunctionsQuery,
|
||||
() => ({
|
||||
workspaceSlug: workspaceSlug.value
|
||||
}),
|
||||
() => ({
|
||||
fetchPolicy: pageFetchPolicy.value
|
||||
})
|
||||
)
|
||||
|
||||
const workspace = computed(() => {
|
||||
const workspaceData = workspaceFunctionsResult.value?.workspaceBySlug
|
||||
|
||||
return workspaceData
|
||||
? {
|
||||
id: workspaceData.id,
|
||||
name: workspaceData.name,
|
||||
slug: workspaceSlug.value
|
||||
}
|
||||
: undefined
|
||||
})
|
||||
|
||||
const workspaceFunctions = computed(
|
||||
() => workspaceFunctionsResult.value?.workspaceBySlug?.automateFunctions?.items ?? []
|
||||
)
|
||||
|
||||
const search = ref('')
|
||||
|
||||
const showNewAutomationDialog = ref(false)
|
||||
const newAutomationTargetFn = ref<CreateAutomationSelectableFunction>()
|
||||
|
||||
const openCreateNewAutomation = (fn: CreateAutomationSelectableFunction) => {
|
||||
newAutomationTargetFn.value = fn
|
||||
showNewAutomationDialog.value = true
|
||||
}
|
||||
</script>
|
||||
@@ -147,6 +147,12 @@ type AutomateFunction {
|
||||
Only returned if user is a part of this speckle server
|
||||
"""
|
||||
creator: LimitedUser
|
||||
workspaceIds: [String!]
|
||||
}
|
||||
|
||||
type AutomateFunctionToken {
|
||||
functionId: String!
|
||||
functionToken: String!
|
||||
}
|
||||
|
||||
type AutomateFunctionRelease {
|
||||
@@ -212,7 +218,6 @@ input AutomateFunctionsFilter {
|
||||
By default we skip functions without releases. Set this to true to include them.
|
||||
"""
|
||||
functionsWithoutReleases: Boolean
|
||||
featuredFunctionsOnly: Boolean
|
||||
}
|
||||
|
||||
input AutomateFunctionRunStatusReportInput {
|
||||
@@ -246,6 +251,11 @@ input CreateAutomateFunctionInput {
|
||||
org: String
|
||||
}
|
||||
|
||||
input CreateAutomateFunctionWithoutVersionInput {
|
||||
name: String!
|
||||
description: String!
|
||||
}
|
||||
|
||||
"""
|
||||
Any null values will be ignored
|
||||
"""
|
||||
@@ -259,6 +269,7 @@ input UpdateAutomateFunctionInput {
|
||||
supportedSourceApps: [String!]
|
||||
tags: [String!]
|
||||
logo: String
|
||||
workspaceIds: [String!]
|
||||
}
|
||||
|
||||
type UserAutomateInfo {
|
||||
@@ -268,6 +279,11 @@ type UserAutomateInfo {
|
||||
|
||||
extend type User {
|
||||
automateInfo: UserAutomateInfo! @isOwner
|
||||
automateFunctions(
|
||||
filter: AutomateFunctionsFilter
|
||||
cursor: String
|
||||
limit: Int
|
||||
): AutomateFunctionCollection!
|
||||
}
|
||||
|
||||
enum AutomateFunctionTemplateLanguage {
|
||||
@@ -313,22 +329,28 @@ extend type ProjectMutations {
|
||||
type AutomateMutations {
|
||||
createFunction(input: CreateAutomateFunctionInput!): AutomateFunction!
|
||||
@hasScope(scope: "automate-functions:write")
|
||||
createFunctionWithoutVersion(
|
||||
input: CreateAutomateFunctionWithoutVersionInput!
|
||||
): AutomateFunctionToken!
|
||||
@hasScope(scope: "automate-functions:write")
|
||||
@hasServerRole(role: SERVER_ADMIN)
|
||||
updateFunction(input: UpdateAutomateFunctionInput!): AutomateFunction!
|
||||
@hasScope(scope: "automate-functions:write")
|
||||
}
|
||||
|
||||
extend type Project {
|
||||
automations(filter: String, cursor: String, limit: Int): AutomationCollection!
|
||||
@hasStreamRole(role: STREAM_OWNER)
|
||||
@hasStreamRole(role: STREAM_REVIEWER)
|
||||
"""
|
||||
Get a single automation by id. Error will be thrown if automation is not found or inaccessible.
|
||||
"""
|
||||
automation(id: String!): Automation! @hasStreamRole(role: STREAM_OWNER)
|
||||
automation(id: String!): Automation! @hasStreamRole(role: STREAM_REVIEWER)
|
||||
}
|
||||
|
||||
input AutomateAuthCodePayloadTest {
|
||||
code: String!
|
||||
userId: String!
|
||||
workspaceId: String
|
||||
action: String!
|
||||
}
|
||||
|
||||
|
||||
@@ -276,6 +276,11 @@ type Workspace {
|
||||
cursor: String
|
||||
filter: WorkspaceProjectsFilter
|
||||
): ProjectCollection!
|
||||
automateFunctions(
|
||||
limit: Int! = 25
|
||||
cursor: String
|
||||
filter: AutomateFunctionsFilter
|
||||
): AutomateFunctionCollection!
|
||||
"""
|
||||
Information about the workspace's SSO configuration and the current user's SSO session, if present
|
||||
"""
|
||||
|
||||
@@ -63,7 +63,11 @@ const getApiUrl = (
|
||||
if (options?.query) {
|
||||
Object.entries(options.query).forEach(([key, val]) => {
|
||||
if (isNullOrUndefined(val)) return
|
||||
url.searchParams.append(key, val.toString())
|
||||
try {
|
||||
url.searchParams.append(key, val.toString())
|
||||
} catch (e) {
|
||||
console.log({ val })
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -300,6 +304,31 @@ export const createFunction = async ({
|
||||
})
|
||||
}
|
||||
|
||||
type CreateFunctionWithoutVersionBody = {
|
||||
speckleServerAuthenticationPayload: AuthCodePayloadWithOrigin
|
||||
functionName: string
|
||||
description: string
|
||||
}
|
||||
|
||||
type CreateFunctionWithoutVersionResponse = {
|
||||
functionId: string
|
||||
functionToken: string
|
||||
}
|
||||
|
||||
export const createFunctionWithoutVersion = async ({
|
||||
body
|
||||
}: {
|
||||
body: CreateFunctionWithoutVersionBody
|
||||
}): Promise<CreateFunctionWithoutVersionResponse> => {
|
||||
const url = getApiUrl('/api/v2/functions')
|
||||
return await invokeJsonRequest<CreateFunctionWithoutVersionResponse>({
|
||||
url,
|
||||
method: 'post',
|
||||
body,
|
||||
retry: false
|
||||
})
|
||||
}
|
||||
|
||||
export type UpdateFunctionBody<AP extends AuthCodePayload = AuthCodePayloadWithOrigin> =
|
||||
{
|
||||
speckleServerAuthenticationPayload: AP
|
||||
@@ -417,18 +446,21 @@ export type GetFunctionsResponse = {
|
||||
items: FunctionWithVersionsSchemaType[]
|
||||
}
|
||||
|
||||
export const getFunctions = async (params: {
|
||||
export const getPublicFunctions = async (params: {
|
||||
query?: {
|
||||
query?: string
|
||||
cursor?: string
|
||||
limit?: number
|
||||
functionsWithoutVersions?: boolean
|
||||
featuredFunctionsOnly?: boolean
|
||||
}
|
||||
}) => {
|
||||
const { query } = params
|
||||
const url = getApiUrl(`/api/v1/functions`, { query })
|
||||
|
||||
const url = getApiUrl(`/api/v1/functions`, {
|
||||
query: {
|
||||
...query,
|
||||
featuredFunctionsOnly: true
|
||||
}
|
||||
})
|
||||
const result = await invokeJsonRequest<GetFunctionsResponse>({
|
||||
url,
|
||||
method: 'get'
|
||||
@@ -437,6 +469,58 @@ export const getFunctions = async (params: {
|
||||
return result
|
||||
}
|
||||
|
||||
type GetUserFunctionsResponse = {
|
||||
functions: FunctionWithVersionsSchemaType[]
|
||||
}
|
||||
|
||||
export const getUserFunctions = async (params: {
|
||||
userId: string
|
||||
query?: {
|
||||
query?: string
|
||||
cursor?: string
|
||||
limit?: number
|
||||
}
|
||||
body: {
|
||||
speckleServerAuthenticationPayload: AuthCodePayloadWithOrigin
|
||||
}
|
||||
}): Promise<GetUserFunctionsResponse> => {
|
||||
const { userId, query, body } = params
|
||||
const url = getApiUrl(`/api/v2/users/${userId}/functions`, { query })
|
||||
|
||||
return await invokeJsonRequest({
|
||||
url,
|
||||
method: 'POST',
|
||||
body,
|
||||
retry: false
|
||||
})
|
||||
}
|
||||
|
||||
type GetWorkspaceFunctionsResponse = {
|
||||
functions: FunctionWithVersionsSchemaType[]
|
||||
}
|
||||
|
||||
export const getWorkspaceFunctions = async (params: {
|
||||
workspaceId: string
|
||||
query?: {
|
||||
query?: string
|
||||
cursor?: string
|
||||
limit?: number
|
||||
}
|
||||
body: {
|
||||
speckleServerAuthenticationPayload: AuthCodePayloadWithOrigin
|
||||
}
|
||||
}): Promise<GetWorkspaceFunctionsResponse> => {
|
||||
const { workspaceId, query, body } = params
|
||||
const url = getApiUrl(`/api/v2/workspaces/${workspaceId}/functions`, { query })
|
||||
|
||||
return await invokeJsonRequest({
|
||||
url,
|
||||
method: 'POST',
|
||||
body,
|
||||
retry: false
|
||||
})
|
||||
}
|
||||
|
||||
type UserGithubAuthStateResponse = {
|
||||
userHasAuthorizedGitHubApp: boolean
|
||||
}
|
||||
|
||||
@@ -66,16 +66,14 @@ const mocks: SpeckleModuleMocksConfig = FF_AUTOMATE_MODULE_ENABLED
|
||||
version: store.get('Version') as any
|
||||
},
|
||||
Query: {
|
||||
automateFunctions: (_parent, args) => {
|
||||
automateFunctions: () => {
|
||||
const forceZero = false
|
||||
const count = forceZero ? 0 : faker.number.int({ min: 0, max: 20 })
|
||||
|
||||
const isFeatured = args.filter?.featuredFunctionsOnly
|
||||
|
||||
return {
|
||||
cursor: null,
|
||||
totalCount: count,
|
||||
items: times(count, () => store.get('AutomateFunction', { isFeatured }))
|
||||
items: times(count, () => store.get('AutomateFunction'))
|
||||
} as any
|
||||
},
|
||||
automateFunction: (_parent, args) => {
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
import {
|
||||
createFunction,
|
||||
createFunctionWithoutVersion,
|
||||
triggerAutomationRun,
|
||||
updateFunction as execEngineUpdateFunction,
|
||||
getFunction,
|
||||
getFunctionRelease,
|
||||
getFunctions,
|
||||
getPublicFunctions,
|
||||
getFunctionReleases,
|
||||
getUserGithubAuthState,
|
||||
getUserGithubOrganizations
|
||||
getUserGithubOrganizations,
|
||||
getUserFunctions
|
||||
} from '@/modules/automate/clients/executionEngine'
|
||||
import {
|
||||
GetProjectAutomationsParams,
|
||||
@@ -54,7 +56,13 @@ import {
|
||||
} from '@/modules/core/graph/generated/graphql'
|
||||
import { getGenericRedis } from '@/modules/shared/redis/redis'
|
||||
import { createAutomation as clientCreateAutomation } from '@/modules/automate/clients/executionEngine'
|
||||
import { Automate, Roles, isNullOrUndefined, isNonNullable } from '@speckle/shared'
|
||||
import {
|
||||
Automate,
|
||||
Roles,
|
||||
isNullOrUndefined,
|
||||
isNonNullable,
|
||||
removeNullOrUndefinedKeys
|
||||
} from '@speckle/shared'
|
||||
import { getFeatureFlags, getServerOrigin } from '@/modules/shared/helpers/envHelper'
|
||||
import {
|
||||
getBranchesByIdsFactory,
|
||||
@@ -116,6 +124,7 @@ import {
|
||||
storeTokenScopesFactory,
|
||||
storeUserServerAppTokenFactory
|
||||
} from '@/modules/core/repositories/tokens'
|
||||
import { getEventBus } from '@/modules/shared/services/eventBus'
|
||||
import { getProjectDbClient } from '@/modules/multiregion/dbSelector'
|
||||
|
||||
const { FF_AUTOMATE_MODULE_ENABLED } = getFeatureFlags()
|
||||
@@ -326,7 +335,7 @@ export = (FF_AUTOMATE_MODULE_ENABLED
|
||||
await authorizeResolver(
|
||||
ctx.userId,
|
||||
parent.projectId,
|
||||
Roles.Stream.Owner,
|
||||
Roles.Stream.Contributor,
|
||||
ctx.resourceAccessRules
|
||||
)
|
||||
|
||||
@@ -526,6 +535,24 @@ export = (FF_AUTOMATE_MODULE_ENABLED
|
||||
return (await create({ input: args.input, userId: ctx.userId! }))
|
||||
.graphqlReturn
|
||||
},
|
||||
async createFunctionWithoutVersion(_parent, args, ctx) {
|
||||
const authCode = await createStoredAuthCodeFactory({
|
||||
redis: getGenericRedis()
|
||||
})({
|
||||
userId: ctx.userId!,
|
||||
action: AuthCodePayloadAction.CreateFunction
|
||||
})
|
||||
return await createFunctionWithoutVersion({
|
||||
body: {
|
||||
speckleServerAuthenticationPayload: {
|
||||
...authCode,
|
||||
origin: getServerOrigin()
|
||||
},
|
||||
functionName: args.input.name,
|
||||
description: args.input.description
|
||||
}
|
||||
})
|
||||
},
|
||||
async updateFunction(_parent, args, ctx) {
|
||||
const update = updateFunctionFactory({
|
||||
updateFunction: execEngineUpdateFunction,
|
||||
@@ -693,10 +720,12 @@ export = (FF_AUTOMATE_MODULE_ENABLED
|
||||
Query: {
|
||||
async automateValidateAuthCode(_parent, args) {
|
||||
const validate = validateStoredAuthCodeFactory({
|
||||
redis: getGenericRedis()
|
||||
redis: getGenericRedis(),
|
||||
emit: getEventBus().emit
|
||||
})
|
||||
const payload = removeNullOrUndefinedKeys(args.payload)
|
||||
return await validate({
|
||||
...args.payload,
|
||||
...payload,
|
||||
action: args.payload.action as AuthCodePayloadAction
|
||||
})
|
||||
},
|
||||
@@ -712,14 +741,13 @@ export = (FF_AUTOMATE_MODULE_ENABLED
|
||||
},
|
||||
async automateFunctions(_parent, args) {
|
||||
try {
|
||||
const res = await getFunctions({
|
||||
const res = await getPublicFunctions({
|
||||
query: {
|
||||
query: args.filter?.search || undefined,
|
||||
cursor: args.cursor || undefined,
|
||||
limit: isNullOrUndefined(args.limit) ? undefined : args.limit,
|
||||
functionsWithoutVersions:
|
||||
args.filter?.functionsWithoutReleases || undefined,
|
||||
featuredFunctionsOnly: args.filter?.featuredFunctionsOnly || undefined
|
||||
args.filter?.functionsWithoutReleases || undefined
|
||||
}
|
||||
})
|
||||
|
||||
@@ -747,7 +775,53 @@ export = (FF_AUTOMATE_MODULE_ENABLED
|
||||
}
|
||||
},
|
||||
User: {
|
||||
automateInfo: (parent) => ({ userId: parent.id })
|
||||
automateInfo: (parent) => ({ userId: parent.id }),
|
||||
automateFunctions: async (_parent, args, context) => {
|
||||
try {
|
||||
const authCode = await createStoredAuthCodeFactory({
|
||||
redis: getGenericRedis()
|
||||
})({
|
||||
userId: context.userId!,
|
||||
action: AuthCodePayloadAction.ListUserFunctions
|
||||
})
|
||||
|
||||
const res = await getUserFunctions({
|
||||
userId: context.userId!,
|
||||
query: {
|
||||
query: args.filter?.search || undefined,
|
||||
cursor: args.cursor || undefined,
|
||||
limit: isNullOrUndefined(args.limit) ? undefined : args.limit
|
||||
},
|
||||
body: {
|
||||
speckleServerAuthenticationPayload: {
|
||||
...authCode,
|
||||
origin: getServerOrigin()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const items = res.functions.map(convertFunctionToGraphQLReturn)
|
||||
|
||||
return {
|
||||
cursor: undefined,
|
||||
totalCount: res.functions.length,
|
||||
items
|
||||
}
|
||||
} catch (e) {
|
||||
const isNotFound =
|
||||
e instanceof ExecutionEngineFailedResponseError &&
|
||||
e.response.statusMessage === 'FunctionNotFound'
|
||||
if (e instanceof ExecutionEngineNetworkError || isNotFound) {
|
||||
return {
|
||||
cursor: null,
|
||||
totalCount: 0,
|
||||
items: []
|
||||
}
|
||||
}
|
||||
|
||||
throw e
|
||||
}
|
||||
}
|
||||
},
|
||||
UserAutomateInfo: {
|
||||
hasAutomateGithubApp: async (parent, _args, ctx) => {
|
||||
@@ -807,7 +881,7 @@ export = (FF_AUTOMATE_MODULE_ENABLED
|
||||
await validateStreamAccess(
|
||||
ctx.userId,
|
||||
projectId,
|
||||
Roles.Stream.Owner,
|
||||
Roles.Stream.Contributor,
|
||||
ctx.resourceAccessRules
|
||||
)
|
||||
return { projectId }
|
||||
@@ -886,9 +960,6 @@ export = (FF_AUTOMATE_MODULE_ENABLED
|
||||
Project: {
|
||||
automation: () => {
|
||||
throw new AutomateApiDisabledError()
|
||||
},
|
||||
automations: () => {
|
||||
throw new AutomateApiDisabledError()
|
||||
}
|
||||
},
|
||||
AutomateMutations: {
|
||||
|
||||
@@ -20,6 +20,7 @@ export type FunctionSchemaType = {
|
||||
speckleUserId: string
|
||||
speckleServerOrigin: string
|
||||
}>
|
||||
workspaceIds: string[]
|
||||
}
|
||||
|
||||
export type FunctionReleaseSchemaType = {
|
||||
|
||||
@@ -28,6 +28,7 @@ export type AutomateFunctionGraphQLReturn = Pick<
|
||||
| 'logo'
|
||||
| 'tags'
|
||||
| 'supportedSourceApps'
|
||||
| 'workspaceIds'
|
||||
> & {
|
||||
functionCreator: Nullable<{
|
||||
speckleUserId: string
|
||||
|
||||
@@ -30,7 +30,7 @@ export default (app: Application) => {
|
||||
getStream: getStreamFactory({ db })
|
||||
}),
|
||||
validateStreamRoleBuilderFactory({ getRoles: getRolesFactory({ db }) })({
|
||||
requiredRole: Roles.Stream.Owner
|
||||
requiredRole: Roles.Stream.Reviewer
|
||||
}),
|
||||
validateResourceAccess
|
||||
]),
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { automateLogger } from '@/logging/logging'
|
||||
import { CreateStoredAuthCode } from '@/modules/automate/domain/operations'
|
||||
import { AutomateAuthCodeHandshakeError } from '@/modules/automate/errors/management'
|
||||
import { EventBus } from '@/modules/shared/services/eventBus'
|
||||
import cryptoRandomString from 'crypto-random-string'
|
||||
import Redis from 'ioredis'
|
||||
import { get, has, isObjectLike } from 'lodash'
|
||||
@@ -8,6 +9,8 @@ import { get, has, isObjectLike } from 'lodash'
|
||||
export enum AuthCodePayloadAction {
|
||||
CreateAutomation = 'createAutomation',
|
||||
CreateFunction = 'createFunction',
|
||||
ListWorkspaceFunctions = 'listWorkspaceFunctions',
|
||||
ListUserFunctions = 'listUserFunctions',
|
||||
BecomeFunctionAuthor = 'becomeFunctionAuthor',
|
||||
GetAvailableGithubOrganizations = 'getAvailableGithubOrganizations',
|
||||
UpdateFunction = 'updateFunction'
|
||||
@@ -16,6 +19,7 @@ export enum AuthCodePayloadAction {
|
||||
export type AuthCodePayload = {
|
||||
code: string
|
||||
userId: string
|
||||
workspaceId?: string
|
||||
action: AuthCodePayloadAction
|
||||
}
|
||||
|
||||
@@ -44,8 +48,9 @@ export const createStoredAuthCodeFactory =
|
||||
}
|
||||
|
||||
export const validateStoredAuthCodeFactory =
|
||||
(deps: { redis: Redis }) => async (payload: AuthCodePayload) => {
|
||||
const { redis } = deps
|
||||
(deps: { redis: Redis; emit: EventBus['emit'] }) =>
|
||||
async (payload: AuthCodePayload) => {
|
||||
const { redis, emit } = deps
|
||||
|
||||
const potentialPayloadString = await redis.get(payload.code)
|
||||
const potentialPayload: unknown = potentialPayloadString
|
||||
@@ -62,6 +67,13 @@ export const validateStoredAuthCodeFactory =
|
||||
throw new AutomateAuthCodeHandshakeError('Invalid automate auth payload')
|
||||
}
|
||||
|
||||
if (payload.workspaceId) {
|
||||
emit({
|
||||
eventName: 'workspace.authorized',
|
||||
payload: { userId: payload.userId, workspaceId: payload.workspaceId }
|
||||
})
|
||||
}
|
||||
|
||||
try {
|
||||
await redis.del(payload.code)
|
||||
} catch (e) {
|
||||
|
||||
@@ -99,7 +99,8 @@ export const convertFunctionToGraphQLReturn = (
|
||||
logo: cleanFunctionLogo(fn.logo),
|
||||
tags: fn.tags,
|
||||
supportedSourceApps: fn.supportedSourceApps,
|
||||
functionCreator: fn.functionCreator
|
||||
functionCreator: fn.functionCreator,
|
||||
workspaceIds: fn.workspaceIds
|
||||
}
|
||||
|
||||
return ret
|
||||
@@ -230,6 +231,8 @@ export const updateFunctionFactory =
|
||||
}
|
||||
})
|
||||
|
||||
console.log(JSON.stringify(apiResult, null, 2))
|
||||
|
||||
return convertFunctionToGraphQLReturn(apiResult)
|
||||
}
|
||||
|
||||
|
||||
@@ -232,6 +232,7 @@ export type AutomateAuthCodePayloadTest = {
|
||||
action: Scalars['String']['input'];
|
||||
code: Scalars['String']['input'];
|
||||
userId: Scalars['String']['input'];
|
||||
workspaceId?: InputMaybe<Scalars['String']['input']>;
|
||||
};
|
||||
|
||||
export type AutomateFunction = {
|
||||
@@ -248,6 +249,7 @@ export type AutomateFunction = {
|
||||
/** SourceAppNames values from @speckle/shared. Empty array means - all of them */
|
||||
supportedSourceApps: Array<Scalars['String']['output']>;
|
||||
tags: Array<Scalars['String']['output']>;
|
||||
workspaceIds?: Maybe<Array<Scalars['String']['output']>>;
|
||||
};
|
||||
|
||||
|
||||
@@ -329,8 +331,13 @@ export enum AutomateFunctionTemplateLanguage {
|
||||
Typescript = 'TYPESCRIPT'
|
||||
}
|
||||
|
||||
export type AutomateFunctionToken = {
|
||||
__typename?: 'AutomateFunctionToken';
|
||||
functionId: Scalars['String']['output'];
|
||||
functionToken: Scalars['String']['output'];
|
||||
};
|
||||
|
||||
export type AutomateFunctionsFilter = {
|
||||
featuredFunctionsOnly?: InputMaybe<Scalars['Boolean']['input']>;
|
||||
/** By default we skip functions without releases. Set this to true to include them. */
|
||||
functionsWithoutReleases?: InputMaybe<Scalars['Boolean']['input']>;
|
||||
search?: InputMaybe<Scalars['String']['input']>;
|
||||
@@ -339,6 +346,7 @@ export type AutomateFunctionsFilter = {
|
||||
export type AutomateMutations = {
|
||||
__typename?: 'AutomateMutations';
|
||||
createFunction: AutomateFunction;
|
||||
createFunctionWithoutVersion: AutomateFunctionToken;
|
||||
updateFunction: AutomateFunction;
|
||||
};
|
||||
|
||||
@@ -348,6 +356,11 @@ export type AutomateMutationsCreateFunctionArgs = {
|
||||
};
|
||||
|
||||
|
||||
export type AutomateMutationsCreateFunctionWithoutVersionArgs = {
|
||||
input: CreateAutomateFunctionWithoutVersionInput;
|
||||
};
|
||||
|
||||
|
||||
export type AutomateMutationsUpdateFunctionArgs = {
|
||||
input: UpdateAutomateFunctionInput;
|
||||
};
|
||||
@@ -838,6 +851,11 @@ export type CreateAutomateFunctionInput = {
|
||||
template: AutomateFunctionTemplateLanguage;
|
||||
};
|
||||
|
||||
export type CreateAutomateFunctionWithoutVersionInput = {
|
||||
description: Scalars['String']['input'];
|
||||
name: Scalars['String']['input'];
|
||||
};
|
||||
|
||||
export type CreateCommentInput = {
|
||||
content: CommentContentInput;
|
||||
projectId: Scalars['String']['input'];
|
||||
@@ -3533,6 +3551,7 @@ export type UpdateAutomateFunctionInput = {
|
||||
/** SourceAppNames values from @speckle/shared */
|
||||
supportedSourceApps?: InputMaybe<Array<Scalars['String']['input']>>;
|
||||
tags?: InputMaybe<Array<Scalars['String']['input']>>;
|
||||
workspaceIds?: InputMaybe<Array<Scalars['String']['input']>>;
|
||||
};
|
||||
|
||||
export type UpdateModelInput = {
|
||||
@@ -3576,6 +3595,7 @@ export type User = {
|
||||
apiTokens: Array<ApiToken>;
|
||||
/** Returns the apps you have authorized. */
|
||||
authorizedApps?: Maybe<Array<ServerAppListItem>>;
|
||||
automateFunctions: AutomateFunctionCollection;
|
||||
automateInfo: UserAutomateInfo;
|
||||
avatar?: Maybe<Scalars['String']['output']>;
|
||||
bio?: Maybe<Scalars['String']['output']>;
|
||||
@@ -3667,6 +3687,17 @@ export type UserActivityArgs = {
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Full user type, should only be used in the context of admin operations or
|
||||
* when a user is reading/writing info about himself
|
||||
*/
|
||||
export type UserAutomateFunctionsArgs = {
|
||||
cursor?: InputMaybe<Scalars['String']['input']>;
|
||||
filter?: InputMaybe<AutomateFunctionsFilter>;
|
||||
limit?: InputMaybe<Scalars['Int']['input']>;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Full user type, should only be used in the context of admin operations or
|
||||
* when a user is reading/writing info about himself
|
||||
@@ -4061,6 +4092,7 @@ export type WebhookUpdateInput = {
|
||||
|
||||
export type Workspace = {
|
||||
__typename?: 'Workspace';
|
||||
automateFunctions: AutomateFunctionCollection;
|
||||
createdAt: Scalars['DateTime']['output'];
|
||||
/** Info about the workspace creation state */
|
||||
creationState?: Maybe<WorkspaceCreationState>;
|
||||
@@ -4101,6 +4133,13 @@ export type Workspace = {
|
||||
};
|
||||
|
||||
|
||||
export type WorkspaceAutomateFunctionsArgs = {
|
||||
cursor?: InputMaybe<Scalars['String']['input']>;
|
||||
filter?: InputMaybe<AutomateFunctionsFilter>;
|
||||
limit?: Scalars['Int']['input'];
|
||||
};
|
||||
|
||||
|
||||
export type WorkspaceHasAccessToFeatureArgs = {
|
||||
featureName: WorkspaceFeatureName;
|
||||
};
|
||||
@@ -4613,6 +4652,7 @@ export type ResolversTypes = {
|
||||
AutomateFunctionRunStatusReportInput: AutomateFunctionRunStatusReportInput;
|
||||
AutomateFunctionTemplate: ResolverTypeWrapper<AutomateFunctionTemplate>;
|
||||
AutomateFunctionTemplateLanguage: AutomateFunctionTemplateLanguage;
|
||||
AutomateFunctionToken: ResolverTypeWrapper<AutomateFunctionToken>;
|
||||
AutomateFunctionsFilter: AutomateFunctionsFilter;
|
||||
AutomateMutations: ResolverTypeWrapper<MutationsObjectGraphQLReturn>;
|
||||
AutomateRun: ResolverTypeWrapper<AutomateRunGraphQLReturn>;
|
||||
@@ -4661,6 +4701,7 @@ export type ResolversTypes = {
|
||||
CommitsMoveInput: CommitsMoveInput;
|
||||
CountOnlyCollection: ResolverTypeWrapper<CountOnlyCollection>;
|
||||
CreateAutomateFunctionInput: CreateAutomateFunctionInput;
|
||||
CreateAutomateFunctionWithoutVersionInput: CreateAutomateFunctionWithoutVersionInput;
|
||||
CreateCommentInput: CreateCommentInput;
|
||||
CreateCommentReplyInput: CreateCommentReplyInput;
|
||||
CreateModelInput: CreateModelInput;
|
||||
@@ -4899,6 +4940,7 @@ export type ResolversParentTypes = {
|
||||
AutomateFunctionRun: AutomateFunctionRunGraphQLReturn;
|
||||
AutomateFunctionRunStatusReportInput: AutomateFunctionRunStatusReportInput;
|
||||
AutomateFunctionTemplate: AutomateFunctionTemplate;
|
||||
AutomateFunctionToken: AutomateFunctionToken;
|
||||
AutomateFunctionsFilter: AutomateFunctionsFilter;
|
||||
AutomateMutations: MutationsObjectGraphQLReturn;
|
||||
AutomateRun: AutomateRunGraphQLReturn;
|
||||
@@ -4944,6 +4986,7 @@ export type ResolversParentTypes = {
|
||||
CommitsMoveInput: CommitsMoveInput;
|
||||
CountOnlyCollection: CountOnlyCollection;
|
||||
CreateAutomateFunctionInput: CreateAutomateFunctionInput;
|
||||
CreateAutomateFunctionWithoutVersionInput: CreateAutomateFunctionWithoutVersionInput;
|
||||
CreateCommentInput: CreateCommentInput;
|
||||
CreateCommentReplyInput: CreateCommentReplyInput;
|
||||
CreateModelInput: CreateModelInput;
|
||||
@@ -5274,6 +5317,7 @@ export type AutomateFunctionResolvers<ContextType = GraphQLContext, ParentType e
|
||||
repo?: Resolver<ResolversTypes['BasicGitRepositoryMetadata'], ParentType, ContextType>;
|
||||
supportedSourceApps?: Resolver<Array<ResolversTypes['String']>, ParentType, ContextType>;
|
||||
tags?: Resolver<Array<ResolversTypes['String']>, ParentType, ContextType>;
|
||||
workspaceIds?: Resolver<Maybe<Array<ResolversTypes['String']>>, ParentType, ContextType>;
|
||||
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
|
||||
};
|
||||
|
||||
@@ -5325,8 +5369,15 @@ export type AutomateFunctionTemplateResolvers<ContextType = GraphQLContext, Pare
|
||||
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
|
||||
};
|
||||
|
||||
export type AutomateFunctionTokenResolvers<ContextType = GraphQLContext, ParentType extends ResolversParentTypes['AutomateFunctionToken'] = ResolversParentTypes['AutomateFunctionToken']> = {
|
||||
functionId?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
|
||||
functionToken?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
|
||||
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
|
||||
};
|
||||
|
||||
export type AutomateMutationsResolvers<ContextType = GraphQLContext, ParentType extends ResolversParentTypes['AutomateMutations'] = ResolversParentTypes['AutomateMutations']> = {
|
||||
createFunction?: Resolver<ResolversTypes['AutomateFunction'], ParentType, ContextType, RequireFields<AutomateMutationsCreateFunctionArgs, 'input'>>;
|
||||
createFunctionWithoutVersion?: Resolver<ResolversTypes['AutomateFunctionToken'], ParentType, ContextType, RequireFields<AutomateMutationsCreateFunctionWithoutVersionArgs, 'input'>>;
|
||||
updateFunction?: Resolver<ResolversTypes['AutomateFunction'], ParentType, ContextType, RequireFields<AutomateMutationsUpdateFunctionArgs, 'input'>>;
|
||||
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
|
||||
};
|
||||
@@ -6334,6 +6385,7 @@ export type UserResolvers<ContextType = GraphQLContext, ParentType extends Resol
|
||||
activity?: Resolver<Maybe<ResolversTypes['ActivityCollection']>, ParentType, ContextType, RequireFields<UserActivityArgs, 'limit'>>;
|
||||
apiTokens?: Resolver<Array<ResolversTypes['ApiToken']>, ParentType, ContextType>;
|
||||
authorizedApps?: Resolver<Maybe<Array<ResolversTypes['ServerAppListItem']>>, ParentType, ContextType>;
|
||||
automateFunctions?: Resolver<ResolversTypes['AutomateFunctionCollection'], ParentType, ContextType, Partial<UserAutomateFunctionsArgs>>;
|
||||
automateInfo?: Resolver<ResolversTypes['UserAutomateInfo'], ParentType, ContextType>;
|
||||
avatar?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
|
||||
bio?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
|
||||
@@ -6518,6 +6570,7 @@ export type WebhookEventCollectionResolvers<ContextType = GraphQLContext, Parent
|
||||
};
|
||||
|
||||
export type WorkspaceResolvers<ContextType = GraphQLContext, ParentType extends ResolversParentTypes['Workspace'] = ResolversParentTypes['Workspace']> = {
|
||||
automateFunctions?: Resolver<ResolversTypes['AutomateFunctionCollection'], ParentType, ContextType, RequireFields<WorkspaceAutomateFunctionsArgs, 'limit'>>;
|
||||
createdAt?: Resolver<ResolversTypes['DateTime'], ParentType, ContextType>;
|
||||
creationState?: Resolver<Maybe<ResolversTypes['WorkspaceCreationState']>, ParentType, ContextType>;
|
||||
customerPortalUrl?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
|
||||
@@ -6687,6 +6740,7 @@ export type Resolvers<ContextType = GraphQLContext> = {
|
||||
AutomateFunctionReleaseCollection?: AutomateFunctionReleaseCollectionResolvers<ContextType>;
|
||||
AutomateFunctionRun?: AutomateFunctionRunResolvers<ContextType>;
|
||||
AutomateFunctionTemplate?: AutomateFunctionTemplateResolvers<ContextType>;
|
||||
AutomateFunctionToken?: AutomateFunctionTokenResolvers<ContextType>;
|
||||
AutomateMutations?: AutomateMutationsResolvers<ContextType>;
|
||||
AutomateRun?: AutomateRunResolvers<ContextType>;
|
||||
AutomateRunCollection?: AutomateRunCollectionResolvers<ContextType>;
|
||||
|
||||
@@ -213,6 +213,7 @@ export type AutomateAuthCodePayloadTest = {
|
||||
action: Scalars['String']['input'];
|
||||
code: Scalars['String']['input'];
|
||||
userId: Scalars['String']['input'];
|
||||
workspaceId?: InputMaybe<Scalars['String']['input']>;
|
||||
};
|
||||
|
||||
export type AutomateFunction = {
|
||||
@@ -229,6 +230,7 @@ export type AutomateFunction = {
|
||||
/** SourceAppNames values from @speckle/shared. Empty array means - all of them */
|
||||
supportedSourceApps: Array<Scalars['String']['output']>;
|
||||
tags: Array<Scalars['String']['output']>;
|
||||
workspaceIds?: Maybe<Array<Scalars['String']['output']>>;
|
||||
};
|
||||
|
||||
|
||||
@@ -310,8 +312,13 @@ export enum AutomateFunctionTemplateLanguage {
|
||||
Typescript = 'TYPESCRIPT'
|
||||
}
|
||||
|
||||
export type AutomateFunctionToken = {
|
||||
__typename?: 'AutomateFunctionToken';
|
||||
functionId: Scalars['String']['output'];
|
||||
functionToken: Scalars['String']['output'];
|
||||
};
|
||||
|
||||
export type AutomateFunctionsFilter = {
|
||||
featuredFunctionsOnly?: InputMaybe<Scalars['Boolean']['input']>;
|
||||
/** By default we skip functions without releases. Set this to true to include them. */
|
||||
functionsWithoutReleases?: InputMaybe<Scalars['Boolean']['input']>;
|
||||
search?: InputMaybe<Scalars['String']['input']>;
|
||||
@@ -320,6 +327,7 @@ export type AutomateFunctionsFilter = {
|
||||
export type AutomateMutations = {
|
||||
__typename?: 'AutomateMutations';
|
||||
createFunction: AutomateFunction;
|
||||
createFunctionWithoutVersion: AutomateFunctionToken;
|
||||
updateFunction: AutomateFunction;
|
||||
};
|
||||
|
||||
@@ -329,6 +337,11 @@ export type AutomateMutationsCreateFunctionArgs = {
|
||||
};
|
||||
|
||||
|
||||
export type AutomateMutationsCreateFunctionWithoutVersionArgs = {
|
||||
input: CreateAutomateFunctionWithoutVersionInput;
|
||||
};
|
||||
|
||||
|
||||
export type AutomateMutationsUpdateFunctionArgs = {
|
||||
input: UpdateAutomateFunctionInput;
|
||||
};
|
||||
@@ -819,6 +832,11 @@ export type CreateAutomateFunctionInput = {
|
||||
template: AutomateFunctionTemplateLanguage;
|
||||
};
|
||||
|
||||
export type CreateAutomateFunctionWithoutVersionInput = {
|
||||
description: Scalars['String']['input'];
|
||||
name: Scalars['String']['input'];
|
||||
};
|
||||
|
||||
export type CreateCommentInput = {
|
||||
content: CommentContentInput;
|
||||
projectId: Scalars['String']['input'];
|
||||
@@ -3514,6 +3532,7 @@ export type UpdateAutomateFunctionInput = {
|
||||
/** SourceAppNames values from @speckle/shared */
|
||||
supportedSourceApps?: InputMaybe<Array<Scalars['String']['input']>>;
|
||||
tags?: InputMaybe<Array<Scalars['String']['input']>>;
|
||||
workspaceIds?: InputMaybe<Array<Scalars['String']['input']>>;
|
||||
};
|
||||
|
||||
export type UpdateModelInput = {
|
||||
@@ -3557,6 +3576,7 @@ export type User = {
|
||||
apiTokens: Array<ApiToken>;
|
||||
/** Returns the apps you have authorized. */
|
||||
authorizedApps?: Maybe<Array<ServerAppListItem>>;
|
||||
automateFunctions: AutomateFunctionCollection;
|
||||
automateInfo: UserAutomateInfo;
|
||||
avatar?: Maybe<Scalars['String']['output']>;
|
||||
bio?: Maybe<Scalars['String']['output']>;
|
||||
@@ -3648,6 +3668,17 @@ export type UserActivityArgs = {
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Full user type, should only be used in the context of admin operations or
|
||||
* when a user is reading/writing info about himself
|
||||
*/
|
||||
export type UserAutomateFunctionsArgs = {
|
||||
cursor?: InputMaybe<Scalars['String']['input']>;
|
||||
filter?: InputMaybe<AutomateFunctionsFilter>;
|
||||
limit?: InputMaybe<Scalars['Int']['input']>;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Full user type, should only be used in the context of admin operations or
|
||||
* when a user is reading/writing info about himself
|
||||
@@ -4042,6 +4073,7 @@ export type WebhookUpdateInput = {
|
||||
|
||||
export type Workspace = {
|
||||
__typename?: 'Workspace';
|
||||
automateFunctions: AutomateFunctionCollection;
|
||||
createdAt: Scalars['DateTime']['output'];
|
||||
/** Info about the workspace creation state */
|
||||
creationState?: Maybe<WorkspaceCreationState>;
|
||||
@@ -4082,6 +4114,13 @@ export type Workspace = {
|
||||
};
|
||||
|
||||
|
||||
export type WorkspaceAutomateFunctionsArgs = {
|
||||
cursor?: InputMaybe<Scalars['String']['input']>;
|
||||
filter?: InputMaybe<AutomateFunctionsFilter>;
|
||||
limit?: Scalars['Int']['input'];
|
||||
};
|
||||
|
||||
|
||||
export type WorkspaceHasAccessToFeatureArgs = {
|
||||
featureName: WorkspaceFeatureName;
|
||||
};
|
||||
|
||||
@@ -163,6 +163,7 @@ export const onWorkspaceAuthorizedFactory =
|
||||
|
||||
// Guests cannot use (and are not restricted by) SSO
|
||||
const workspaceRole = await getWorkspaceRoleForUser({ userId, workspaceId })
|
||||
if (!workspaceRole) throw new WorkspacesNotAuthorizedError()
|
||||
if (workspaceRole?.role === Roles.Workspace.Guest) return
|
||||
|
||||
const provider = await getWorkspaceSsoProviderRecord({ workspaceId })
|
||||
|
||||
@@ -42,7 +42,7 @@ import {
|
||||
import { createProjectInviteFactory } from '@/modules/serverinvites/services/projectInviteManagement'
|
||||
import { getInvitationTargetUsersFactory } from '@/modules/serverinvites/services/retrieval'
|
||||
import { authorizeResolver } from '@/modules/shared'
|
||||
import { getFeatureFlags } from '@/modules/shared/helpers/envHelper'
|
||||
import { getFeatureFlags, getServerOrigin } from '@/modules/shared/helpers/envHelper'
|
||||
import { getEventBus } from '@/modules/shared/services/eventBus'
|
||||
import { WorkspaceInviteResourceType } from '@/modules/workspaces/domain/constants'
|
||||
import {
|
||||
@@ -177,7 +177,18 @@ import {
|
||||
listWorkspaceSsoMembershipsFactory
|
||||
} from '@/modules/workspaces/repositories/sso'
|
||||
import { getDecryptor } from '@/modules/workspaces/helpers/sso'
|
||||
import { getWorkspaceFunctions } from '@/modules/automate/clients/executionEngine'
|
||||
import {
|
||||
ExecutionEngineFailedResponseError,
|
||||
ExecutionEngineNetworkError
|
||||
} from '@/modules/automate/errors/executionEngine'
|
||||
import { getDefaultRegionFactory } from '@/modules/workspaces/repositories/regions'
|
||||
import {
|
||||
AuthCodePayloadAction,
|
||||
createStoredAuthCodeFactory
|
||||
} from '@/modules/automate/services/authCode'
|
||||
import { getGenericRedis } from '@/modules/shared/redis/redis'
|
||||
import { convertFunctionToGraphQLReturn } from '@/modules/automate/services/functionManagement'
|
||||
import { getWorkspacePlanFactory } from '@/modules/gatekeeper/repositories/billing'
|
||||
import { Knex } from 'knex'
|
||||
|
||||
@@ -561,7 +572,10 @@ export = FF_WORKSPACES_MODULE_ENABLED
|
||||
await updateWorkspaceRole({ userId, workspaceId, role })
|
||||
}
|
||||
|
||||
return await getWorkspaceFactory({ db })({ workspaceId })
|
||||
return await getWorkspaceFactory({ db })({
|
||||
workspaceId: args.input.workspaceId,
|
||||
userId: context.userId
|
||||
})
|
||||
},
|
||||
addDomain: async (_parent, args, context) => {
|
||||
await authorizeResolver(
|
||||
@@ -982,6 +996,48 @@ export = FF_WORKSPACES_MODULE_ENABLED
|
||||
})
|
||||
}
|
||||
},
|
||||
automateFunctions: async (parent, args, context) => {
|
||||
try {
|
||||
const authCode = await createStoredAuthCodeFactory({
|
||||
redis: getGenericRedis()
|
||||
})({
|
||||
userId: context.userId!,
|
||||
action: AuthCodePayloadAction.ListWorkspaceFunctions
|
||||
})
|
||||
|
||||
const res = await getWorkspaceFunctions({
|
||||
workspaceId: parent.id,
|
||||
query: removeNullOrUndefinedKeys(args),
|
||||
body: {
|
||||
speckleServerAuthenticationPayload: {
|
||||
...authCode,
|
||||
origin: getServerOrigin()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const items = res.functions.map(convertFunctionToGraphQLReturn)
|
||||
|
||||
return {
|
||||
cursor: undefined,
|
||||
totalCount: res.functions.length,
|
||||
items
|
||||
}
|
||||
} catch (e) {
|
||||
const isNotFound =
|
||||
e instanceof ExecutionEngineFailedResponseError &&
|
||||
e.response.statusMessage === 'FunctionNotFound'
|
||||
if (e instanceof ExecutionEngineNetworkError || isNotFound) {
|
||||
return {
|
||||
cursor: null,
|
||||
totalCount: 0,
|
||||
items: []
|
||||
}
|
||||
}
|
||||
|
||||
throw e
|
||||
}
|
||||
},
|
||||
domains: async (parent) => {
|
||||
return await getWorkspaceDomainsFactory({ db })({ workspaceIds: [parent.id] })
|
||||
},
|
||||
|
||||
@@ -214,6 +214,7 @@ export type AutomateAuthCodePayloadTest = {
|
||||
action: Scalars['String']['input'];
|
||||
code: Scalars['String']['input'];
|
||||
userId: Scalars['String']['input'];
|
||||
workspaceId?: InputMaybe<Scalars['String']['input']>;
|
||||
};
|
||||
|
||||
export type AutomateFunction = {
|
||||
@@ -230,6 +231,7 @@ export type AutomateFunction = {
|
||||
/** SourceAppNames values from @speckle/shared. Empty array means - all of them */
|
||||
supportedSourceApps: Array<Scalars['String']['output']>;
|
||||
tags: Array<Scalars['String']['output']>;
|
||||
workspaceIds?: Maybe<Array<Scalars['String']['output']>>;
|
||||
};
|
||||
|
||||
|
||||
@@ -311,8 +313,13 @@ export enum AutomateFunctionTemplateLanguage {
|
||||
Typescript = 'TYPESCRIPT'
|
||||
}
|
||||
|
||||
export type AutomateFunctionToken = {
|
||||
__typename?: 'AutomateFunctionToken';
|
||||
functionId: Scalars['String']['output'];
|
||||
functionToken: Scalars['String']['output'];
|
||||
};
|
||||
|
||||
export type AutomateFunctionsFilter = {
|
||||
featuredFunctionsOnly?: InputMaybe<Scalars['Boolean']['input']>;
|
||||
/** By default we skip functions without releases. Set this to true to include them. */
|
||||
functionsWithoutReleases?: InputMaybe<Scalars['Boolean']['input']>;
|
||||
search?: InputMaybe<Scalars['String']['input']>;
|
||||
@@ -321,6 +328,7 @@ export type AutomateFunctionsFilter = {
|
||||
export type AutomateMutations = {
|
||||
__typename?: 'AutomateMutations';
|
||||
createFunction: AutomateFunction;
|
||||
createFunctionWithoutVersion: AutomateFunctionToken;
|
||||
updateFunction: AutomateFunction;
|
||||
};
|
||||
|
||||
@@ -330,6 +338,11 @@ export type AutomateMutationsCreateFunctionArgs = {
|
||||
};
|
||||
|
||||
|
||||
export type AutomateMutationsCreateFunctionWithoutVersionArgs = {
|
||||
input: CreateAutomateFunctionWithoutVersionInput;
|
||||
};
|
||||
|
||||
|
||||
export type AutomateMutationsUpdateFunctionArgs = {
|
||||
input: UpdateAutomateFunctionInput;
|
||||
};
|
||||
@@ -820,6 +833,11 @@ export type CreateAutomateFunctionInput = {
|
||||
template: AutomateFunctionTemplateLanguage;
|
||||
};
|
||||
|
||||
export type CreateAutomateFunctionWithoutVersionInput = {
|
||||
description: Scalars['String']['input'];
|
||||
name: Scalars['String']['input'];
|
||||
};
|
||||
|
||||
export type CreateCommentInput = {
|
||||
content: CommentContentInput;
|
||||
projectId: Scalars['String']['input'];
|
||||
@@ -3515,6 +3533,7 @@ export type UpdateAutomateFunctionInput = {
|
||||
/** SourceAppNames values from @speckle/shared */
|
||||
supportedSourceApps?: InputMaybe<Array<Scalars['String']['input']>>;
|
||||
tags?: InputMaybe<Array<Scalars['String']['input']>>;
|
||||
workspaceIds?: InputMaybe<Array<Scalars['String']['input']>>;
|
||||
};
|
||||
|
||||
export type UpdateModelInput = {
|
||||
@@ -3558,6 +3577,7 @@ export type User = {
|
||||
apiTokens: Array<ApiToken>;
|
||||
/** Returns the apps you have authorized. */
|
||||
authorizedApps?: Maybe<Array<ServerAppListItem>>;
|
||||
automateFunctions: AutomateFunctionCollection;
|
||||
automateInfo: UserAutomateInfo;
|
||||
avatar?: Maybe<Scalars['String']['output']>;
|
||||
bio?: Maybe<Scalars['String']['output']>;
|
||||
@@ -3649,6 +3669,17 @@ export type UserActivityArgs = {
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Full user type, should only be used in the context of admin operations or
|
||||
* when a user is reading/writing info about himself
|
||||
*/
|
||||
export type UserAutomateFunctionsArgs = {
|
||||
cursor?: InputMaybe<Scalars['String']['input']>;
|
||||
filter?: InputMaybe<AutomateFunctionsFilter>;
|
||||
limit?: InputMaybe<Scalars['Int']['input']>;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Full user type, should only be used in the context of admin operations or
|
||||
* when a user is reading/writing info about himself
|
||||
@@ -4043,6 +4074,7 @@ export type WebhookUpdateInput = {
|
||||
|
||||
export type Workspace = {
|
||||
__typename?: 'Workspace';
|
||||
automateFunctions: AutomateFunctionCollection;
|
||||
createdAt: Scalars['DateTime']['output'];
|
||||
/** Info about the workspace creation state */
|
||||
creationState?: Maybe<WorkspaceCreationState>;
|
||||
@@ -4083,6 +4115,13 @@ export type Workspace = {
|
||||
};
|
||||
|
||||
|
||||
export type WorkspaceAutomateFunctionsArgs = {
|
||||
cursor?: InputMaybe<Scalars['String']['input']>;
|
||||
filter?: InputMaybe<AutomateFunctionsFilter>;
|
||||
limit?: Scalars['Int']['input'];
|
||||
};
|
||||
|
||||
|
||||
export type WorkspaceHasAccessToFeatureArgs = {
|
||||
featureName: WorkspaceFeatureName;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user