Skip to content

Commit 6699bb7

Browse files
authored
Merge pull request #33 from ThreeDotsLabs/restore
Restore exercises on init + checkout command
2 parents 15f9fe0 + 48b2ff1 commit 6699bb7

File tree

15 files changed

+1519
-388
lines changed

15 files changed

+1519
-388
lines changed

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ require (
1111
github.com/google/uuid v1.6.0
1212
github.com/hexops/gotextdiff v1.0.3
1313
github.com/manifoldco/promptui v0.9.0
14+
github.com/mergestat/timediff v0.0.4
1415
github.com/pkg/errors v0.9.1
1516
github.com/sirupsen/logrus v1.8.1
1617
github.com/spf13/afero v1.6.0

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@ github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope
4444
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
4545
github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng=
4646
github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
47+
github.com/mergestat/timediff v0.0.4 h1:NZ3sqG/6K9flhTubdltmRx3RBfIiYv6LsGP+4FlXMM8=
48+
github.com/mergestat/timediff v0.0.4/go.mod h1:yvMUaRu2oetc+9IbPLYBJviz6sA7xz8OXMDfhBl7YSI=
4749
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
4850
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
4951
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=

internal/update.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,13 @@ import (
44
"context"
55
"encoding/json"
66
"fmt"
7-
"github.com/fatih/color"
87
"net/http"
98
"os"
109
"path"
1110
"strings"
1211
"time"
12+
13+
"github.com/fatih/color"
1314
)
1415

1516
type releaseResponse struct {
@@ -57,7 +58,7 @@ func CheckForUpdate(currentVersion string) {
5758
func printVersionNotice(currentVersion string, availableVersion string) {
5859
c := color.New(color.FgHiYellow)
5960
_, _ = c.Printf("A new version of the CLI is available: %s (current: %s)\n", availableVersion, currentVersion)
60-
_, _ = c.Printf("Some features may not work correctly. Please update ASAP!\n")
61+
_, _ = c.Printf("Some features may be missing or not work correctly. Please update soon!\n")
6162
_, _ = c.Printf("See instructions at: %v\n", repoURL)
6263
fmt.Println()
6364
}

tdl/main.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,13 @@ var app = &cli.App{
211211
return newHandlers(c).List(c.Context)
212212
},
213213
},
214+
{
215+
Name: "checkout",
216+
Usage: "checkout one of your past solutions for the current exercise",
217+
Action: func(c *cli.Context) error {
218+
return newHandlers(c).Checkout(c.Context)
219+
},
220+
},
214221
{
215222
Name: "clone",
216223
Usage: "clone solution files to current directory",

trainings/api/protobuf/server.proto

Lines changed: 50 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,15 @@ service Trainings {
88
rpc Init(InitRequest) returns (InitResponse) {};
99

1010
rpc GetTrainings(google.protobuf.Empty) returns (GetTrainingsResponse) {};
11-
rpc StartTraining(StartTrainingRequest) returns (google.protobuf.Empty) {};
11+
rpc StartTraining(StartTrainingRequest) returns (StartTrainingResponse) {};
1212

1313
rpc NextExercise(NextExerciseRequest) returns (NextExerciseResponse) {};
1414

1515
rpc VerifyExercise(VerifyExerciseRequest) returns (stream VerifyExerciseResponse) {};
1616

17+
rpc GetSolutions(GetSolutionsRequest) returns (GetSolutionsResponse) {};
1718
rpc GetSolutionFiles(GetSolutionFilesRequest) returns (GetSolutionFilesResponse) {};
19+
rpc GetAllSolutionFiles(GetAllSolutionFilesRequest) returns (GetAllSolutionFilesResponse) {};
1820

1921
rpc GetExercises(GetExercisesRequest) returns (GetExercisesResponse) {};
2022
rpc GetExercise(GetExerciseRequest) returns (NextExerciseResponse) {};
@@ -45,7 +47,7 @@ message StartTrainingRequest {
4547
}
4648

4749
message StartTrainingResponse {
48-
50+
bool previous_solutions_available = 1;
4951
}
5052

5153
message NextExerciseRequest {
@@ -86,6 +88,28 @@ message NextExerciseResponse {
8688
google.protobuf.Timestamp next_batch_date = 8;
8789
}
8890

91+
message ExerciseSolution {
92+
string exercise_id = 1;
93+
string dir = 2;
94+
repeated File files = 3;
95+
bool is_text_only = 4;
96+
bool is_optional = 5;
97+
98+
Module module = 6;
99+
Exercise exercise = 7;
100+
101+
message Module {
102+
string id = 1;
103+
string name = 2;
104+
}
105+
106+
message Exercise {
107+
string id = 1;
108+
Module module = 2;
109+
string name = 3;
110+
}
111+
}
112+
89113
message NextExercise {
90114
string dir = 1;
91115
repeated File files_to_create = 2;
@@ -142,6 +166,15 @@ message GetSolutionFilesResponse {
142166
repeated File files_to_create = 4;
143167
}
144168

169+
message GetAllSolutionFilesRequest {
170+
string training_name = 1;
171+
string token = 2;
172+
}
173+
174+
message GetAllSolutionFilesResponse {
175+
repeated ExerciseSolution solutions = 1;
176+
}
177+
145178
message GetExercisesRequest {
146179
string training_name = 1;
147180
string token = 2;
@@ -188,3 +221,18 @@ message CanSkipExerciseResponse {
188221
bool can_skip = 1;
189222
bool can_skip_all_optional = 2;
190223
}
224+
225+
message GetSolutionsRequest {
226+
string exercise_id = 1;
227+
string token = 2;
228+
}
229+
230+
message GetSolutionsResponse {
231+
message Solution {
232+
string verification_id = 1;
233+
bool successful = 2;
234+
google.protobuf.Timestamp executed_at = 3;
235+
}
236+
237+
repeated Solution solutions = 1;
238+
}

trainings/checkout.go

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
package trainings
2+
3+
import (
4+
"context"
5+
"fmt"
6+
7+
"github.com/ThreeDotsLabs/cli/trainings/config"
8+
"github.com/ThreeDotsLabs/cli/trainings/files"
9+
"github.com/ThreeDotsLabs/cli/trainings/genproto"
10+
"github.com/manifoldco/promptui"
11+
"github.com/mergestat/timediff"
12+
"github.com/pkg/errors"
13+
"github.com/sirupsen/logrus"
14+
)
15+
16+
func (h *Handlers) Checkout(ctx context.Context) error {
17+
trainingRoot, err := h.config.FindTrainingRoot()
18+
if errors.Is(err, config.TrainingRootNotFoundError) {
19+
h.printNotInATrainingDirectory()
20+
return nil
21+
}
22+
23+
trainingRootFs := newTrainingRootFs(trainingRoot)
24+
25+
resp, err := h.newGrpcClient().GetSolutions(ctx, &genproto.GetSolutionsRequest{
26+
ExerciseId: h.config.ExerciseConfig(trainingRootFs).ExerciseID,
27+
Token: h.config.GlobalConfig().Token,
28+
})
29+
if err != nil {
30+
return fmt.Errorf("failed to get solutions: %w", err)
31+
}
32+
33+
logrus.WithFields(logrus.Fields{
34+
"resp": resp,
35+
"err": err,
36+
}).Debug("Received solutions from server")
37+
38+
items := []string{"(cancel)"}
39+
for _, solution := range resp.Solutions {
40+
text := ""
41+
if solution.Successful {
42+
text += "✅"
43+
} else {
44+
text += "❌"
45+
}
46+
text += " "
47+
text += solution.VerificationId
48+
text += " "
49+
text += timediff.TimeDiff(solution.ExecutedAt.AsTime())
50+
51+
items = append(items, text)
52+
}
53+
54+
selectUI := promptui.Select{
55+
Label: "Select a solution to checkout",
56+
Items: items,
57+
Size: 10,
58+
Templates: &promptui.SelectTemplates{
59+
Label: "{{ . }}",
60+
Active: "{{ . | cyan }}",
61+
Inactive: "{{ . }}",
62+
},
63+
HideSelected: true,
64+
}
65+
66+
index, _, err := selectUI.Run()
67+
if err != nil {
68+
return err
69+
}
70+
71+
if index == 0 {
72+
fmt.Println("Cancelled")
73+
return nil
74+
}
75+
76+
getResp, err := h.newGrpcClient().GetSolutionFiles(ctx, &genproto.GetSolutionFilesRequest{
77+
ExecutionId: resp.Solutions[index-1].VerificationId,
78+
})
79+
if err != nil {
80+
return fmt.Errorf("failed to get solution files: %w", err)
81+
}
82+
83+
if err := h.writeExerciseFiles(files.NewFilesWithConfig(true, true), getSolutionFilesToExerciseSolution(getResp), trainingRootFs); err != nil {
84+
return err
85+
}
86+
87+
err = addModuleToWorkspace(trainingRoot, getResp.Dir)
88+
if err != nil {
89+
logrus.WithError(err).Warn("Failed to add module to workspace")
90+
}
91+
92+
return nil
93+
}

trainings/clone.go

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"path"
88

99
"github.com/ThreeDotsLabs/cli/trainings/config"
10+
"github.com/ThreeDotsLabs/cli/trainings/files"
1011
"github.com/ThreeDotsLabs/cli/trainings/genproto"
1112
"github.com/pkg/errors"
1213
"github.com/sirupsen/logrus"
@@ -33,7 +34,7 @@ func (h *Handlers) Clone(ctx context.Context, executionID string, directory stri
3334

3435
absoluteDirToClone = path.Join(absoluteDirToClone, directory)
3536

36-
if _, err := h.startTraining(ctx, resp.TrainingName, absoluteDirToClone); err != nil {
37+
if _, _, err := h.startTraining(ctx, resp.TrainingName, absoluteDirToClone); err != nil {
3738
return err
3839
}
3940

@@ -43,15 +44,7 @@ func (h *Handlers) Clone(ctx context.Context, executionID string, directory stri
4344
return errors.Wrap(err, "can't write training config")
4445
}
4546

46-
files := &genproto.NextExerciseResponse{
47-
TrainingStatus: genproto.NextExerciseResponse_IN_PROGRESS,
48-
Dir: resp.Dir,
49-
ExerciseId: resp.ExerciseId,
50-
FilesToCreate: resp.FilesToCreate,
51-
IsTextOnly: false,
52-
}
53-
54-
if err := h.writeExerciseFiles(files, trainingRootFs); err != nil {
47+
if err := h.writeExerciseFiles(files.NewFiles(), getSolutionFilesToExerciseSolution(resp), trainingRootFs); err != nil {
5548
return err
5649
}
5750

@@ -62,3 +55,13 @@ func (h *Handlers) Clone(ctx context.Context, executionID string, directory stri
6255

6356
return nil
6457
}
58+
59+
func getSolutionFilesToExerciseSolution(resp *genproto.GetSolutionFilesResponse) *genproto.ExerciseSolution {
60+
return &genproto.ExerciseSolution{
61+
ExerciseId: resp.ExerciseId,
62+
Dir: resp.Dir,
63+
Files: resp.FilesToCreate,
64+
IsTextOnly: false,
65+
IsOptional: false,
66+
}
67+
}

trainings/files/files.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,22 @@ import (
88
type Files struct {
99
stdin io.Reader
1010
stdout io.Writer
11+
12+
deleteUnusedFiles bool
13+
showFullDiff bool
1114
}
1215

1316
func NewFiles() Files {
1417
return NewFilesWithStdOuts(os.Stdin, os.Stdout)
1518
}
1619

20+
func NewFilesWithConfig(deleteUnusedFiles bool, showFullDiff bool) Files {
21+
f := NewFiles()
22+
f.deleteUnusedFiles = deleteUnusedFiles
23+
f.showFullDiff = showFullDiff
24+
return f
25+
}
26+
1727
func NewFilesWithStdOuts(stdin io.Reader, stdout io.Writer) Files {
1828
return Files{
1929
stdin: stdin,

0 commit comments

Comments
 (0)