Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 27 additions & 27 deletions kompassi-v2-frontend/src/__generated__/gql.ts

Large diffs are not rendered by default.

64 changes: 32 additions & 32 deletions kompassi-v2-frontend/src/__generated__/graphql.ts

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ const query = graphql(`
slug
title(lang: $locale)
canRemove
countPendingInvitations
canGrantAccess
purpose
languages {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,10 @@ import Link from "next/link";
import { ReactNode } from "react";
import Card from "react-bootstrap/Card";
import CardBody from "react-bootstrap/CardBody";
import { Survey } from "../../../surveys/[surveySlug]/edit/models";
import {
getLanguageVersionNames,
Survey,
} from "../../../surveys/[surveySlug]/edit/models";
import SurveyEditorTabs from "../../../surveys/[surveySlug]/edit/SurveyEditorTabs";
import { deleteProgramForm } from "./actions";
import type { Translations } from "@/translations/en";
Expand Down Expand Up @@ -54,10 +57,33 @@ export default function ProgramFormEditorView({
submitButtonVariant="danger"
action={deleteProgramForm.bind(null, event.slug, survey.slug)}
disabled={!survey.canRemove}
// TODO disabledMessage={t.actions.deleteSurvey.cannotRemove}
labelTitle={
survey.canRemove
? undefined
: t.actions.deleteProgramForm.cannotRemove
Comment thread
jkjuopperi marked this conversation as resolved.
Resolved
}
>
{t.actions.deleteProgramForm.confirmation(
survey.title || survey.slug,
<p>
{t.actions.deleteProgramForm.confirmation(
survey.title || survey.slug,
)}
</p>
{survey.languages.length > 0 && (
<p>
{t.actions.deleteProgramForm.languageVersionsWillBeRemoved(
getLanguageVersionNames(
survey,
translations.LanguageSwitcher.supportedLanguages,
),
)}
</p>
)}
{survey.countPendingInvitations > 0 && (
<p>
{t.actions.deleteProgramForm.pendingInvitationsWillBeRemoved(
survey.countPendingInvitations,
)}
</p>
)}
</ModalButton>
</ViewHeadingActions>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ graphql(`
slug
title(lang: $locale)
canRemove
countPendingInvitations
canGrantAccess
purpose

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ graphql(`
activeFrom
activeUntil
canRemove
countPendingInvitations
canGrantAccess
purpose

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ graphql(`
slug
title(lang: $locale)
canRemove
countPendingInvitations
canGrantAccess
purpose

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ const query = graphql(`
title(lang: $locale)
purpose
canRemove
countPendingInvitations
canGrantAccess
languages {
language
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ const query = graphql(`
slug
title(lang: $locale)
canRemove
countPendingInvitations
canGrantAccess
purpose
languages {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { ReactNode } from "react";
import Card from "react-bootstrap/Card";
import CardBody from "react-bootstrap/CardBody";
import { deleteSurvey } from "./actions";
import { Survey } from "./models";
import { getLanguageVersionNames, Survey } from "./models";
import SurveyEditorTabs from "./SurveyEditorTabs";
import { getTranslations } from "@/translations";

Expand Down Expand Up @@ -57,9 +57,30 @@ export default function SurveyEditorView({
submitButtonVariant="danger"
action={deleteSurvey.bind(null, eventSlug, survey.slug)}
disabled={!survey.canRemove}
// TODO disabledMessage={t.actions.deleteSurvey.cannotRemove}
labelTitle={
survey.canRemove ? undefined : t.actions.deleteSurvey.cannotRemove
}
>
{t.actions.deleteSurvey.confirmation(survey.title || survey.slug)}
<p>
{t.actions.deleteSurvey.confirmation(survey.title || survey.slug)}
</p>
{survey.languages.length > 0 && (
<p>
{t.actions.deleteSurvey.languageVersionsWillBeRemoved(
getLanguageVersionNames(
survey,
translations.LanguageSwitcher.supportedLanguages,
),
)}
</p>
)}
{survey.countPendingInvitations > 0 && (
<p>
{t.actions.deleteSurvey.pendingInvitationsWillBeRemoved(
survey.countPendingInvitations,
)}
</p>
)}
</ModalButton>
</ViewHeadingActions>
</ViewHeadingActionsWrapper>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ graphql(`
slug
title(lang: $locale)
canRemove
countPendingInvitations
canGrantAccess
purpose

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ graphql(`
slug
title(lang: $locale)
canRemove
countPendingInvitations
canGrantAccess
purpose

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,23 @@ export interface Survey {
slug: string;
title?: string | null;
canRemove: boolean;
countPendingInvitations: number;
canGrantAccess: boolean;
languages: {
language: string;
}[];
purpose: "DEFAULT" | "INVITE" | "FOLLOWUP";
}

/// Human-readable names of the language versions of a survey, eg. "Finnish, English"
export function getLanguageVersionNames(
survey: Survey,
supportedLanguages: Record<string, string>,
): string {
return survey.languages
.map(
({ language }) =>
supportedLanguages[language.toLowerCase()] ?? language.toUpperCase(),
)
.join(", ");
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ graphql(`
activeUntil
responsesEditableUntil
canRemove
countPendingInvitations
canGrantAccess
purpose
protectResponses
Expand Down
17 changes: 15 additions & 2 deletions kompassi-v2-frontend/src/translations/en.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1806,7 +1806,13 @@ const translations = {
deleteProgramForm: {
title: "Remove program form",
cannotRemove:
"A program form that has program offers cannot be removed.",
"A program form that has program offers or follow-up surveys cannot be removed.",
languageVersionsWillBeRemoved: (languages: string) =>
`The following language versions of the program form will also be removed: ${languages}.`,
pendingInvitationsWillBeRemoved: (count: number) =>
count === 1
? "1 invitation that has not been accepted yet will also be removed. The link in the invitation email will stop working."
: `${count} invitations that have not been accepted yet will also be removed. The links in the invitation emails will stop working.`,
confirmation: (surveyTitle: string) => (
<>
Are you sure you want to remove the program form{" "}
Expand Down Expand Up @@ -2860,7 +2866,14 @@ const translations = {
},
deleteSurvey: {
title: "Remove survey",
cannotRemove: "A survey that has responses cannot be removed.",
cannotRemove:
"A survey that has responses or follow-up surveys cannot be removed.",
languageVersionsWillBeRemoved: (languages: string) =>
`The following language versions of the survey will also be removed: ${languages}.`,
pendingInvitationsWillBeRemoved: (count: number) =>
count === 1
? "1 invitation that has not been accepted yet will also be removed. The link in the invitation email will stop working."
: `${count} invitations that have not been accepted yet will also be removed. The links in the invitation emails will stop working.`,
confirmation: (surveyTitle: string) => (
<>
Are you sure you want to remove the survey{" "}
Expand Down
17 changes: 15 additions & 2 deletions kompassi-v2-frontend/src/translations/fi.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1801,7 +1801,13 @@ const translations: Translations = {
deleteProgramForm: {
title: "Poista ohjelmalomake",
cannotRemove:
"Ohjelmalomaketta ei voi poistaa, koska sillä on tehty ohjelmatarjouksia.",
"Ohjelmalomaketta ei voi poistaa, jos sillä on tehty ohjelmatarjouksia tai sillä on jatkokyselyitä.",
languageVersionsWillBeRemoved: (languages: string) =>
`Myös ohjelmalomakkeen seuraavat kieliversiot poistetaan: ${languages}.`,
pendingInvitationsWillBeRemoved: (count: number) =>
count === 1
? "Myös 1 kutsu, jota ei ole vielä hyväksytty, poistetaan. Kutsusähköpostissa oleva linkki lakkaa toimimasta."
: `Myös ${count} kutsua, joita ei ole vielä hyväksytty, poistetaan. Kutsusähköposteissa olevat linkit lakkaavat toimimasta.`,
confirmation: (surveyTitle: string) => (
<>
Haluatko varmasti poistaa ohjelmalomakkeen{" "}
Expand Down Expand Up @@ -2861,7 +2867,14 @@ const translations: Translations = {
},
deleteSurvey: {
title: "Poista kysely",
cannotRemove: "Kyselyä, joka on jo saanut vastauksia, ei voi poistaa.",
cannotRemove:
"Kyselyä, joka on jo saanut vastauksia tai jolla on jatkokyselyitä, ei voi poistaa.",
languageVersionsWillBeRemoved: (languages: string) =>
`Myös kyselyn seuraavat kieliversiot poistetaan: ${languages}.`,
pendingInvitationsWillBeRemoved: (count: number) =>
count === 1
? "Myös 1 kutsu, jota ei ole vielä hyväksytty, poistetaan. Kutsusähköpostissa oleva linkki lakkaa toimimasta."
: `Myös ${count} kutsua, joita ei ole vielä hyväksytty, poistetaan. Kutsusähköposteissa olevat linkit lakkaavat toimimasta.`,
confirmation: (surveyTitle: string) => (
<>
Haluatko varmasti poistaa kyselyn <strong>{surveyTitle}</strong>?
Expand Down
17 changes: 15 additions & 2 deletions kompassi-v2-frontend/src/translations/sv.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1774,7 +1774,13 @@ const translations: Translations = {
deleteProgramForm: {
title: "Ta bort programformulär",
cannotRemove:
"Ett programformulär som har programerbjudanden kan inte tas bort.",
"Ett programformulär som har programerbjudanden eller uppföljningsenkäter kan inte tas bort.",
languageVersionsWillBeRemoved: (languages: string) =>
`Följande språkversioner av programformuläret kommer också att tas bort: ${languages}.`,
pendingInvitationsWillBeRemoved: (count: number) =>
count === 1
? "1 inbjudan som ännu inte har accepterats kommer också att tas bort. Länken i inbjudningsmejlet slutar fungera."
: `${count} inbjudningar som ännu inte har accepterats kommer också att tas bort. Länkarna i inbjudningsmejlen slutar fungera.`,
confirmation: (surveyTitle: string) => (
<>
Är du säker på att du vill ta bort programformuläret{" "}
Expand Down Expand Up @@ -2806,7 +2812,14 @@ const translations: Translations = {
},
deleteSurvey: {
title: "Radera enkät",
cannotRemove: "En enkät som redan har fått svar kan inte raderas.",
cannotRemove:
"En enkät som redan har fått svar eller har uppföljningsenkäter kan inte raderas.",
languageVersionsWillBeRemoved: (languages: string) =>
`Följande språkversioner av enkäten kommer också att raderas: ${languages}.`,
pendingInvitationsWillBeRemoved: (count: number) =>
count === 1
? "1 inbjudan som ännu inte har accepterats kommer också att raderas. Länken i inbjudningsmejlet slutar fungera."
: `${count} inbjudningar som ännu inte har accepterats kommer också att raderas. Länkarna i inbjudningsmejlen slutar fungera.`,
confirmation: (surveyTitle: string) => (
<>
Är du säker på att du vill ta bort enkäten{" "}
Expand Down
24 changes: 15 additions & 9 deletions kompassi/forms/graphql/mutations/delete_survey.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import graphene
from django.db import transaction
from django.http import HttpRequest

from ...models.survey import Survey
Expand All @@ -21,16 +22,21 @@ def mutate(
info,
input: DeleteSurveyInput,
):
survey = Survey.objects.get(
event__slug=input.event_slug,
slug=input.survey_slug,
)

request: HttpRequest = info.context
if not survey.can_be_deleted_by(request):
raise Exception("Cannot delete survey")

survey.workflow.revoke_all_access(request)
survey.delete()
with transaction.atomic():
Comment thread
jkjuopperi marked this conversation as resolved.
Resolved
survey = Survey.objects.select_for_update().get(
event__slug=input.event_slug,
slug=input.survey_slug,
)
# Lock the language versions so that no response can be created between the check and the delete
list(survey.languages.select_for_update())

if not survey.can_be_deleted_by(request):
raise Exception("Cannot delete survey")

survey.workflow.revoke_all_access(request)
# language versions (Forms) and pending invitations are deleted by cascade
survey.delete()

return DeleteSurvey(slug=input.survey_slug) # type: ignore
21 changes: 19 additions & 2 deletions kompassi/forms/graphql/survey_full.py
Original file line number Diff line number Diff line change
Expand Up @@ -248,8 +248,8 @@ def resolve_languages(parent: Survey, info):
@staticmethod
def resolve_can_remove(survey: Survey, info):
"""
Surveys that have language versions cannot be removed.
Having language versions is also a prerequisite for a survey to have responses.
Surveys that have responses or follow-up surveys cannot be removed.
Removing a survey also removes its language versions and pending invitations.
"""
request: HttpRequest = info.context
return survey.can_be_deleted_by(request)
Expand All @@ -259,6 +259,23 @@ def resolve_can_remove(survey: Survey, info):
description=normalize_whitespace(resolve_can_remove.__doc__ or ""),
)

@staticmethod
def resolve_count_pending_invitations(survey: Survey, info) -> int:
"""
Returns the number of invitations to this survey that have not been accepted yet.
Removing the survey also removes them. Returns 0 to those who cannot remove the survey.
"""
request: HttpRequest = info.context
if not survey.workflow.is_allowed(request, operation="delete"):
return 0

return survey.invitations.filter(used_at__isnull=True).count()

count_pending_invitations = graphene.NonNull(
graphene.Int,
description=normalize_whitespace(resolve_count_pending_invitations.__doc__ or ""),
)

@staticmethod
def resolve_responses_deletion_status(survey: Survey, info):
"""
Expand Down
10 changes: 9 additions & 1 deletion kompassi/forms/models/survey.py
Original file line number Diff line number Diff line change
Expand Up @@ -412,7 +412,15 @@ def title_dict(self):
return {form.language: form.title for form in self.languages.all()}

def can_be_deleted_by(self, request: HttpRequest):
return self.workflow.is_allowed(request, operation="delete") and not self.languages.exists()
"""
Deleting a survey also deletes its language versions, so a survey can be deleted
if none of its language versions have responses. Follow-up surveys protect their parent.
"""
return (
self.workflow.is_allowed(request, operation="delete")
and not self.all_responses.exists()
and not self.followups.exists()
)

def can_responses_be_deleted_by(self, request: HttpRequest) -> CanResponsesBeDeleted:
return self.workflow.responses_can_be_deleted_by(request)
Expand Down
Loading
Loading