Skip to content

Commit

Permalink
Allow deleting of iterations (#1343)
Browse files Browse the repository at this point in the history
* Implement deleted iteration functionality

* Fix test

* Allow user to delete iterations

* Add API endpoint

* Add modal styling

* Fix tests

* Fix JS Test

Co-authored-by: Karlo Soriano <karlo@thalamus.ai>
  • Loading branch information
iHiD and kntsoriano committed Aug 2, 2021
1 parent b636054 commit fd5d01e
Show file tree
Hide file tree
Showing 53 changed files with 768 additions and 91 deletions.
17 changes: 17 additions & 0 deletions app/commands/iteration/destroy.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
class Iteration
class Destroy
include Mandate

initialize_with :iteration

def call
iteration.update!(deleted_at: Time.current)
solution.update!(published_iteration_id: nil) if solution.published_iteration_id == iteration.id
end

private
def solution
iteration.solution
end
end
end
4 changes: 4 additions & 0 deletions app/controllers/api/base_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,10 @@ def render_solution_not_found
render_404(:solution_not_found)
end

def render_iteration_not_found
render_404(:iteration_not_found)
end

def render_submission_not_found
render_404(:submission_not_found)
end
Expand Down
14 changes: 14 additions & 0 deletions app/controllers/api/iterations_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,20 @@ def create
}, status: :created
end

def destroy
begin
iteration = @solution.iterations.find_by!(uuid: params[:uuid])
rescue ActiveRecord::RecordNotFound
return render_iteration_not_found
end

Iteration::Destroy.(iteration)

render json: {
iteration: SerializeIteration.(iteration)
}
end

