feat: quick polls in comments that end in a decision

«📊 Añadir una encuesta» adds 2 to 6 options to a new comment. Members,
viewers too, vote by clicking an option (one vote each, which they can
change or take back) and see each option's votes as a bar. Whoever
started it, or an admin, closes it with «Cerrar y decidir»: the most
voted wins (none on a tie) and a «Decidido: …» reply marked as a
decision shows above the ticket. Events of the comment (voted, closed).

API: addComment(…, poll), votePoll, closePoll, Comment.poll;
POST /api/comments/{id}/poll/vote and …/poll/close. MCP: add_comment
poll, vote_poll, close_poll.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-20 20:48:12 +02:00
parent 93d028e870
commit 3a46a236e4
41 changed files with 2144 additions and 77 deletions

View File

@@ -745,6 +745,14 @@ export type IssueFieldsFragment = {
users: Array<{ id: string; name: string }>;
}>;
decidedBy: { id: string; name: string } | null;
poll: {
closed: boolean;
winner: number | null;
options: Array<{
text: string;
voters: Array<{ id: string; name: string }>;
}>;
} | null;
resolvedBy: { id: string; name: string } | null;
author: { id: string; name: string };
}>;
@@ -800,6 +808,14 @@ export type CommentFieldsFragment = {
users: Array<{ id: string; name: string }>;
}>;
decidedBy: { id: string; name: string } | null;
poll: {
closed: boolean;
winner: number | null;
options: Array<{
text: string;
voters: Array<{ id: string; name: string }>;
}>;
} | null;
resolvedBy: { id: string; name: string } | null;
author: { id: string; name: string };
};
@@ -1002,6 +1018,14 @@ export type IssueQuery = {
users: Array<{ id: string; name: string }>;
}>;
decidedBy: { id: string; name: string } | null;
poll: {
closed: boolean;
winner: number | null;
options: Array<{
text: string;
voters: Array<{ id: string; name: string }>;
}>;
} | null;
resolvedBy: { id: string; name: string } | null;
author: { id: string; name: string };
}>;
@@ -1365,6 +1389,14 @@ export type IssueCommentsQuery = {
users: Array<{ id: string; name: string }>;
}>;
decidedBy: { id: string; name: string } | null;
poll: {
closed: boolean;
winner: number | null;
options: Array<{
text: string;
voters: Array<{ id: string; name: string }>;
}>;
} | null;
resolvedBy: { id: string; name: string } | null;
author: { id: string; name: string };
}>;
@@ -1376,12 +1408,31 @@ export type AddCommentMutationVariables = Exact<{
issueId: string;
body: string;
replyTo?: string | null | undefined;
poll?: Array<string> | string | null | undefined;
}>;
export type AddCommentMutation = {
addComment: { id: string; key: string | null; version: number };
};
export type VotePollMutationVariables = Exact<{
commentId: string;
option?: number | null | undefined;
}>;
export type VotePollMutation = {
votePoll: { id: string; key: string | null; version: number };
};
export type ClosePollMutationVariables = Exact<{
commentId: string;
decide: boolean;
}>;
export type ClosePollMutation = {
closePoll: { id: string; key: string | null; version: number };
};
export type EditCommentMutationVariables = Exact<{
commentId: string;
body: string;
@@ -1810,6 +1861,14 @@ export type SearchDecisionsQuery = {
users: Array<{ id: string; name: string }>;
}>;
decidedBy: { id: string; name: string } | null;
poll: {
closed: boolean;
winner: number | null;
options: Array<{
text: string;
voters: Array<{ id: string; name: string }>;
}>;
} | null;
resolvedBy: { id: string; name: string } | null;
author: { id: string; name: string };
};
@@ -3135,6 +3194,14 @@ export type CommentChangedSubscription = {
users: Array<{ id: string; name: string }>;
}>;
decidedBy: { id: string; name: string } | null;
poll: {
closed: boolean;
winner: number | null;
options: Array<{
text: string;
voters: Array<{ id: string; name: string }>;
}>;
} | null;
resolvedBy: { id: string; name: string } | null;
author: { id: string; name: string };
} | null;
@@ -3925,6 +3992,40 @@ export const CommentFieldsFragmentDoc = {
],
},
},
{
kind: 'Field',
name: { kind: 'Name', value: 'poll' },
selectionSet: {
kind: 'SelectionSet',
selections: [
{
kind: 'Field',
name: { kind: 'Name', value: 'options' },
selectionSet: {
kind: 'SelectionSet',
selections: [
{ kind: 'Field', name: { kind: 'Name', value: 'text' } },
{
kind: 'Field',
name: { kind: 'Name', value: 'voters' },
selectionSet: {
kind: 'SelectionSet',
selections: [
{
kind: 'FragmentSpread',
name: { kind: 'Name', value: 'UserRefFields' },
},
],
},
},
],
},
},
{ kind: 'Field', name: { kind: 'Name', value: 'closed' } },
{ kind: 'Field', name: { kind: 'Name', value: 'winner' } },
],
},
},
{ kind: 'Field', name: { kind: 'Name', value: 'resolved' } },
{
kind: 'Field',
@@ -4336,6 +4437,40 @@ export const IssueFieldsFragmentDoc = {
],
},
},
{
kind: 'Field',
name: { kind: 'Name', value: 'poll' },
selectionSet: {
kind: 'SelectionSet',
selections: [
{
kind: 'Field',
name: { kind: 'Name', value: 'options' },
selectionSet: {
kind: 'SelectionSet',
selections: [
{ kind: 'Field', name: { kind: 'Name', value: 'text' } },
{
kind: 'Field',
name: { kind: 'Name', value: 'voters' },
selectionSet: {
kind: 'SelectionSet',
selections: [
{
kind: 'FragmentSpread',
name: { kind: 'Name', value: 'UserRefFields' },
},
],
},
},
],
},
},
{ kind: 'Field', name: { kind: 'Name', value: 'closed' } },
{ kind: 'Field', name: { kind: 'Name', value: 'winner' } },
],
},
},
{ kind: 'Field', name: { kind: 'Name', value: 'resolved' } },
{
kind: 'Field',
@@ -6598,6 +6733,40 @@ export const IssueDocument = {
],
},
},
{
kind: 'Field',
name: { kind: 'Name', value: 'poll' },
selectionSet: {
kind: 'SelectionSet',
selections: [
{
kind: 'Field',
name: { kind: 'Name', value: 'options' },
selectionSet: {
kind: 'SelectionSet',
selections: [
{ kind: 'Field', name: { kind: 'Name', value: 'text' } },
{
kind: 'Field',
name: { kind: 'Name', value: 'voters' },
selectionSet: {
kind: 'SelectionSet',
selections: [
{
kind: 'FragmentSpread',
name: { kind: 'Name', value: 'UserRefFields' },
},
],
},
},
],
},
},
{ kind: 'Field', name: { kind: 'Name', value: 'closed' } },
{ kind: 'Field', name: { kind: 'Name', value: 'winner' } },
],
},
},
{ kind: 'Field', name: { kind: 'Name', value: 'resolved' } },
{
kind: 'Field',
@@ -8915,6 +9084,40 @@ export const IssueCommentsDocument = {
],
},
},
{
kind: 'Field',
name: { kind: 'Name', value: 'poll' },
selectionSet: {
kind: 'SelectionSet',
selections: [
{
kind: 'Field',
name: { kind: 'Name', value: 'options' },
selectionSet: {
kind: 'SelectionSet',
selections: [
{ kind: 'Field', name: { kind: 'Name', value: 'text' } },
{
kind: 'Field',
name: { kind: 'Name', value: 'voters' },
selectionSet: {
kind: 'SelectionSet',
selections: [
{
kind: 'FragmentSpread',
name: { kind: 'Name', value: 'UserRefFields' },
},
],
},
},
],
},
},
{ kind: 'Field', name: { kind: 'Name', value: 'closed' } },
{ kind: 'Field', name: { kind: 'Name', value: 'winner' } },
],
},
},
{ kind: 'Field', name: { kind: 'Name', value: 'resolved' } },
{
kind: 'Field',
@@ -8987,6 +9190,20 @@ export const AddCommentDocument = {
},
type: { kind: 'NamedType', name: { kind: 'Name', value: 'ID' } },
},
{
kind: 'VariableDefinition',
variable: { kind: 'Variable', name: { kind: 'Name', value: 'poll' } },
type: {
kind: 'ListType',
type: {
kind: 'NonNullType',
type: {
kind: 'NamedType',
name: { kind: 'Name', value: 'String' },
},
},
},
},
],
selectionSet: {
kind: 'SelectionSet',
@@ -9019,6 +9236,14 @@ export const AddCommentDocument = {
name: { kind: 'Name', value: 'replyTo' },
},
},
{
kind: 'Argument',
name: { kind: 'Name', value: 'poll' },
value: {
kind: 'Variable',
name: { kind: 'Name', value: 'poll' },
},
},
],
selectionSet: {
kind: 'SelectionSet',
@@ -9051,6 +9276,178 @@ export const AddCommentDocument = {
},
],
} as unknown as DocumentNode<AddCommentMutation, AddCommentMutationVariables>;
export const VotePollDocument = {
kind: 'Document',
definitions: [
{
kind: 'OperationDefinition',
operation: 'mutation',
name: { kind: 'Name', value: 'VotePoll' },
variableDefinitions: [
{
kind: 'VariableDefinition',
variable: {
kind: 'Variable',
name: { kind: 'Name', value: 'commentId' },
},
type: {
kind: 'NonNullType',
type: { kind: 'NamedType', name: { kind: 'Name', value: 'ID' } },
},
},
{
kind: 'VariableDefinition',
variable: {
kind: 'Variable',
name: { kind: 'Name', value: 'option' },
},
type: { kind: 'NamedType', name: { kind: 'Name', value: 'Int' } },
},
],
selectionSet: {
kind: 'SelectionSet',
selections: [
{
kind: 'Field',
name: { kind: 'Name', value: 'votePoll' },
arguments: [
{
kind: 'Argument',
name: { kind: 'Name', value: 'commentId' },
value: {
kind: 'Variable',
name: { kind: 'Name', value: 'commentId' },
},
},
{
kind: 'Argument',
name: { kind: 'Name', value: 'option' },
value: {
kind: 'Variable',
name: { kind: 'Name', value: 'option' },
},
},
],
selectionSet: {
kind: 'SelectionSet',
selections: [
{
kind: 'FragmentSpread',
name: { kind: 'Name', value: 'CommandResultFields' },
},
],
},
},
],
},
},
{
kind: 'FragmentDefinition',
name: { kind: 'Name', value: 'CommandResultFields' },
typeCondition: {
kind: 'NamedType',
name: { kind: 'Name', value: 'CommandResult' },
},
selectionSet: {
kind: 'SelectionSet',
selections: [
{ kind: 'Field', name: { kind: 'Name', value: 'id' } },
{ kind: 'Field', name: { kind: 'Name', value: 'key' } },
{ kind: 'Field', name: { kind: 'Name', value: 'version' } },
],
},
},
],
} as unknown as DocumentNode<VotePollMutation, VotePollMutationVariables>;
export const ClosePollDocument = {
kind: 'Document',
definitions: [
{
kind: 'OperationDefinition',
operation: 'mutation',
name: { kind: 'Name', value: 'ClosePoll' },
variableDefinitions: [
{
kind: 'VariableDefinition',
variable: {
kind: 'Variable',
name: { kind: 'Name', value: 'commentId' },
},
type: {
kind: 'NonNullType',
type: { kind: 'NamedType', name: { kind: 'Name', value: 'ID' } },
},
},
{
kind: 'VariableDefinition',
variable: {
kind: 'Variable',
name: { kind: 'Name', value: 'decide' },
},
type: {
kind: 'NonNullType',
type: {
kind: 'NamedType',
name: { kind: 'Name', value: 'Boolean' },
},
},
},
],
selectionSet: {
kind: 'SelectionSet',
selections: [
{
kind: 'Field',
name: { kind: 'Name', value: 'closePoll' },
arguments: [
{
kind: 'Argument',
name: { kind: 'Name', value: 'commentId' },
value: {
kind: 'Variable',
name: { kind: 'Name', value: 'commentId' },
},
},
{
kind: 'Argument',
name: { kind: 'Name', value: 'decide' },
value: {
kind: 'Variable',
name: { kind: 'Name', value: 'decide' },
},
},
],
selectionSet: {
kind: 'SelectionSet',
selections: [
{
kind: 'FragmentSpread',
name: { kind: 'Name', value: 'CommandResultFields' },
},
],
},
},
],
},
},
{
kind: 'FragmentDefinition',
name: { kind: 'Name', value: 'CommandResultFields' },
typeCondition: {
kind: 'NamedType',
name: { kind: 'Name', value: 'CommandResult' },
},
selectionSet: {
kind: 'SelectionSet',
selections: [
{ kind: 'Field', name: { kind: 'Name', value: 'id' } },
{ kind: 'Field', name: { kind: 'Name', value: 'key' } },
{ kind: 'Field', name: { kind: 'Name', value: 'version' } },
],
},
},
],
} as unknown as DocumentNode<ClosePollMutation, ClosePollMutationVariables>;
export const EditCommentDocument = {
kind: 'Document',
definitions: [
@@ -11787,6 +12184,40 @@ export const SearchDecisionsDocument = {
],
},
},
{
kind: 'Field',
name: { kind: 'Name', value: 'poll' },
selectionSet: {
kind: 'SelectionSet',
selections: [
{
kind: 'Field',
name: { kind: 'Name', value: 'options' },
selectionSet: {
kind: 'SelectionSet',
selections: [
{ kind: 'Field', name: { kind: 'Name', value: 'text' } },
{
kind: 'Field',
name: { kind: 'Name', value: 'voters' },
selectionSet: {
kind: 'SelectionSet',
selections: [
{
kind: 'FragmentSpread',
name: { kind: 'Name', value: 'UserRefFields' },
},
],
},
},
],
},
},
{ kind: 'Field', name: { kind: 'Name', value: 'closed' } },
{ kind: 'Field', name: { kind: 'Name', value: 'winner' } },
],
},
},
{ kind: 'Field', name: { kind: 'Name', value: 'resolved' } },
{
kind: 'Field',
@@ -18728,6 +19159,40 @@ export const CommentChangedDocument = {
],
},
},
{
kind: 'Field',
name: { kind: 'Name', value: 'poll' },
selectionSet: {
kind: 'SelectionSet',
selections: [
{
kind: 'Field',
name: { kind: 'Name', value: 'options' },
selectionSet: {
kind: 'SelectionSet',
selections: [
{ kind: 'Field', name: { kind: 'Name', value: 'text' } },
{
kind: 'Field',
name: { kind: 'Name', value: 'voters' },
selectionSet: {
kind: 'SelectionSet',
selections: [
{
kind: 'FragmentSpread',
name: { kind: 'Name', value: 'UserRefFields' },
},
],
},
},
],
},
},
{ kind: 'Field', name: { kind: 'Name', value: 'closed' } },
{ kind: 'Field', name: { kind: 'Name', value: 'winner' } },
],
},
},
{ kind: 'Field', name: { kind: 'Name', value: 'resolved' } },
{
kind: 'Field',