feat: batch commit actions improvements (list view support, batch delete in 'your commits')
* feat(frontend): batch commit action support in list view * feat(frontend): batch delete in 'your commits' page * fix(server): authorizeResolver isPublic check always failed * fix(server): fixing tests
This commit is contained in:
committed by
GitHub
parent
025141a8b1
commit
61a2caaae9
@@ -84,7 +84,7 @@ function createCache(): InMemoryCache {
|
||||
},
|
||||
commits: {
|
||||
keyArgs: false,
|
||||
merge: buildAbstractCollectionMergeFunction('CommitCollectionUser', {
|
||||
merge: buildAbstractCollectionMergeFunction('CommitCollection', {
|
||||
checkIdentity: true
|
||||
})
|
||||
},
|
||||
|
||||
@@ -290,6 +290,15 @@ export type Commit = {
|
||||
parents?: Maybe<Array<Maybe<Scalars['String']>>>;
|
||||
referencedObject: Scalars['String'];
|
||||
sourceApplication?: Maybe<Scalars['String']>;
|
||||
/**
|
||||
* Will throw an authorization error if active user isn't authorized to see it, for example,
|
||||
* if a stream isn't public and the user doesn't have the appropriate rights.
|
||||
*/
|
||||
stream: Stream;
|
||||
/** @deprecated Use the stream field instead */
|
||||
streamId?: Maybe<Scalars['String']>;
|
||||
/** @deprecated Use the stream field instead */
|
||||
streamName?: Maybe<Scalars['String']>;
|
||||
totalChildrenCount?: Maybe<Scalars['Int']>;
|
||||
};
|
||||
|
||||
@@ -305,42 +314,10 @@ export type CommitActivityArgs = {
|
||||
export type CommitCollection = {
|
||||
__typename?: 'CommitCollection';
|
||||
cursor?: Maybe<Scalars['String']>;
|
||||
items?: Maybe<Array<Maybe<Commit>>>;
|
||||
items?: Maybe<Array<Commit>>;
|
||||
totalCount: Scalars['Int'];
|
||||
};
|
||||
|
||||
export type CommitCollectionUser = {
|
||||
__typename?: 'CommitCollectionUser';
|
||||
cursor?: Maybe<Scalars['String']>;
|
||||
items?: Maybe<Array<Maybe<CommitCollectionUserNode>>>;
|
||||
totalCount: Scalars['Int'];
|
||||
};
|
||||
|
||||
export type CommitCollectionUserNode = {
|
||||
__typename?: 'CommitCollectionUserNode';
|
||||
branchName?: Maybe<Scalars['String']>;
|
||||
/**
|
||||
* The total number of comments for this commit. To actually get the comments, use the comments query and pass in a resource array consisting of of this commit's id.
|
||||
* E.g.,
|
||||
* ```
|
||||
* query{
|
||||
* comments(streamId:"streamId" resources:[{resourceType: commit, resourceId:"commitId"}] ){
|
||||
* ...
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
commentCount: Scalars['Int'];
|
||||
createdAt?: Maybe<Scalars['DateTime']>;
|
||||
id: Scalars['String'];
|
||||
message?: Maybe<Scalars['String']>;
|
||||
parents?: Maybe<Array<Maybe<Scalars['String']>>>;
|
||||
referencedObject: Scalars['String'];
|
||||
sourceApplication?: Maybe<Scalars['String']>;
|
||||
streamId?: Maybe<Scalars['String']>;
|
||||
streamName?: Maybe<Scalars['String']>;
|
||||
totalChildrenCount?: Maybe<Scalars['Int']>;
|
||||
};
|
||||
|
||||
export type CommitCreateInput = {
|
||||
branchName: Scalars['String'];
|
||||
message?: InputMaybe<Scalars['String']>;
|
||||
@@ -1497,7 +1474,7 @@ export type User = {
|
||||
* Get commits authored by the user. If requested for another user, then only commits
|
||||
* from public streams will be returned.
|
||||
*/
|
||||
commits?: Maybe<CommitCollectionUser>;
|
||||
commits?: Maybe<CommitCollection>;
|
||||
company?: Maybe<Scalars['String']>;
|
||||
/** Returns the apps you have created. */
|
||||
createdApps?: Maybe<Array<Maybe<ServerApp>>>;
|
||||
@@ -1699,7 +1676,7 @@ export type StreamWithBranchQueryVariables = Exact<{
|
||||
}>;
|
||||
|
||||
|
||||
export type StreamWithBranchQuery = { __typename?: 'Query', stream?: { __typename?: 'Stream', id: string, name: string, role?: string | null, branch?: { __typename?: 'Branch', id: string, name: string, description?: string | null, commits?: { __typename?: 'CommitCollection', totalCount: number, cursor?: string | null, items?: Array<{ __typename?: 'Commit', id: string, authorName?: string | null, authorId?: string | null, authorAvatar?: string | null, sourceApplication?: string | null, message?: string | null, referencedObject: string, createdAt?: string | null, commentCount: number } | null> | null } | null } | null } | null };
|
||||
export type StreamWithBranchQuery = { __typename?: 'Query', stream?: { __typename?: 'Stream', id: string, name: string, role?: string | null, branch?: { __typename?: 'Branch', id: string, name: string, description?: string | null, commits?: { __typename?: 'CommitCollection', totalCount: number, cursor?: string | null, items?: Array<{ __typename?: 'Commit', id: string, authorName?: string | null, authorId?: string | null, authorAvatar?: string | null, sourceApplication?: string | null, message?: string | null, referencedObject: string, createdAt?: string | null, commentCount: number }> | null } | null } | null } | null };
|
||||
|
||||
export type BranchCreatedSubscriptionVariables = Exact<{
|
||||
streamId: Scalars['String'];
|
||||
@@ -1857,14 +1834,14 @@ export type StreamCommitsQueryVariables = Exact<{
|
||||
}>;
|
||||
|
||||
|
||||
export type StreamCommitsQuery = { __typename?: 'Query', stream?: { __typename?: 'Stream', id: string, role?: string | null, commits?: { __typename?: 'CommitCollection', totalCount: number, items?: Array<{ __typename?: 'Commit', id: string, authorId?: string | null, authorName?: string | null, authorAvatar?: string | null, createdAt?: string | null, message?: string | null, referencedObject: string, branchName?: string | null, sourceApplication?: string | null } | null> | null } | null } | null };
|
||||
export type StreamCommitsQuery = { __typename?: 'Query', stream?: { __typename?: 'Stream', id: string, role?: string | null, commits?: { __typename?: 'CommitCollection', totalCount: number, items?: Array<{ __typename?: 'Commit', id: string, authorId?: string | null, authorName?: string | null, authorAvatar?: string | null, createdAt?: string | null, message?: string | null, referencedObject: string, branchName?: string | null, sourceApplication?: string | null }> | null } | null } | null };
|
||||
|
||||
export type StreamsQueryVariables = Exact<{
|
||||
cursor?: InputMaybe<Scalars['String']>;
|
||||
}>;
|
||||
|
||||
|
||||
export type StreamsQuery = { __typename?: 'Query', streams?: { __typename?: 'StreamCollection', totalCount: number, cursor?: string | null, items?: Array<{ __typename?: 'Stream', id: string, name: string, description?: string | null, role?: string | null, isPublic: boolean, createdAt: string, updatedAt: string, commentCount: number, favoritedDate?: string | null, favoritesCount: number, collaborators: Array<{ __typename?: 'StreamCollaborator', id: string, name: string, company?: string | null, avatar?: string | null, role: string }>, commits?: { __typename?: 'CommitCollection', totalCount: number, items?: Array<{ __typename?: 'Commit', id: string, createdAt?: string | null, message?: string | null, authorId?: string | null, branchName?: string | null, authorName?: string | null, authorAvatar?: string | null, referencedObject: string } | null> | null } | null, branches?: { __typename?: 'BranchCollection', totalCount: number } | null }> | null } | null };
|
||||
export type StreamsQuery = { __typename?: 'Query', streams?: { __typename?: 'StreamCollection', totalCount: number, cursor?: string | null, items?: Array<{ __typename?: 'Stream', id: string, name: string, description?: string | null, role?: string | null, isPublic: boolean, createdAt: string, updatedAt: string, commentCount: number, favoritedDate?: string | null, favoritesCount: number, collaborators: Array<{ __typename?: 'StreamCollaborator', id: string, name: string, company?: string | null, avatar?: string | null, role: string }>, commits?: { __typename?: 'CommitCollection', totalCount: number, items?: Array<{ __typename?: 'Commit', id: string, createdAt?: string | null, message?: string | null, authorId?: string | null, branchName?: string | null, authorName?: string | null, authorAvatar?: string | null, referencedObject: string }> | null } | null, branches?: { __typename?: 'BranchCollection', totalCount: number } | null }> | null } | null };
|
||||
|
||||
export type CommonStreamFieldsFragment = { __typename?: 'Stream', id: string, name: string, description?: string | null, role?: string | null, isPublic: boolean, createdAt: string, updatedAt: string, commentCount: number, favoritedDate?: string | null, favoritesCount: number, collaborators: Array<{ __typename?: 'StreamCollaborator', id: string, name: string, company?: string | null, avatar?: string | null, role: string }>, commits?: { __typename?: 'CommitCollection', totalCount: number } | null, branches?: { __typename?: 'BranchCollection', totalCount: number } | null };
|
||||
|
||||
@@ -1909,7 +1886,7 @@ export type StreamFirstCommitQueryVariables = Exact<{
|
||||
}>;
|
||||
|
||||
|
||||
export type StreamFirstCommitQuery = { __typename?: 'Query', stream?: { __typename?: 'Stream', id: string, commits?: { __typename?: 'CommitCollection', totalCount: number, items?: Array<{ __typename?: 'Commit', id: string, referencedObject: string } | null> | null } | null } | null };
|
||||
export type StreamFirstCommitQuery = { __typename?: 'Query', stream?: { __typename?: 'Stream', id: string, commits?: { __typename?: 'CommitCollection', totalCount: number, items?: Array<{ __typename?: 'Commit', id: string, referencedObject: string }> | null } | null } | null };
|
||||
|
||||
export type StreamBranchFirstCommitQueryVariables = Exact<{
|
||||
id: Scalars['String'];
|
||||
@@ -1917,7 +1894,7 @@ export type StreamBranchFirstCommitQueryVariables = Exact<{
|
||||
}>;
|
||||
|
||||
|
||||
export type StreamBranchFirstCommitQuery = { __typename?: 'Query', stream?: { __typename?: 'Stream', id: string, branch?: { __typename?: 'Branch', commits?: { __typename?: 'CommitCollection', totalCount: number, items?: Array<{ __typename?: 'Commit', id: string, referencedObject: string } | null> | null } | null } | null } | null };
|
||||
export type StreamBranchFirstCommitQuery = { __typename?: 'Query', stream?: { __typename?: 'Stream', id: string, branch?: { __typename?: 'Branch', commits?: { __typename?: 'CommitCollection', totalCount: number, items?: Array<{ __typename?: 'Commit', id: string, referencedObject: string }> | null } | null } | null } | null };
|
||||
|
||||
export type StreamSettingsQueryVariables = Exact<{
|
||||
id: Scalars['String'];
|
||||
@@ -1940,24 +1917,24 @@ export type DeleteStreamMutationVariables = Exact<{
|
||||
|
||||
export type DeleteStreamMutation = { __typename?: 'Mutation', streamDelete: boolean };
|
||||
|
||||
export type CommonUserFieldsFragment = { __typename?: 'User', id: string, email?: string | null, name?: string | null, bio?: string | null, company?: string | null, avatar?: string | null, verified?: boolean | null, hasPendingVerification?: boolean | null, profiles?: Record<string, unknown> | null, role?: string | null, streams?: { __typename?: 'StreamCollection', totalCount: number } | null, commits?: { __typename?: 'CommitCollectionUser', totalCount: number, items?: Array<{ __typename?: 'CommitCollectionUserNode', id: string, createdAt?: string | null } | null> | null } | null };
|
||||
export type CommonUserFieldsFragment = { __typename?: 'User', id: string, email?: string | null, name?: string | null, bio?: string | null, company?: string | null, avatar?: string | null, verified?: boolean | null, hasPendingVerification?: boolean | null, profiles?: Record<string, unknown> | null, role?: string | null, streams?: { __typename?: 'StreamCollection', totalCount: number } | null, commits?: { __typename?: 'CommitCollection', totalCount: number, items?: Array<{ __typename?: 'Commit', id: string, createdAt?: string | null }> | null } | null };
|
||||
|
||||
export type UserFavoriteStreamsQueryVariables = Exact<{
|
||||
cursor?: InputMaybe<Scalars['String']>;
|
||||
}>;
|
||||
|
||||
|
||||
export type UserFavoriteStreamsQuery = { __typename?: 'Query', user?: { __typename?: 'User', id: string, email?: string | null, name?: string | null, bio?: string | null, company?: string | null, avatar?: string | null, verified?: boolean | null, hasPendingVerification?: boolean | null, profiles?: Record<string, unknown> | null, role?: string | null, favoriteStreams?: { __typename?: 'StreamCollection', totalCount: number, cursor?: string | null, items?: Array<{ __typename?: 'Stream', id: string, name: string, description?: string | null, role?: string | null, isPublic: boolean, createdAt: string, updatedAt: string, commentCount: number, favoritedDate?: string | null, favoritesCount: number, collaborators: Array<{ __typename?: 'StreamCollaborator', id: string, name: string, company?: string | null, avatar?: string | null, role: string }>, commits?: { __typename?: 'CommitCollection', totalCount: number } | null, branches?: { __typename?: 'BranchCollection', totalCount: number } | null }> | null } | null, streams?: { __typename?: 'StreamCollection', totalCount: number } | null, commits?: { __typename?: 'CommitCollectionUser', totalCount: number, items?: Array<{ __typename?: 'CommitCollectionUserNode', id: string, createdAt?: string | null } | null> | null } | null } | null };
|
||||
export type UserFavoriteStreamsQuery = { __typename?: 'Query', user?: { __typename?: 'User', id: string, email?: string | null, name?: string | null, bio?: string | null, company?: string | null, avatar?: string | null, verified?: boolean | null, hasPendingVerification?: boolean | null, profiles?: Record<string, unknown> | null, role?: string | null, favoriteStreams?: { __typename?: 'StreamCollection', totalCount: number, cursor?: string | null, items?: Array<{ __typename?: 'Stream', id: string, name: string, description?: string | null, role?: string | null, isPublic: boolean, createdAt: string, updatedAt: string, commentCount: number, favoritedDate?: string | null, favoritesCount: number, collaborators: Array<{ __typename?: 'StreamCollaborator', id: string, name: string, company?: string | null, avatar?: string | null, role: string }>, commits?: { __typename?: 'CommitCollection', totalCount: number } | null, branches?: { __typename?: 'BranchCollection', totalCount: number } | null }> | null } | null, streams?: { __typename?: 'StreamCollection', totalCount: number } | null, commits?: { __typename?: 'CommitCollection', totalCount: number, items?: Array<{ __typename?: 'Commit', id: string, createdAt?: string | null }> | null } | null } | null };
|
||||
|
||||
export type MainUserDataQueryVariables = Exact<{ [key: string]: never; }>;
|
||||
|
||||
|
||||
export type MainUserDataQuery = { __typename?: 'Query', user?: { __typename?: 'User', id: string, email?: string | null, name?: string | null, bio?: string | null, company?: string | null, avatar?: string | null, verified?: boolean | null, hasPendingVerification?: boolean | null, profiles?: Record<string, unknown> | null, role?: string | null, streams?: { __typename?: 'StreamCollection', totalCount: number } | null, commits?: { __typename?: 'CommitCollectionUser', totalCount: number, items?: Array<{ __typename?: 'CommitCollectionUserNode', id: string, createdAt?: string | null } | null> | null } | null } | null };
|
||||
export type MainUserDataQuery = { __typename?: 'Query', user?: { __typename?: 'User', id: string, email?: string | null, name?: string | null, bio?: string | null, company?: string | null, avatar?: string | null, verified?: boolean | null, hasPendingVerification?: boolean | null, profiles?: Record<string, unknown> | null, role?: string | null, streams?: { __typename?: 'StreamCollection', totalCount: number } | null, commits?: { __typename?: 'CommitCollection', totalCount: number, items?: Array<{ __typename?: 'Commit', id: string, createdAt?: string | null }> | null } | null } | null };
|
||||
|
||||
export type ExtraUserDataQueryVariables = Exact<{ [key: string]: never; }>;
|
||||
|
||||
|
||||
export type ExtraUserDataQuery = { __typename?: 'Query', user?: { __typename?: 'User', totalOwnedStreamsFavorites: number, notificationPreferences: Record<string, unknown>, id: string, email?: string | null, name?: string | null, bio?: string | null, company?: string | null, avatar?: string | null, verified?: boolean | null, hasPendingVerification?: boolean | null, profiles?: Record<string, unknown> | null, role?: string | null, streams?: { __typename?: 'StreamCollection', totalCount: number } | null, commits?: { __typename?: 'CommitCollectionUser', totalCount: number, items?: Array<{ __typename?: 'CommitCollectionUserNode', id: string, createdAt?: string | null } | null> | null } | null } | null };
|
||||
export type ExtraUserDataQuery = { __typename?: 'Query', user?: { __typename?: 'User', totalOwnedStreamsFavorites: number, notificationPreferences: Record<string, unknown>, id: string, email?: string | null, name?: string | null, bio?: string | null, company?: string | null, avatar?: string | null, verified?: boolean | null, hasPendingVerification?: boolean | null, profiles?: Record<string, unknown> | null, role?: string | null, streams?: { __typename?: 'StreamCollection', totalCount: number } | null, commits?: { __typename?: 'CommitCollection', totalCount: number, items?: Array<{ __typename?: 'Commit', id: string, createdAt?: string | null }> | null } | null } | null };
|
||||
|
||||
export type UserSearchQueryVariables = Exact<{
|
||||
query: Scalars['String'];
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { ReactiveVar } from '@apollo/client/core'
|
||||
import { isUndefined } from 'lodash'
|
||||
import Vue, { VueConstructor } from 'vue'
|
||||
import { LooseRequired } from 'vue/types/common'
|
||||
|
||||
export type Nullable<T> = T | null
|
||||
|
||||
@@ -25,6 +26,8 @@ export type GetReactiveVarType<V extends ReactiveVar<any>> = V extends ReactiveV
|
||||
? T
|
||||
: unknown
|
||||
|
||||
export type SetupProps<P = unknown> = Readonly<LooseRequired<P>>
|
||||
|
||||
// Copied from Vue typings & improved ergonomics
|
||||
export type CombinedVueInstance<
|
||||
Instance extends Vue = Vue,
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
v-if="allowSelect"
|
||||
v-model="selectedState"
|
||||
dense
|
||||
hide-details
|
||||
@change="onSelect"
|
||||
/>
|
||||
<div style="overflow: hidden; text-overflow: ellipsis; white-space: nowrap">
|
||||
@@ -44,13 +45,13 @@
|
||||
</div>
|
||||
<v-divider v-if="showStreamAndBranch" />
|
||||
<div v-if="showStreamAndBranch" class="d-flex align-center caption px-5 py-2">
|
||||
<div v-show="commit.streamName" class="text-truncate mr-2">
|
||||
<div v-show="streamName" class="text-truncate mr-2">
|
||||
<router-link
|
||||
class="text-decoration-none d-inline-flex align-center"
|
||||
:to="`/streams/${streamId}`"
|
||||
>
|
||||
<v-icon x-small class="primary--text mr-2">mdi-folder-outline</v-icon>
|
||||
{{ commit.streamName }}
|
||||
{{ streamName }}
|
||||
</router-link>
|
||||
</div>
|
||||
<div class="text-right flex-grow-1 text-truncate">
|
||||
@@ -85,6 +86,8 @@
|
||||
</v-hover>
|
||||
</template>
|
||||
<script>
|
||||
import { useSelectableCommit } from '@/main/lib/stream/composables/commitMultiActions'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
PreviewImage: () => import('@/main/components/common/PreviewImage'),
|
||||
@@ -106,27 +109,22 @@ export default {
|
||||
default: false
|
||||
}
|
||||
},
|
||||
setup(props, ctx) {
|
||||
const { highlighted, selectedState, onSelect } = useSelectableCommit(props, ctx)
|
||||
|
||||
return { highlighted, selectedState, onSelect }
|
||||
},
|
||||
computed: {
|
||||
highlighted() {
|
||||
return this.highlight || this.selected
|
||||
},
|
||||
streamId() {
|
||||
return (
|
||||
this.commit.streamId ?? this.$route.params.streamId ?? this.$route.query.stream
|
||||
this.commit.streamId ||
|
||||
this.commit.stream?.id ||
|
||||
this.$route.params.streamId ||
|
||||
this.$route.query.stream
|
||||
)
|
||||
},
|
||||
selectedState: {
|
||||
get() {
|
||||
return this.selected
|
||||
},
|
||||
set(val) {
|
||||
this.$emit('update:selected', !!val)
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onSelect() {
|
||||
this.$emit('select', { value: this.selected })
|
||||
streamName() {
|
||||
return this.commit.streamName || this.commit.stream?.name
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,10 +2,16 @@
|
||||
<div>
|
||||
<div
|
||||
:class="`${background} d-flex px-2 py-3 mb-2 align-center rounded-lg`"
|
||||
:style="`transition: all 0.2s ease-in-out; ${
|
||||
highlight ? 'outline: 0.2rem solid #047EFB;' : ''
|
||||
}`"
|
||||
:style="`${highlighted ? 'outline: 0.2rem solid #047EFB;' : ''}`"
|
||||
>
|
||||
<v-checkbox
|
||||
v-if="allowSelect"
|
||||
v-model="selectedState"
|
||||
dense
|
||||
hide-details
|
||||
class="mt-0 ml-2 pa-0"
|
||||
@change="onSelect"
|
||||
/>
|
||||
<div class="flex-shrink-0">
|
||||
<user-avatar :id="commit.authorId" :size="30" />
|
||||
</div>
|
||||
@@ -58,6 +64,7 @@
|
||||
<script>
|
||||
import { gql } from '@apollo/client/core'
|
||||
import { limitedCommitActivityFieldsFragment } from '@/graphql/fragments/activity'
|
||||
import { useSelectableCommit } from '@/main/lib/stream/composables/commitMultiActions'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
@@ -102,8 +109,21 @@ export default {
|
||||
highlight: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
allowSelect: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
selected: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
setup(props, ctx) {
|
||||
const { highlighted, selectedState, onSelect } = useSelectableCommit(props, ctx)
|
||||
|
||||
return { highlighted, selectedState, onSelect }
|
||||
},
|
||||
apollo: {
|
||||
activity: {
|
||||
query: gql`
|
||||
|
||||
@@ -7,7 +7,9 @@
|
||||
<prioritized-portal to="actions" identity="commits-multi-select" :priority="2">
|
||||
<div class="d-flex align-center">
|
||||
<v-btn small @click="clear">Clear selection</v-btn>
|
||||
<v-btn small class="ml-2" color="primary" @click="initMove">Move To</v-btn>
|
||||
<v-btn v-if="streamId" small class="ml-2" color="primary" @click="initMove">
|
||||
Move To
|
||||
</v-btn>
|
||||
<v-btn small class="mx-2" color="red" @click="initDelete">Delete</v-btn>
|
||||
</div>
|
||||
</prioritized-portal>
|
||||
@@ -23,6 +25,7 @@
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { Optional } from '@/helpers/typeHelpers'
|
||||
import PrioritizedPortal from '@/main/components/common/utility/PrioritizedPortal.vue'
|
||||
import CommitsBatchActionsDialog from '@/main/dialogs/commit/CommitsBatchActionsDialog.vue'
|
||||
import { BatchActionType } from '@/main/lib/stream/composables/commitMultiActions'
|
||||
@@ -36,26 +39,28 @@ export default defineComponent({
|
||||
},
|
||||
props: {
|
||||
streamId: {
|
||||
type: String,
|
||||
required: true
|
||||
type: String as PropType<Optional<string>>,
|
||||
default: undefined
|
||||
},
|
||||
selectedCommitIds: {
|
||||
type: Array as PropType<string[]>,
|
||||
required: true
|
||||
},
|
||||
branchName: {
|
||||
type: String,
|
||||
required: true
|
||||
type: String as PropType<Optional<string>>,
|
||||
default: undefined
|
||||
}
|
||||
},
|
||||
setup(props, { emit }) {
|
||||
const showDialog = ref(false)
|
||||
const dialogType = ref(BatchActionType.Move)
|
||||
const dialogType = ref(BatchActionType.Delete)
|
||||
|
||||
const count = computed(() => props.selectedCommitIds?.length || 0)
|
||||
|
||||
const clear = () => emit('clear')
|
||||
const initMove = () => {
|
||||
if (!props.streamId) return
|
||||
|
||||
showDialog.value = true
|
||||
dialogType.value = BatchActionType.Move
|
||||
}
|
||||
|
||||
@@ -8,14 +8,18 @@
|
||||
Deleting commits is an irrevocable action! If you are sure about wanting to
|
||||
delete the selected commits, please click on the button below!
|
||||
</template>
|
||||
<template v-else-if="type === BatchActionType.Move">
|
||||
<template
|
||||
v-else-if="
|
||||
type === BatchActionType.Move && supportsBranchScopedActions && streamId
|
||||
"
|
||||
>
|
||||
<div class="mb-4">
|
||||
Please select the target branch to move all of the selected commits to:
|
||||
</div>
|
||||
<branch-select
|
||||
v-model="targetBranch"
|
||||
:stream-id="streamId"
|
||||
:excluded-names="[branchName]"
|
||||
:excluded-names="branchName ? [branchName] : []"
|
||||
/>
|
||||
</template>
|
||||
</template>
|
||||
@@ -27,7 +31,9 @@
|
||||
Delete
|
||||
</v-btn>
|
||||
</template>
|
||||
<template v-else-if="type === BatchActionType.Move">
|
||||
<template
|
||||
v-else-if="type === BatchActionType.Move && supportsBranchScopedActions"
|
||||
>
|
||||
<v-btn color="primary" :disabled="moveDisabled" @click="moveCommits">
|
||||
Move
|
||||
</v-btn>
|
||||
@@ -49,7 +55,7 @@ import {
|
||||
DeleteCommitsMutation,
|
||||
DeleteCommitsMutationVariables
|
||||
} from '@/graphql/generated/graphql'
|
||||
import { Nullable } from '@/helpers/typeHelpers'
|
||||
import { Nullable, Optional } from '@/helpers/typeHelpers'
|
||||
import {
|
||||
convertThrowIntoFetchResult,
|
||||
getFirstErrorMessage
|
||||
@@ -65,12 +71,12 @@ export default defineComponent({
|
||||
},
|
||||
props: {
|
||||
streamId: {
|
||||
type: String,
|
||||
required: true
|
||||
type: String as PropType<Optional<string>>,
|
||||
default: undefined
|
||||
},
|
||||
branchName: {
|
||||
type: String,
|
||||
required: true
|
||||
type: String as PropType<Optional<string>>,
|
||||
default: undefined
|
||||
},
|
||||
selectedCommitIds: {
|
||||
type: Array as PropType<string[]>,
|
||||
@@ -89,6 +95,10 @@ export default defineComponent({
|
||||
const apollo = useApolloClient().client
|
||||
const { triggerNotification } = useGlobalToast()
|
||||
|
||||
const supportsBranchScopedActions = computed(
|
||||
() => props.streamId && props.branchName
|
||||
)
|
||||
|
||||
const targetBranch = ref(null as Nullable<string>)
|
||||
const loading = ref(false)
|
||||
|
||||
@@ -156,7 +166,7 @@ export default defineComponent({
|
||||
|
||||
const moveCommits = async () => {
|
||||
await invokeAction<MoveCommitsMutation, MoveCommitsMutationVariables>({
|
||||
shouldQuit: () => moveDisabled.value,
|
||||
shouldQuit: () => !supportsBranchScopedActions.value || moveDisabled.value,
|
||||
getResult: (data) => data?.commitsMove,
|
||||
document: MoveCommitsDocument,
|
||||
variables: {
|
||||
@@ -193,7 +203,8 @@ export default defineComponent({
|
||||
deleteCommits,
|
||||
targetBranch,
|
||||
moveDisabled,
|
||||
deleteDisabled
|
||||
deleteDisabled,
|
||||
supportsBranchScopedActions
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { ref, computed } from 'vue'
|
||||
import { SetupProps } from '@/helpers/typeHelpers'
|
||||
import { ref, computed, SetupContext } from 'vue'
|
||||
|
||||
export enum BatchActionType {
|
||||
Move = 'move',
|
||||
@@ -44,3 +45,24 @@ export function useCommitMultiActions() {
|
||||
clearSelectedCommits
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Use inside a component that represents a commit that can be selected (e.g. for batch actions)
|
||||
*/
|
||||
export function useSelectableCommit(
|
||||
props: SetupProps<{ allowSelect: boolean; selected: boolean; highlight: boolean }>,
|
||||
ctx: SetupContext
|
||||
) {
|
||||
const highlighted = computed(() => props.highlight || props.selected)
|
||||
const selectedState = computed({
|
||||
get: () => props.selected,
|
||||
set: (newVal) => ctx.emit('update:selected', !!newVal)
|
||||
})
|
||||
const onSelect = () => ctx.emit('select', { value: props.selected })
|
||||
|
||||
return {
|
||||
highlighted,
|
||||
onSelect,
|
||||
selectedState
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
<template>
|
||||
<div>
|
||||
<!-- Toolbar -->
|
||||
<commit-multi-select-toolbar
|
||||
v-if="hasSelectedCommits"
|
||||
:selected-commit-ids="selectedCommitIds"
|
||||
@clear="clearSelectedCommits"
|
||||
@finish="onBatchCommitActionFinish"
|
||||
/>
|
||||
<prioritized-portal to="toolbar" identity="commits" :priority="0">
|
||||
<div class="font-weight-bold">
|
||||
Your Latest Commits
|
||||
@@ -18,7 +23,12 @@
|
||||
lg="4"
|
||||
xl="3"
|
||||
>
|
||||
<commit-preview-card :commit="commit" :preview-height="180" />
|
||||
<commit-preview-card
|
||||
:commit="commit"
|
||||
:preview-height="180"
|
||||
:allow-select="isCommitOrStreamOwner(commit)"
|
||||
:selected.sync="selectedCommitsState[commit.id]"
|
||||
/>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" md="6" lg="4" xl="3">
|
||||
<infinite-loading spinner="waveDots" @infinite="infiniteHandler">
|
||||
@@ -65,6 +75,9 @@ import { gql } from '@apollo/client/core'
|
||||
import { useQuery } from '@vue/apollo-composable'
|
||||
import { computed, defineComponent } from 'vue'
|
||||
import PrioritizedPortal from '@/main/components/common/utility/PrioritizedPortal.vue'
|
||||
import CommitMultiSelectToolbar from '@/main/components/stream/commit/CommitMultiSelectToolbar.vue'
|
||||
import { useCommitMultiActions } from '@/main/lib/stream/composables/commitMultiActions'
|
||||
import { Roles } from '@/helpers/mainConstants'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'TheCommits',
|
||||
@@ -72,10 +85,15 @@ export default defineComponent({
|
||||
InfiniteLoading: () => import('vue-infinite-loading'),
|
||||
CommitPreviewCard: () => import('@/main/components/common/CommitPreviewCard'),
|
||||
NoDataPlaceholder: () => import('@/main/components/common/NoDataPlaceholder'),
|
||||
PrioritizedPortal
|
||||
PrioritizedPortal,
|
||||
CommitMultiSelectToolbar
|
||||
},
|
||||
setup() {
|
||||
const { result, fetchMore: userFetchMore } = useQuery(gql`
|
||||
const {
|
||||
result,
|
||||
fetchMore: userFetchMore,
|
||||
refetch: userRefetch
|
||||
} = useQuery(gql`
|
||||
query ($cursor: String) {
|
||||
user {
|
||||
id
|
||||
@@ -87,12 +105,16 @@ export default defineComponent({
|
||||
id
|
||||
referencedObject
|
||||
message
|
||||
streamName
|
||||
streamId
|
||||
authorId
|
||||
createdAt
|
||||
sourceApplication
|
||||
branchName
|
||||
commentCount
|
||||
stream {
|
||||
id
|
||||
role
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -103,10 +125,32 @@ export default defineComponent({
|
||||
(user.value?.commits.items || []).filter((c) => c.branchName !== 'globals')
|
||||
)
|
||||
|
||||
const isCommitOrStreamOwner = (commit) => {
|
||||
const userId = user.value.id
|
||||
return commit.stream.role === Roles.Stream.Owner || commit.authorId === userId
|
||||
}
|
||||
|
||||
const {
|
||||
selectedCommitIds,
|
||||
hasSelectedCommits,
|
||||
clearSelectedCommits,
|
||||
selectedCommitsState
|
||||
} = useCommitMultiActions()
|
||||
|
||||
const onBatchCommitActionFinish = () => {
|
||||
userRefetch()
|
||||
}
|
||||
|
||||
return {
|
||||
user,
|
||||
commitItems,
|
||||
userFetchMore
|
||||
userFetchMore,
|
||||
selectedCommitIds,
|
||||
hasSelectedCommits,
|
||||
clearSelectedCommits,
|
||||
selectedCommitsState,
|
||||
onBatchCommitActionFinish,
|
||||
isCommitOrStreamOwner
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
||||
@@ -52,13 +52,14 @@
|
||||
<v-col v-if="stream && stream.branch && listMode" cols="12" class="px-4">
|
||||
<v-list v-if="stream.branch.commits.items.length > 0" class="transparent">
|
||||
<list-item-commit
|
||||
v-for="(item, index) in allCommits"
|
||||
v-for="item in allCommits"
|
||||
:key="item.id + 'list'"
|
||||
:commit="item"
|
||||
:stream-id="streamId"
|
||||
:allow-select="isStreamOwner || isCommitOwner(item)"
|
||||
:selected.sync="selectedCommitsState[item.id]"
|
||||
show-received-receipts
|
||||
class="mb-1 rounded"
|
||||
:highlight="index === 0"
|
||||
></list-item-commit>
|
||||
</v-list>
|
||||
</v-col>
|
||||
|
||||
@@ -42,20 +42,6 @@ extend type Commit {
|
||||
commentCount: Int!
|
||||
}
|
||||
|
||||
extend type CommitCollectionUserNode {
|
||||
"""
|
||||
The total number of comments for this commit. To actually get the comments, use the comments query and pass in a resource array consisting of of this commit's id.
|
||||
E.g.,
|
||||
```
|
||||
query{
|
||||
comments(streamId:"streamId" resources:[{resourceType: commit, resourceId:"commitId"}] ){
|
||||
...
|
||||
}
|
||||
```
|
||||
"""
|
||||
commentCount: Int!
|
||||
}
|
||||
|
||||
extend type Object {
|
||||
"""
|
||||
The total number of comments for this commit. To actually get the comments, use the comments query and pass in a resource array consisting of of this object's id.
|
||||
|
||||
@@ -10,7 +10,7 @@ extend type User {
|
||||
Get commits authored by the user. If requested for another user, then only commits
|
||||
from public streams will be returned.
|
||||
"""
|
||||
commits(limit: Int! = 25, cursor: String): CommitCollectionUser
|
||||
commits(limit: Int! = 25, cursor: String): CommitCollection
|
||||
}
|
||||
|
||||
type Branch {
|
||||
@@ -34,19 +34,13 @@ type Commit {
|
||||
authorId: String
|
||||
authorAvatar: String
|
||||
createdAt: DateTime
|
||||
}
|
||||
|
||||
type CommitCollectionUserNode {
|
||||
id: String!
|
||||
referencedObject: String!
|
||||
message: String
|
||||
sourceApplication: String
|
||||
totalChildrenCount: Int
|
||||
branchName: String
|
||||
parents: [String]
|
||||
streamId: String
|
||||
streamName: String
|
||||
createdAt: DateTime
|
||||
streamId: String @deprecated(reason: "Use the stream field instead")
|
||||
streamName: String @deprecated(reason: "Use the stream field instead")
|
||||
"""
|
||||
Will throw an authorization error if active user isn't authorized to see it, for example,
|
||||
if a stream isn't public and the user doesn't have the appropriate rights.
|
||||
"""
|
||||
stream: Stream! @hasRole(role: "server:user") @hasScope(scope: "streams:read")
|
||||
}
|
||||
|
||||
type BranchCollection {
|
||||
@@ -58,13 +52,7 @@ type BranchCollection {
|
||||
type CommitCollection {
|
||||
totalCount: Int!
|
||||
cursor: String
|
||||
items: [Commit]
|
||||
}
|
||||
|
||||
type CommitCollectionUser {
|
||||
totalCount: Int!
|
||||
cursor: String
|
||||
items: [CommitCollectionUserNode]
|
||||
items: [Commit!]
|
||||
}
|
||||
|
||||
extend type Mutation {
|
||||
|
||||
@@ -8,18 +8,19 @@ const crs = require('crypto-random-string')
|
||||
const { gql } = require('apollo-server-express')
|
||||
const { createBlobs } = require('@/modules/blobstorage/tests/helpers')
|
||||
const { expect } = require('chai')
|
||||
const { Users, Streams } = require('@/modules/core/dbSchema')
|
||||
|
||||
describe('Blobs graphql @blobstorage', () => {
|
||||
/** @type {import('apollo-server-express').ApolloServer} */
|
||||
let apollo
|
||||
const user = {
|
||||
name: 'Baron Von Blubba',
|
||||
email: 'barron@bubble.bobble',
|
||||
email: 'zebarron@bubble.bobble',
|
||||
password: 'bubblesAreMyBlobs'
|
||||
}
|
||||
before(async () => {
|
||||
await truncateTables(['blob_storage', Users.name, Streams.name])
|
||||
user.id = await createUser(user)
|
||||
await truncateTables(['blob_storage'])
|
||||
apollo = buildApolloServer({
|
||||
context: () =>
|
||||
addLoadersToCtx({
|
||||
|
||||
@@ -108,14 +108,6 @@ module.exports = {
|
||||
return await getResourceCommentCount({ resourceId: parent.id })
|
||||
}
|
||||
},
|
||||
CommitCollectionUserNode: {
|
||||
// urgh, i think we tripped our gql schemas in there a bit
|
||||
async commentCount(parent, args, context) {
|
||||
if (context.role === Roles.Server.ArchivedUser)
|
||||
throw new ApolloForbiddenError('You are not authorized.')
|
||||
return await getResourceCommentCount({ resourceId: parent.id })
|
||||
}
|
||||
},
|
||||
Object: {
|
||||
async commentCount(parent, args, context) {
|
||||
if (context.role === Roles.Server.ArchivedUser)
|
||||
|
||||
@@ -284,6 +284,15 @@ export type Commit = {
|
||||
parents?: Maybe<Array<Maybe<Scalars['String']>>>;
|
||||
referencedObject: Scalars['String'];
|
||||
sourceApplication?: Maybe<Scalars['String']>;
|
||||
/**
|
||||
* Will throw an authorization error if active user isn't authorized to see it, for example,
|
||||
* if a stream isn't public and the user doesn't have the appropriate rights.
|
||||
*/
|
||||
stream: Stream;
|
||||
/** @deprecated Use the stream field instead */
|
||||
streamId?: Maybe<Scalars['String']>;
|
||||
/** @deprecated Use the stream field instead */
|
||||
streamName?: Maybe<Scalars['String']>;
|
||||
totalChildrenCount?: Maybe<Scalars['Int']>;
|
||||
};
|
||||
|
||||
@@ -299,42 +308,10 @@ export type CommitActivityArgs = {
|
||||
export type CommitCollection = {
|
||||
__typename?: 'CommitCollection';
|
||||
cursor?: Maybe<Scalars['String']>;
|
||||
items?: Maybe<Array<Maybe<Commit>>>;
|
||||
items?: Maybe<Array<Commit>>;
|
||||
totalCount: Scalars['Int'];
|
||||
};
|
||||
|
||||
export type CommitCollectionUser = {
|
||||
__typename?: 'CommitCollectionUser';
|
||||
cursor?: Maybe<Scalars['String']>;
|
||||
items?: Maybe<Array<Maybe<CommitCollectionUserNode>>>;
|
||||
totalCount: Scalars['Int'];
|
||||
};
|
||||
|
||||
export type CommitCollectionUserNode = {
|
||||
__typename?: 'CommitCollectionUserNode';
|
||||
branchName?: Maybe<Scalars['String']>;
|
||||
/**
|
||||
* The total number of comments for this commit. To actually get the comments, use the comments query and pass in a resource array consisting of of this commit's id.
|
||||
* E.g.,
|
||||
* ```
|
||||
* query{
|
||||
* comments(streamId:"streamId" resources:[{resourceType: commit, resourceId:"commitId"}] ){
|
||||
* ...
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
commentCount: Scalars['Int'];
|
||||
createdAt?: Maybe<Scalars['DateTime']>;
|
||||
id: Scalars['String'];
|
||||
message?: Maybe<Scalars['String']>;
|
||||
parents?: Maybe<Array<Maybe<Scalars['String']>>>;
|
||||
referencedObject: Scalars['String'];
|
||||
sourceApplication?: Maybe<Scalars['String']>;
|
||||
streamId?: Maybe<Scalars['String']>;
|
||||
streamName?: Maybe<Scalars['String']>;
|
||||
totalChildrenCount?: Maybe<Scalars['Int']>;
|
||||
};
|
||||
|
||||
export type CommitCreateInput = {
|
||||
branchName: Scalars['String'];
|
||||
message?: InputMaybe<Scalars['String']>;
|
||||
@@ -1471,7 +1448,7 @@ export type User = {
|
||||
* Get commits authored by the user. If requested for another user, then only commits
|
||||
* from public streams will be returned.
|
||||
*/
|
||||
commits?: Maybe<CommitCollectionUser>;
|
||||
commits?: Maybe<CommitCollection>;
|
||||
company?: Maybe<Scalars['String']>;
|
||||
/** Returns the apps you have created. */
|
||||
createdApps?: Maybe<Array<Maybe<ServerApp>>>;
|
||||
@@ -1726,7 +1703,7 @@ export type ResolversTypes = {
|
||||
BlobMetadata: ResolverTypeWrapper<BlobMetadata>;
|
||||
BlobMetadataCollection: ResolverTypeWrapper<BlobMetadataCollection>;
|
||||
Boolean: ResolverTypeWrapper<Scalars['Boolean']>;
|
||||
Branch: ResolverTypeWrapper<Omit<Branch, 'author'> & { author?: Maybe<ResolversTypes['User']> }>;
|
||||
Branch: ResolverTypeWrapper<Omit<Branch, 'author' | 'commits'> & { author?: Maybe<ResolversTypes['User']>, commits?: Maybe<ResolversTypes['CommitCollection']> }>;
|
||||
BranchCollection: ResolverTypeWrapper<Omit<BranchCollection, 'items'> & { items?: Maybe<Array<ResolversTypes['Branch']>> }>;
|
||||
BranchCreateInput: BranchCreateInput;
|
||||
BranchDeleteInput: BranchDeleteInput;
|
||||
@@ -1737,10 +1714,8 @@ export type ResolversTypes = {
|
||||
CommentCreateInput: CommentCreateInput;
|
||||
CommentEditInput: CommentEditInput;
|
||||
CommentThreadActivityMessage: ResolverTypeWrapper<CommentThreadActivityMessage>;
|
||||
Commit: ResolverTypeWrapper<Commit>;
|
||||
CommitCollection: ResolverTypeWrapper<CommitCollection>;
|
||||
CommitCollectionUser: ResolverTypeWrapper<CommitCollectionUser>;
|
||||
CommitCollectionUserNode: ResolverTypeWrapper<CommitCollectionUserNode>;
|
||||
Commit: ResolverTypeWrapper<Omit<Commit, 'stream'> & { stream: ResolversTypes['Stream'] }>;
|
||||
CommitCollection: ResolverTypeWrapper<Omit<CommitCollection, 'items'> & { items?: Maybe<Array<ResolversTypes['Commit']>> }>;
|
||||
CommitCreateInput: CommitCreateInput;
|
||||
CommitDeleteInput: CommitDeleteInput;
|
||||
CommitReceivedInput: CommitReceivedInput;
|
||||
@@ -1791,7 +1766,7 @@ export type ResolversTypes = {
|
||||
StreamUpdatePermissionInput: StreamUpdatePermissionInput;
|
||||
String: ResolverTypeWrapper<Scalars['String']>;
|
||||
Subscription: ResolverTypeWrapper<{}>;
|
||||
User: ResolverTypeWrapper<Omit<User, 'favoriteStreams' | 'streams'> & { favoriteStreams?: Maybe<ResolversTypes['StreamCollection']>, streams?: Maybe<ResolversTypes['StreamCollection']> }>;
|
||||
User: ResolverTypeWrapper<Omit<User, 'commits' | 'favoriteStreams' | 'streams'> & { commits?: Maybe<ResolversTypes['CommitCollection']>, favoriteStreams?: Maybe<ResolversTypes['StreamCollection']>, streams?: Maybe<ResolversTypes['StreamCollection']> }>;
|
||||
UserDeleteInput: UserDeleteInput;
|
||||
UserRoleInput: UserRoleInput;
|
||||
UserSearchResultCollection: ResolverTypeWrapper<UserSearchResultCollection>;
|
||||
@@ -1821,7 +1796,7 @@ export type ResolversParentTypes = {
|
||||
BlobMetadata: BlobMetadata;
|
||||
BlobMetadataCollection: BlobMetadataCollection;
|
||||
Boolean: Scalars['Boolean'];
|
||||
Branch: Omit<Branch, 'author'> & { author?: Maybe<ResolversParentTypes['User']> };
|
||||
Branch: Omit<Branch, 'author' | 'commits'> & { author?: Maybe<ResolversParentTypes['User']>, commits?: Maybe<ResolversParentTypes['CommitCollection']> };
|
||||
BranchCollection: Omit<BranchCollection, 'items'> & { items?: Maybe<Array<ResolversParentTypes['Branch']>> };
|
||||
BranchCreateInput: BranchCreateInput;
|
||||
BranchDeleteInput: BranchDeleteInput;
|
||||
@@ -1832,10 +1807,8 @@ export type ResolversParentTypes = {
|
||||
CommentCreateInput: CommentCreateInput;
|
||||
CommentEditInput: CommentEditInput;
|
||||
CommentThreadActivityMessage: CommentThreadActivityMessage;
|
||||
Commit: Commit;
|
||||
CommitCollection: CommitCollection;
|
||||
CommitCollectionUser: CommitCollectionUser;
|
||||
CommitCollectionUserNode: CommitCollectionUserNode;
|
||||
Commit: Omit<Commit, 'stream'> & { stream: ResolversParentTypes['Stream'] };
|
||||
CommitCollection: Omit<CommitCollection, 'items'> & { items?: Maybe<Array<ResolversParentTypes['Commit']>> };
|
||||
CommitCreateInput: CommitCreateInput;
|
||||
CommitDeleteInput: CommitDeleteInput;
|
||||
CommitReceivedInput: CommitReceivedInput;
|
||||
@@ -1882,7 +1855,7 @@ export type ResolversParentTypes = {
|
||||
StreamUpdatePermissionInput: StreamUpdatePermissionInput;
|
||||
String: Scalars['String'];
|
||||
Subscription: {};
|
||||
User: Omit<User, 'favoriteStreams' | 'streams'> & { favoriteStreams?: Maybe<ResolversParentTypes['StreamCollection']>, streams?: Maybe<ResolversParentTypes['StreamCollection']> };
|
||||
User: Omit<User, 'commits' | 'favoriteStreams' | 'streams'> & { commits?: Maybe<ResolversParentTypes['CommitCollection']>, favoriteStreams?: Maybe<ResolversParentTypes['StreamCollection']>, streams?: Maybe<ResolversParentTypes['StreamCollection']> };
|
||||
UserDeleteInput: UserDeleteInput;
|
||||
UserRoleInput: UserRoleInput;
|
||||
UserSearchResultCollection: UserSearchResultCollection;
|
||||
@@ -2077,39 +2050,20 @@ export type CommitResolvers<ContextType = GraphQLContext, ParentType extends Res
|
||||
parents?: Resolver<Maybe<Array<Maybe<ResolversTypes['String']>>>, ParentType, ContextType>;
|
||||
referencedObject?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
|
||||
sourceApplication?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
|
||||
stream?: Resolver<ResolversTypes['Stream'], ParentType, ContextType>;
|
||||
streamId?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
|
||||
streamName?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
|
||||
totalChildrenCount?: Resolver<Maybe<ResolversTypes['Int']>, ParentType, ContextType>;
|
||||
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
|
||||
};
|
||||
|
||||
export type CommitCollectionResolvers<ContextType = GraphQLContext, ParentType extends ResolversParentTypes['CommitCollection'] = ResolversParentTypes['CommitCollection']> = {
|
||||
cursor?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
|
||||
items?: Resolver<Maybe<Array<Maybe<ResolversTypes['Commit']>>>, ParentType, ContextType>;
|
||||
items?: Resolver<Maybe<Array<ResolversTypes['Commit']>>, ParentType, ContextType>;
|
||||
totalCount?: Resolver<ResolversTypes['Int'], ParentType, ContextType>;
|
||||
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
|
||||
};
|
||||
|
||||
export type CommitCollectionUserResolvers<ContextType = GraphQLContext, ParentType extends ResolversParentTypes['CommitCollectionUser'] = ResolversParentTypes['CommitCollectionUser']> = {
|
||||
cursor?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
|
||||
items?: Resolver<Maybe<Array<Maybe<ResolversTypes['CommitCollectionUserNode']>>>, ParentType, ContextType>;
|
||||
totalCount?: Resolver<ResolversTypes['Int'], ParentType, ContextType>;
|
||||
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
|
||||
};
|
||||
|
||||
export type CommitCollectionUserNodeResolvers<ContextType = GraphQLContext, ParentType extends ResolversParentTypes['CommitCollectionUserNode'] = ResolversParentTypes['CommitCollectionUserNode']> = {
|
||||
branchName?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
|
||||
commentCount?: Resolver<ResolversTypes['Int'], ParentType, ContextType>;
|
||||
createdAt?: Resolver<Maybe<ResolversTypes['DateTime']>, ParentType, ContextType>;
|
||||
id?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
|
||||
message?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
|
||||
parents?: Resolver<Maybe<Array<Maybe<ResolversTypes['String']>>>, ParentType, ContextType>;
|
||||
referencedObject?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
|
||||
sourceApplication?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
|
||||
streamId?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
|
||||
streamName?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
|
||||
totalChildrenCount?: Resolver<Maybe<ResolversTypes['Int']>, ParentType, ContextType>;
|
||||
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
|
||||
};
|
||||
|
||||
export interface DateTimeScalarConfig extends GraphQLScalarTypeConfig<ResolversTypes['DateTime'], any> {
|
||||
name: 'DateTime';
|
||||
}
|
||||
@@ -2439,7 +2393,7 @@ export type UserResolvers<ContextType = GraphQLContext, ParentType extends Resol
|
||||
authorizedApps?: Resolver<Maybe<Array<Maybe<ResolversTypes['ServerAppListItem']>>>, ParentType, ContextType>;
|
||||
avatar?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
|
||||
bio?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
|
||||
commits?: Resolver<Maybe<ResolversTypes['CommitCollectionUser']>, ParentType, ContextType, RequireFields<UserCommitsArgs, 'limit'>>;
|
||||
commits?: Resolver<Maybe<ResolversTypes['CommitCollection']>, ParentType, ContextType, RequireFields<UserCommitsArgs, 'limit'>>;
|
||||
company?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
|
||||
createdApps?: Resolver<Maybe<Array<Maybe<ResolversTypes['ServerApp']>>>, ParentType, ContextType>;
|
||||
email?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
|
||||
@@ -2516,8 +2470,6 @@ export type Resolvers<ContextType = GraphQLContext> = {
|
||||
CommentThreadActivityMessage?: CommentThreadActivityMessageResolvers<ContextType>;
|
||||
Commit?: CommitResolvers<ContextType>;
|
||||
CommitCollection?: CommitCollectionResolvers<ContextType>;
|
||||
CommitCollectionUser?: CommitCollectionUserResolvers<ContextType>;
|
||||
CommitCollectionUserNode?: CommitCollectionUserNodeResolvers<ContextType>;
|
||||
DateTime?: GraphQLScalarType;
|
||||
EmailAddress?: GraphQLScalarType;
|
||||
FileUpload?: FileUploadResolvers<ContextType>;
|
||||
|
||||
@@ -31,6 +31,10 @@ const {
|
||||
batchMoveCommits,
|
||||
batchDeleteCommits
|
||||
} = require('@/modules/core/services/commit/batchCommitActions')
|
||||
const {
|
||||
validateStreamAccess
|
||||
} = require('@/modules/core/services/streams/streamAccessService')
|
||||
const { StreamInvalidAccessError } = require('@/modules/core/errors/stream')
|
||||
|
||||
// subscription events
|
||||
const COMMIT_CREATED = 'COMMIT_CREATED'
|
||||
@@ -40,6 +44,29 @@ const COMMIT_DELETED = 'COMMIT_DELETED'
|
||||
/** @type {import('@/modules/core/graph/generated/graphql').Resolvers} */
|
||||
module.exports = {
|
||||
Query: {},
|
||||
Commit: {
|
||||
async stream(parent, _args, ctx) {
|
||||
const { id: commitId } = parent
|
||||
|
||||
const stream = await ctx.loaders.commits.getCommitStream.load(commitId)
|
||||
if (!stream) {
|
||||
throw new StreamInvalidAccessError('Commit stream not found')
|
||||
}
|
||||
|
||||
await validateStreamAccess(ctx.userId, stream.id)
|
||||
return stream
|
||||
},
|
||||
async streamId(parent, _args, ctx) {
|
||||
const { id: commitId } = parent
|
||||
const stream = await ctx.loaders.commits.getCommitStream.load(commitId)
|
||||
return stream?.id || null
|
||||
},
|
||||
async streamName(parent, _args, ctx) {
|
||||
const { id: commitId } = parent
|
||||
const stream = await ctx.loaders.commits.getCommitStream.load(commitId)
|
||||
return stream?.name || null
|
||||
}
|
||||
},
|
||||
Stream: {
|
||||
async commits(parent, args) {
|
||||
if (args.limit && args.limit > 100)
|
||||
|
||||
@@ -22,10 +22,10 @@ const {
|
||||
|
||||
const {
|
||||
authorizeResolver,
|
||||
validateScopes,
|
||||
validateServerRole,
|
||||
pubsub,
|
||||
StreamPubsubEvents
|
||||
StreamPubsubEvents,
|
||||
validateScopes,
|
||||
validateServerRole
|
||||
} = require(`@/modules/shared`)
|
||||
const { saveActivity } = require(`@/modules/activitystream/services`)
|
||||
const { ActionTypes } = require('@/modules/activitystream/helpers/types')
|
||||
@@ -102,7 +102,7 @@ const _deleteStream = async (parent, args, context) => {
|
||||
*/
|
||||
module.exports = {
|
||||
Query: {
|
||||
async stream(parent, args, context) {
|
||||
async stream(_, args, context) {
|
||||
const stream = await getStream({ streamId: args.id, userId: context.userId })
|
||||
if (!stream) throw new ApolloError('Stream not found')
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ import {
|
||||
} from '@/modules/core/helpers/types'
|
||||
import { Nullable } from '@/modules/shared/helpers/typeHelper'
|
||||
import { ServerInviteRecord } from '@/modules/serverinvites/helpers/types'
|
||||
import { getCommitStreams } from '@/modules/core/repositories/commits'
|
||||
|
||||
/**
|
||||
* Build request-scoped dataloaders
|
||||
@@ -78,9 +79,20 @@ export function buildRequestLoaders(ctx: AuthContext) {
|
||||
if (!userId) return streamIds.map(() => null)
|
||||
|
||||
const results = await getStreamRoles(userId, streamIds.slice())
|
||||
return streamIds.map((id) => results[id])
|
||||
return streamIds.map((id) => results[id] || null)
|
||||
})
|
||||
},
|
||||
commits: {
|
||||
/**
|
||||
* Get a commit's stream from DB
|
||||
*/
|
||||
getCommitStream: new DataLoader<string, Nullable<StreamRecord>>(
|
||||
async (commitIds) => {
|
||||
const results = await getCommitStreams(commitIds.slice())
|
||||
return commitIds.map((id) => results[id] || null)
|
||||
}
|
||||
)
|
||||
},
|
||||
users: {
|
||||
/**
|
||||
* Get user from DB
|
||||
|
||||
@@ -2,10 +2,15 @@ import {
|
||||
BranchCommits,
|
||||
Branches,
|
||||
Commits,
|
||||
StreamCommits
|
||||
StreamCommits,
|
||||
Streams
|
||||
} from '@/modules/core/dbSchema'
|
||||
import { BranchCommitRecord, CommitRecord } from '@/modules/core/helpers/types'
|
||||
import { uniqBy } from 'lodash'
|
||||
import {
|
||||
BranchCommitRecord,
|
||||
CommitRecord,
|
||||
StreamRecord
|
||||
} from '@/modules/core/helpers/types'
|
||||
import { keyBy, uniqBy } from 'lodash'
|
||||
|
||||
const CommitWithStreamBranchMetadataFields = [
|
||||
...Commits.cols,
|
||||
@@ -20,6 +25,23 @@ export type CommitWithStreamBranchMetadata = CommitRecord & {
|
||||
branchName: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Get commit associated streams. Results are streams keyed by commit IDs
|
||||
*/
|
||||
export async function getCommitStreams(commitIds: string[]) {
|
||||
const q = Commits.knex()
|
||||
.select<Array<StreamRecord & { commitId: string }>>([
|
||||
...Streams.cols,
|
||||
`${Commits.col.id} as commitId`
|
||||
])
|
||||
.whereIn(Commits.col.id, commitIds)
|
||||
.innerJoin(StreamCommits.name, StreamCommits.col.commitId, Commits.col.id)
|
||||
.innerJoin(Streams.name, Streams.col.id, StreamCommits.col.streamId)
|
||||
|
||||
const results = await q
|
||||
return keyBy(results, (r) => r.commitId)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get commits with their stream and branch IDs
|
||||
*/
|
||||
|
||||
@@ -263,13 +263,17 @@ module.exports = {
|
||||
'commits.createdAt',
|
||||
{ branchName: 'branches.name' },
|
||||
{ streamId: 'stream_commits.streamId' },
|
||||
{ streamName: 'streams.name' }
|
||||
{ streamName: 'streams.name' },
|
||||
{ authorName: 'users.name' },
|
||||
{ authorId: 'users.id' },
|
||||
{ authorAvatar: 'users.avatar' }
|
||||
])
|
||||
.select()
|
||||
.join('stream_commits', 'commits.id', 'stream_commits.commitId')
|
||||
.join('streams', 'stream_commits.streamId', 'streams.id')
|
||||
.join('branch_commits', 'commits.id', 'branch_commits.commitId')
|
||||
.join('branches', 'branches.id', 'branch_commits.branchId')
|
||||
.leftJoin('users', 'commits.author', 'users.id')
|
||||
.where('author', userId)
|
||||
|
||||
if (publicOnly) query.andWhere('streams.isPublic', true)
|
||||
|
||||
@@ -16,17 +16,21 @@ const {
|
||||
|
||||
/**
|
||||
* Validate that the user has the required permission level (or one above it) for the specified stream
|
||||
* @param {string} userId
|
||||
* @param {string} [userId] If falsy, will throw for non-public streams
|
||||
* @param {string} streamId
|
||||
* @param {string} expectedRole
|
||||
* @param {string} [expectedRole] Defaults to reviewer
|
||||
* @returns {Promise<boolean>}
|
||||
*/
|
||||
async function validateStreamAccess(userId, streamId, expectedRole) {
|
||||
expectedRole = expectedRole || Roles.Stream.Reviewer
|
||||
|
||||
const streamRoles = Object.values(Roles.Stream)
|
||||
if (!streamRoles.includes(expectedRole)) {
|
||||
throw new LogicError('Unexpected stream role')
|
||||
}
|
||||
|
||||
userId = userId || null
|
||||
|
||||
try {
|
||||
await authorizeResolver(userId, streamId, expectedRole)
|
||||
} catch (e) {
|
||||
|
||||
@@ -142,6 +142,8 @@ async function validateScopes(scopes, scope) {
|
||||
* @param {string} requiredRole
|
||||
*/
|
||||
async function authorizeResolver(userId, resourceId, requiredRole) {
|
||||
userId = userId || null
|
||||
|
||||
if (!roles) roles = await knex('user_roles').select('*')
|
||||
|
||||
// TODO: Cache these results with a TTL of 1 mins or so, it's pointless to query the db every time we get a ping.
|
||||
@@ -155,7 +157,7 @@ async function authorizeResolver(userId, resourceId, requiredRole) {
|
||||
.select('isPublic')
|
||||
.where({ id: resourceId })
|
||||
.first()
|
||||
if (isPublic && roles[requiredRole] < 200) return true
|
||||
if (isPublic && role.weight < 200) return true
|
||||
} catch (e) {
|
||||
throw new ApolloError(
|
||||
`Resource of type ${role.resourceTarget} with ${resourceId} not found`
|
||||
|
||||
@@ -278,6 +278,15 @@ export type Commit = {
|
||||
parents?: Maybe<Array<Maybe<Scalars['String']>>>;
|
||||
referencedObject: Scalars['String'];
|
||||
sourceApplication?: Maybe<Scalars['String']>;
|
||||
/**
|
||||
* Will throw an authorization error if active user isn't authorized to see it, for example,
|
||||
* if a stream isn't public and the user doesn't have the appropriate rights.
|
||||
*/
|
||||
stream: Stream;
|
||||
/** @deprecated Use the stream field instead */
|
||||
streamId?: Maybe<Scalars['String']>;
|
||||
/** @deprecated Use the stream field instead */
|
||||
streamName?: Maybe<Scalars['String']>;
|
||||
totalChildrenCount?: Maybe<Scalars['Int']>;
|
||||
};
|
||||
|
||||
@@ -293,42 +302,10 @@ export type CommitActivityArgs = {
|
||||
export type CommitCollection = {
|
||||
__typename?: 'CommitCollection';
|
||||
cursor?: Maybe<Scalars['String']>;
|
||||
items?: Maybe<Array<Maybe<Commit>>>;
|
||||
items?: Maybe<Array<Commit>>;
|
||||
totalCount: Scalars['Int'];
|
||||
};
|
||||
|
||||
export type CommitCollectionUser = {
|
||||
__typename?: 'CommitCollectionUser';
|
||||
cursor?: Maybe<Scalars['String']>;
|
||||
items?: Maybe<Array<Maybe<CommitCollectionUserNode>>>;
|
||||
totalCount: Scalars['Int'];
|
||||
};
|
||||
|
||||
export type CommitCollectionUserNode = {
|
||||
__typename?: 'CommitCollectionUserNode';
|
||||
branchName?: Maybe<Scalars['String']>;
|
||||
/**
|
||||
* The total number of comments for this commit. To actually get the comments, use the comments query and pass in a resource array consisting of of this commit's id.
|
||||
* E.g.,
|
||||
* ```
|
||||
* query{
|
||||
* comments(streamId:"streamId" resources:[{resourceType: commit, resourceId:"commitId"}] ){
|
||||
* ...
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
commentCount: Scalars['Int'];
|
||||
createdAt?: Maybe<Scalars['DateTime']>;
|
||||
id: Scalars['String'];
|
||||
message?: Maybe<Scalars['String']>;
|
||||
parents?: Maybe<Array<Maybe<Scalars['String']>>>;
|
||||
referencedObject: Scalars['String'];
|
||||
sourceApplication?: Maybe<Scalars['String']>;
|
||||
streamId?: Maybe<Scalars['String']>;
|
||||
streamName?: Maybe<Scalars['String']>;
|
||||
totalChildrenCount?: Maybe<Scalars['Int']>;
|
||||
};
|
||||
|
||||
export type CommitCreateInput = {
|
||||
branchName: Scalars['String'];
|
||||
message?: InputMaybe<Scalars['String']>;
|
||||
@@ -1465,7 +1442,7 @@ export type User = {
|
||||
* Get commits authored by the user. If requested for another user, then only commits
|
||||
* from public streams will be returned.
|
||||
*/
|
||||
commits?: Maybe<CommitCollectionUser>;
|
||||
commits?: Maybe<CommitCollection>;
|
||||
company?: Maybe<Scalars['String']>;
|
||||
/** Returns the apps you have created. */
|
||||
createdApps?: Maybe<Array<Maybe<ServerApp>>>;
|
||||
@@ -1709,7 +1686,7 @@ export type ReadStreamBranchCommitsQueryVariables = Exact<{
|
||||
}>;
|
||||
|
||||
|
||||
export type ReadStreamBranchCommitsQuery = { __typename?: 'Query', stream?: { __typename?: 'Stream', id: string, name: string, role?: string | null, branch?: { __typename?: 'Branch', id: string, name: string, description?: string | null, commits?: { __typename?: 'CommitCollection', totalCount: number, cursor?: string | null, items?: Array<{ __typename?: 'Commit', id: string, authorName?: string | null, authorId?: string | null, authorAvatar?: string | null, sourceApplication?: string | null, message?: string | null, referencedObject: string, createdAt?: string | null, commentCount: number } | null> | null } | null } | null } | null };
|
||||
export type ReadStreamBranchCommitsQuery = { __typename?: 'Query', stream?: { __typename?: 'Stream', id: string, name: string, role?: string | null, branch?: { __typename?: 'Branch', id: string, name: string, description?: string | null, commits?: { __typename?: 'CommitCollection', totalCount: number, cursor?: string | null, items?: Array<{ __typename?: 'Commit', id: string, authorName?: string | null, authorId?: string | null, authorAvatar?: string | null, sourceApplication?: string | null, message?: string | null, referencedObject: string, createdAt?: string | null, commentCount: number }> | null } | null } | null } | null };
|
||||
|
||||
export type MoveCommitsMutationVariables = Exact<{
|
||||
input: CommitsMoveInput;
|
||||
|
||||
Reference in New Issue
Block a user