private
def use_solution
begin
Expand Down
4 changes: 4 additions & 0 deletions app/css/components/mentor/discussion.css
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,11 @@
}
}
}
& .deleted {
@apply py-32 text-16 text-textColor6 text-center;
}
& .c-iterations-footer {
@apply mt-auto;
& .settings-button {
@apply ml-auto;

Expand Down
4 changes: 2 additions & 2 deletions app/css/components/solution-iterations.css
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@
@apply flex-grow-0;
@apply flex items-center;

& .c-publish-settings {
@apply ml-auto;
& .publish-settings-button {
@apply ml-auto px-0 border-borderColor6;
& .c-icon {
filter: var(--textColor6-filter);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
@import "../styles";

.m-finish-mentor-discussion.c-modal {
.m-generic-confirmation.c-modal:not(.--cover) {
& .--modal-content {
max-width: 490px;
@apply p-24;
Expand All @@ -9,8 +9,11 @@
@apply text-h5 mb-8;
}
& p {
@apply text-15 leading-code; /* leading-160 */
@apply text-15 leading-160;
@apply mb-16;
& strong {
@apply font-medium text-textColor2;
}
}

& .buttons {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
@import "../styles";

.m-destructive {
.m-generic-destructive {
& form {
@apply flex flex-col;
}
Expand Down
2 changes: 1 addition & 1 deletion app/css/pages/exercise-show.css
Original file line number Diff line number Diff line change
Expand Up @@ -466,7 +466,7 @@
& h3 {
@apply text-h4 mb-0;
}
& button.c-publish-settings {
& button.publish-settings-button {
@apply ml-auto;
@apply px-0 border-borderColor6;
& .c-icon {
Expand Down
9 changes: 9 additions & 0 deletions app/css/pages/iterations-index.css
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,10 @@
}
}
}
& .deleted {
@apply p-32;
@apply text-16 text-textColor6 text-center;
}
& .content {
@apply flex-grow;
@apply flex items-stretch;
Expand All @@ -90,6 +94,11 @@
& .c-tab {
line-height: 38px;
}

& .options-button {
@apply py-6 px-12 ml-auto;
@apply shadow-smZ1 rounded-8;
}
}
& .panels {
@apply overflow-auto;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
module ReactComponents
module Student
class IterationPage < ReactComponent
class IterationsList < ReactComponent
initialize_with :solution

def to_s
super(
"student-iteration-page",
"student-iterations-list",
{
solution_uuid: solution.uuid,
request: request,
Expand Down
5 changes: 4 additions & 1 deletion app/javascript/components/common/ProcessingStatusSummary.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,10 @@ export function ProcessingStatusSummary({
}: {
iterationStatus: IterationStatus
}): JSX.Element {
if (iterationStatus == IterationStatus.UNTESTED) {
if (
iterationStatus == IterationStatus.DELETED ||
iterationStatus == IterationStatus.UNTESTED
) {
return <></>
}
const status = transformStatus(iterationStatus)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,11 @@ export const LeaveTrackModal = ({
const [tab, setTab] = useState<TabIndex>('leave')

return (
<Modal className="m-leave-track m-destructive" onClose={onClose} {...props}>
<Modal
className="m-leave-track m-generic-destructive"
onClose={onClose}
{...props}
>
<div className="info">
<h2>You’re about to leave the {track.title} track.</h2>
<p className="large">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,11 @@ export const ResetTrackModal = ({
const { attempt, setAttempt, isAttemptPass } = useConfirmation(confirmation)

return (
<Modal className="m-reset-track m-destructive" onClose={onClose} {...props}>
<Modal
className="m-reset-track m-generic-destructive"
onClose={onClose}
{...props}
>
<form>
<div className="info">
<h2>You’re about to reset all your {track.title} progress.</h2>
Expand Down
38 changes: 22 additions & 16 deletions app/javascript/components/mentoring/session/IterationView.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React from 'react'
import { Iteration } from '../../types'
import { Iteration, IterationStatus } from '../../types'
import { IterationsList } from './IterationsList'
import { FilePanel } from './FilePanel'
import { IterationHeader } from './IterationHeader'
Expand Down Expand Up @@ -32,6 +32,7 @@ export const IterationView = ({
settings: Settings
setSettings: (settings: Settings) => void
}): JSX.Element => {
/* TODO: Don't do this if currentIteration.links.files is null */
const { resolvedData, error, status, isFetching } = usePaginatedRequestQuery<{
files: File[]
}>(currentIteration.links.files, {
Expand All @@ -48,21 +49,26 @@ export const IterationView = ({
}
isOutOfDate={isOutOfDate}
/>
<ResultsZone isFetching={isFetching}>
<FetchingBoundary
error={error}
status={status}
defaultError={DEFAULT_ERROR}
>
{resolvedData ? (
<FilePanel
files={resolvedData.files}
language={language}
indentSize={indentSize}
/>
) : null}
</FetchingBoundary>
</ResultsZone>

{currentIteration.status == IterationStatus.DELETED ? (
<div className="deleted">This iteration has been deleted</div>
) : (
<ResultsZone isFetching={isFetching}>
<FetchingBoundary
error={error}
status={status}
defaultError={DEFAULT_ERROR}
>
{resolvedData ? (
<FilePanel
files={resolvedData.files}
language={language}
indentSize={indentSize}
/>
) : null}
</FetchingBoundary>
</ResultsZone>
)}
<footer className="c-iterations-footer">
{iterations.length > 1 ? (
<IterationsList
Expand Down
2 changes: 1 addition & 1 deletion app/javascript/components/modals/DeleteAccountModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ export const DeleteAccountModal = ({
const { attempt, setAttempt, isAttemptPass } = useConfirmation(handle)

return (
<Modal {...props} className="m-delete-account m-destructive">
<Modal {...props} className="m-delete-account m-generic-destructive">
<form onSubmit={handleSubmit}>
<div className="info">
<h2>You&apos;re about to delete your Exercism account</h2>
Expand Down
1 change: 1 addition & 0 deletions app/javascript/components/modals/Modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export const Modal = ({

return (
<ReactModal
ariaHideApp={process.env.NODE_ENV !== 'test'}
isOpen={open}
onRequestClose={onClose}
className={'--modal-content'}
Expand Down
2 changes: 1 addition & 1 deletion app/javascript/components/modals/ResetAccountModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ export const ResetAccountModal = ({
const { attempt, setAttempt, isAttemptPass } = useConfirmation(handle)

return (
<Modal {...props} className="m-reset-account m-destructive">
<Modal {...props} className="m-reset-account m-generic-destructive">
<form onSubmit={handleSubmit}>
<div className="info">
<h2>You&apos;re about to reset your Exercism account</h2>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export const FinishMentorDiscussionModal = ({
<Modal
open={open}
onClose={onCancel}
className="m-finish-mentor-discussion"
className="m-generic-confirmation"
{...props}
>
<h3>Are you sure you want to end this discussion?</h3>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import React, { useEffect, useState } from 'react'
import { Loading } from '../common'
import { Iteration } from '../types'
import { IterationReport } from './iteration-page/IterationReport'
import { EmptyIterations } from './iteration-page/EmptyIterations'
import { IterationReport } from './iterations-list/IterationReport'
import { EmptyIterations } from './iterations-list/EmptyIterations'
import { usePaginatedRequestQuery } from '../../hooks/request-query'
import { SolutionChannel } from '../../channels/solutionChannel'
import { queryCache } from 'react-query'
Expand All @@ -26,7 +26,7 @@ export type Links = {
solvingExercisesLocally: string
}

export type IterationPageRequest = {
export type IterationsListRequest = {
endpoint: string
options: {
initialData: {
Expand All @@ -35,15 +35,15 @@ export type IterationPageRequest = {
}
}

export const IterationPage = ({
export const IterationsList = ({
solutionUuid,
request,
exercise,
track,
links,
}: {
solutionUuid: string
request: IterationPageRequest
request: IterationsListRequest
exercise: Exercise
track: Track
links: Links
Expand All @@ -54,6 +54,25 @@ export const IterationPage = ({
iterations: readonly Iteration[]
}>(CACHE_KEY, request)

const handleDelete = (deletedIteration: Iteration) => {
queryCache.setQueryData<{ iterations: readonly Iteration[] }>(
CACHE_KEY,
(result) => {
if (!result) {
return { iterations: [] }
}

return {
...result,
iterations: result.iterations.map((i) =>
i.uuid === deletedIteration.uuid ? deletedIteration : i
),
}
}
),
[]
}

useEffect(() => {
const solutionChannel = new SolutionChannel(
{ uuid: solutionUuid },
Expand Down Expand Up @@ -126,6 +145,7 @@ export const IterationPage = ({
onCompressed={() => {
setIsOpen(isOpen.map((o, i) => (index === i ? false : o)))
}}
onDelete={handleDelete}
/>
)
})}
Expand Down
1 change: 1 addition & 0 deletions app/javascript/components/student/Nudge.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ export const Nudge = ({

useEffect(() => {
switch (iterationStatus) {
case IterationStatus.DELETED:
case IterationStatus.UNTESTED:
case IterationStatus.TESTING:
case IterationStatus.ANALYZING:
Expand Down
1 change: 1 addition & 0 deletions app/javascript/components/student/SolutionSummary.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ export const SolutionSummary = ({
}

switch (latestIteration.status) {
case IterationStatus.DELETED:
case IterationStatus.UNTESTED:
case IterationStatus.TESTING:
case IterationStatus.ANALYZING:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React from 'react'
import { GraphicalIcon } from '../../common'
import { Iteration, IterationStatus } from '../../types'
import { Exercise, Track, Links } from '../IterationPage'
import { Exercise, Track, Links } from '../IterationsList'
import { RepresenterFeedback } from './RepresenterFeedback'
import { AnalyzerFeedback } from './AnalyzerFeedback'

Expand All @@ -17,6 +17,7 @@ export const AnalysisInformation = ({
links: Links
}): JSX.Element | null => {
switch (iteration.status) {
case IterationStatus.DELETED:
case IterationStatus.TESTING:
case IterationStatus.ANALYZING:
return (
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React from 'react'
import { Icon, TrackIcon } from '../../common'
import { AnalyzerFeedback as Props, AnalyzerFeedbackComment } from '../../types'
import { Track } from '../IterationPage'
import { Track } from '../IterationsList'

export const AnalyzerFeedback = ({
summary,
Expand Down
Loading

0 comments on commit fd5d01e

Please sign in to comment.