Skip to content

Commit c73ca46

Browse files
committed
feat(course-setup): add CLI ping step controlled by feature flag
Introduce a new "Test CLI connection" setup step that replaces the previous "Push empty commit" step when the `canViewCLIPingFlow` feature flag is enabled. Update related UI text and instructions to reflect this new flow, including the addition of a reusable command component for running CLI connection tests. This change enables a smoother onboarding experience for users with access to experimental CLI ping functionality, controlled via a feature flag to allow gradual rollout without impacting existing users.
1 parent 6efff22 commit c73ca46

21 files changed

Lines changed: 277 additions & 82 deletions

app/components/course-page/course-stage-step/first-stage-your-task-card/index.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import CoursePageStateService from 'codecrafters-frontend/services/course-page-s
44
import RepositoryModel from 'codecrafters-frontend/models/repository';
55
import Store from '@ember-data/store';
66
import type CourseStageStep from 'codecrafters-frontend/utils/course-page-step-list/course-stage-step';
7+
import type FeatureFlagsService from 'codecrafters-frontend/services/feature-flags';
78
import type { StepDefinition } from 'codecrafters-frontend/components/step-list';
89
import { action } from '@ember/object';
910
import { service } from '@ember/service';
@@ -40,6 +41,14 @@ class UncommentCodeStep extends BaseStep implements StepDefinition {
4041
}
4142
}
4243

44+
class TestCodeStep extends BaseStep implements StepDefinition {
45+
id = 'submit-code';
46+
47+
get titleMarkdown() {
48+
return 'Run tests';
49+
}
50+
}
51+
4352
class SubmitCodeStep extends BaseStep implements StepDefinition {
4453
id = 'submit-code';
4554

@@ -51,6 +60,7 @@ class SubmitCodeStep extends BaseStep implements StepDefinition {
5160
export default class FirstStageYourTaskCard extends Component<Signature> {
5261
@service declare analyticsEventTracker: AnalyticsEventTrackerService;
5362
@service declare coursePageState: CoursePageStateService;
63+
@service declare featureFlags: FeatureFlagsService;
5464
@service declare store: Store;
5565

5666
get filename() {
@@ -70,7 +80,9 @@ export default class FirstStageYourTaskCard extends Component<Signature> {
7080
get steps() {
7181
return [
7282
new UncommentCodeStep(this.args.currentStep.repository, this.stepsAreComplete),
73-
new SubmitCodeStep(this.args.currentStep.repository, this.stepsAreComplete),
83+
this.featureFlags.canViewCLIPingFlow
84+
? new TestCodeStep(this.args.currentStep.repository, this.stepsAreComplete)
85+
: new SubmitCodeStep(this.args.currentStep.repository, this.stepsAreComplete),
7486
];
7587
}
7688

app/components/course-page/course-stage-step/first-stage-your-task-card/submit-code-step.hbs

Lines changed: 36 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,49 @@
1-
<div class="mb-3">
2-
<div class="prose prose-compact dark:prose-invert mb-3">
3-
First, run this command to commit your changes:
1+
{{#if this.featureFlags.canViewCLIPingFlow}}
2+
<div>
3+
<CopyableTerminalCommand @commands={{array "codecrafters test"}} class="mb-1.5" />
4+
5+
<div class="flex items-center gap-0.5 pl-0.5">
6+
{{svg-jar "question-mark-circle" class="w-3 h-3 fill-current text-blue-500"}}
7+
8+
{{! TODO: Change troubleshooting link }}
9+
<span class="text-blue-500 text-xs underline font-medium">
10+
<a href="#" {{on "click" (fn (mut this.isCommitModalOpen) true)}}>Troubleshoot</a>
11+
</span>
12+
</div>
413
</div>
14+
{{else}}
15+
<div class="mb-3">
16+
<div class="prose prose-compact dark:prose-invert mb-3">
17+
First, run this command to commit your changes:
18+
</div>
519

6-
<CopyableTerminalCommand @commands={{array 'git commit -am "[any message]"'}} class="mb-1.5" />
20+
<CopyableTerminalCommand @commands={{array 'git commit -am "[any message]"'}} class="mb-1.5" />
721

8-
<div class="flex items-center gap-0.5 pl-0.5">
9-
{{svg-jar "question-mark-circle" class="w-3 h-3 fill-current text-blue-500"}}
22+
<div class="flex items-center gap-0.5 pl-0.5">
23+
{{svg-jar "question-mark-circle" class="w-3 h-3 fill-current text-blue-500"}}
1024

11-
<span class="text-blue-500 text-xs underline font-medium">
12-
<a href="#" {{on "click" (fn (mut this.isCommitModalOpen) true)}}>Troubleshoot</a>
13-
</span>
25+
<span class="text-blue-500 text-xs underline font-medium">
26+
<a href="#" {{on "click" (fn (mut this.isCommitModalOpen) true)}}>Troubleshoot</a>
27+
</span>
28+
</div>
1429
</div>
15-
</div>
1630

17-
<div>
18-
<div class="prose prose-compact dark:prose-invert mb-3">
19-
Next, run this command to push your changes:
20-
</div>
31+
<div>
32+
<div class="prose prose-compact dark:prose-invert mb-3">
33+
Next, run this command to push your changes:
34+
</div>
2135

22-
<CopyableTerminalCommand @commands={{array "git push origin master"}} class="mb-1.5" />
36+
<CopyableTerminalCommand @commands={{array "git push origin master"}} class="mb-1.5" />
2337

24-
<div class="flex items-center gap-0.5 pl-0.5">
25-
{{svg-jar "question-mark-circle" class="w-3 h-3 fill-current text-blue-500"}}
38+
<div class="flex items-center gap-0.5 pl-0.5">
39+
{{svg-jar "question-mark-circle" class="w-3 h-3 fill-current text-blue-500"}}
2640

27-
<span class="text-blue-500 text-xs underline font-medium">
28-
<a href="#" {{on "click" (fn (mut this.isPushModalOpen) true)}}>Troubleshoot</a>
29-
</span>
41+
<span class="text-blue-500 text-xs underline font-medium">
42+
<a href="#" {{on "click" (fn (mut this.isPushModalOpen) true)}}>Troubleshoot</a>
43+
</span>
44+
</div>
3045
</div>
31-
</div>
46+
{{/if}}
3247

3348
{{!-- <div class="prose dark:prose-invert">
3449
<p class={{if @isComplete "line-through"}}>

app/components/course-page/course-stage-step/test-runner-card/run-tests-instructions.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import type Owner from '@ember/owner';
44
import { tracked } from '@glimmer/tracking';
55
import type { CopyableTerminalCommandVariant } from 'codecrafters-frontend/components/copyable-terminal-command-with-variants';
66
import type CourseStageStep from 'codecrafters-frontend/utils/course-page-step-list/course-stage-step';
7+
import { service } from '@ember/service';
8+
import type FeatureFlagsService from 'codecrafters-frontend/services/feature-flags';
79

810
interface Signature {
911
Element: HTMLDivElement;
@@ -15,6 +17,8 @@ interface Signature {
1517
}
1618

1719
export default class RunTestsInstructions extends Component<Signature> {
20+
@service declare featureFlags: FeatureFlagsService;
21+
1822
@tracked selectedCommandVariant: CopyableTerminalCommandVariant;
1923

2024
constructor(owner: Owner, args: Signature['Args']) {
@@ -46,6 +50,10 @@ export default class RunTestsInstructions extends Component<Signature> {
4650
}
4751

4852
get recommendedClientType() {
53+
if (this.featureFlags.canViewCLIPingFlow) {
54+
return 'cli';
55+
}
56+
4957
if (this.args.currentStep.courseStage.isFirst || this.args.currentStep.courseStage.isSecond) {
5058
return 'git';
5159
} else {

app/components/course-page/setup-step-complete-modal.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,9 @@ export default class SetupStepCompleteModalComponent extends Component<Signature
5252
async handleDidInsert() {
5353
console.log('handleDidInsert');
5454
// Pre-cache the workflow animation GIF to avoid flickering when it loads.
55+
fetch('https://codecrafters.io/images/overview/workflow-animation-3.gif', { mode: 'no-cors' });
56+
57+
// TODO: Remove this once can-view-cli-ping-flow feature flag is the default
5558
fetch('https://codecrafters.io/images/overview/workflow-animation-2.gif', { mode: 'no-cors' });
5659
}
5760

app/components/course-page/setup-step-complete-modal/workflow-tutorial-step2-screen.hbs

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,22 +5,40 @@
55

66
<div class="px-3 sm:px-6 py-6 bg-gray-50 dark:bg-gray-925 rounded-sm border border-gray-200 dark:border-gray-700 mb-6">
77
<div class="mb-4 font-bold text-lg text-gray-700 dark:text-gray-100 text-center text-pretty">
8-
`git push` to submit
8+
{{#if this.featureFlags.canViewCLIPingFlow}}
9+
Run `codecrafters test`
10+
{{else}}
11+
`git push` to submit
12+
{{/if}}
913
</div>
1014

1115
<div class="prose prose-sm dark:prose-invert mb-4 text-center text-pretty">
1216
<p>
13-
When you're ready to test, commit your changes and run `git push`.
17+
{{#if this.featureFlags.canViewCLIPingFlow}}
18+
Once you're ready to test your code, simply run `codecrafters test`.
19+
{{else}}
20+
When you're ready to test, commit your changes and run `git push`.
21+
{{/if}}
1422
</p>
1523
</div>
1624

17-
{{! Note: This image is pre-cached in SetupStepCompleteModal.handleDidInsert }}
18-
<div class="w-full h-48 mb-4 bg-[url(https://codecrafters.io/images/overview/workflow-animation-2.gif)] bg-cover bg-center rounded-sm">
19-
</div>
25+
{{#if this.featureFlags.canViewCLIPingFlow}}
26+
{{! Note: This image is pre-cached in SetupStepCompleteModal.handleDidInsert }}
27+
<div class="w-full h-48 mb-4 bg-[url(https://codecrafters.io/images/overview/workflow-animation-3.gif)] bg-cover bg-center rounded-sm">
28+
</div>
29+
{{else}}
30+
{{! Note: This image is pre-cached in SetupStepCompleteModal.handleDidInsert }}
31+
<div class="w-full h-48 mb-4 bg-[url(https://codecrafters.io/images/overview/workflow-animation-2.gif)] bg-cover bg-center rounded-sm">
32+
</div>
33+
{{/if}}
2034

2135
<div class="prose prose-sm dark:prose-invert text-center text-pretty">
2236
<p>
23-
Your code will be submitted to CodeCrafters and you'll see feedback in your browser.
37+
{{#if this.featureFlags.canViewCLIPingFlow}}
38+
You'll receive instant feedback in both your terminal and browser.
39+
{{else}}
40+
Your code will be submitted to CodeCrafters and you'll see feedback in your browser.
41+
{{/if}}
2442
</p>
2543
</div>
2644
</div>

app/components/course-page/setup-step-complete-modal/workflow-tutorial-step2-screen.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import Component from '@glimmer/component';
2+
import FeatureFlagsService from 'codecrafters-frontend/services/feature-flags';
23
import type SetupStep from 'codecrafters-frontend/utils/course-page-step-list/setup-step';
4+
import { service } from '@ember/service';
35

46
interface Signature {
57
Element: HTMLDivElement;
@@ -11,7 +13,9 @@ interface Signature {
1113
};
1214
}
1315

14-
export default class WorkflowTutorialStep2ScreenComponent extends Component<Signature> {}
16+
export default class WorkflowTutorialStep2ScreenComponent extends Component<Signature> {
17+
@service declare featureFlags: FeatureFlagsService;
18+
}
1519

1620
declare module '@glint/environment-ember-loose/registry' {
1721
export default interface Registry {

app/components/course-page/setup-step/repository-setup-card/clone-repository-step.hbs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<div>
1+
<div data-test-clone-repository-step>
22
{{! @glint-expect-error isNew is not typed? }}
33
{{#if @repository.isNew}}
44
<div class="prose dark:prose-invert">
@@ -10,7 +10,15 @@
1010
{{else}}
1111
<CopyableTerminalCommand
1212
@commands={{array (concat "git clone " @repository.cloneUrl " " @repository.cloneDirectory) (concat "cd " @repository.cloneDirectory)}}
13-
data-test-copyable-repository-clone-instructions
13+
class="mb-1.5"
1414
/>
15+
16+
<div class="flex items-center gap-0.5 pl-0.5" data-test-troubleshoot-link>
17+
{{svg-jar "question-mark-circle" class="w-3 h-3 fill-current text-blue-500"}}
18+
19+
<span class="text-blue-500 text-xs underline font-medium">
20+
<a href="#" {{on "click" @onTroubleshootingLinkClick}}>Troubleshoot</a>
21+
</span>
22+
</div>
1523
{{/if}}
1624
</div>

app/components/course-page/setup-step/repository-setup-card/clone-repository-step.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ export interface Signature {
66

77
Args: {
88
repository: RepositoryModel;
9+
onTroubleshootingLinkClick: () => void;
910
};
1011
}
1112

app/components/course-page/setup-step/repository-setup-card/index.hbs

Lines changed: 26 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -16,16 +16,32 @@
1616

1717
<StepList @steps={{this.steps}} data-test-repository-setup-step-list as |context|>
1818
{{#if (eq context.step.id "clone-repository")}}
19-
<CoursePage::SetupStep::RepositorySetupCard::CloneRepositoryStep @repository={{@repository}} />
19+
<CoursePage::SetupStep::RepositorySetupCard::CloneRepositoryStep
20+
@repository={{@repository}}
21+
@onTroubleshootingLinkClick={{fn (mut this.isTroubleshootingModalOpen) true}}
22+
/>
2023
{{else if (eq context.step.id "push-empty-commit")}}
21-
<CoursePage::SetupStep::RepositorySetupCard::PushEmptyCommitStep @repository={{@repository}} />
24+
<CoursePage::SetupStep::RepositorySetupCard::PushEmptyCommitStep
25+
@repository={{@repository}}
26+
@onTroubleshootingLinkClick={{fn (mut this.isTroubleshootingModalOpen) true}}
27+
/>
28+
{{else if (eq context.step.id "test-cli-connection")}}
29+
<CoursePage::SetupStep::RepositorySetupCard::TestCliConnectionStep
30+
@repository={{@repository}}
31+
@onTroubleshootingLinkClick={{fn (mut this.isTroubleshootingModalOpen) true}}
32+
/>
2233
{{/if}}
2334
</StepList>
2435

2536
{{#if this.isComplete}}
2637
<div class="mt-4 prose dark:prose-invert">
2738
<span class="text-xl mr-1">🎉</span>
28-
Git push received! The first stage is now activated.
39+
40+
{{#if this.featureFlags.canViewCLIPingFlow}}
41+
The first stage is now activated.
42+
{{else}}
43+
Git push received! The first stage is now activated.
44+
{{/if}}
2945
</div>
3046

3147
<PrimaryLinkButton
@@ -41,28 +57,12 @@
4157
<div class="px-4 py-3 bg-gray-50 dark:bg-gray-900 inline-flex rounded-sm border border-gray-200 dark:border-white/10 shadow-xs mt-4">
4258
<CoursePage::StepProgressIndicator @step={{this.coursePageState.currentStep}} @size="large" />
4359
</div>
44-
45-
<div class="mt-6 prose prose-compact prose-sm dark:prose-invert">
46-
<p>Need help?</p>
47-
<ul>
48-
<li>
49-
<a
50-
data-test-run-git-commands-link
51-
href="https://docs.codecrafters.io/challenges/how-challenges-work#how-to-run-git-commands"
52-
target="_blank"
53-
rel="noopener noreferrer"
54-
>Not sure how to run git commands</a>
55-
</li>
56-
<li>
57-
<a
58-
data-test-git-clone-issues-link
59-
href="https://docs.codecrafters.io/troubleshooting/fix-clone-errors"
60-
target="_blank"
61-
rel="noopener noreferrer"
62-
>Running into issues with the git clone step</a>
63-
</li>
64-
</ul>
65-
</div>
6660
{{/if}}
6761
</:content>
68-
</CoursePage::InstructionsCard>
62+
</CoursePage::InstructionsCard>
63+
64+
{{#if this.isTroubleshootingModalOpen}}
65+
<ModalBackdrop>
66+
<CoursePage::SetupStep::RepositorySetupCard::TroubleshootingModal @onClose={{this.handleTroubleshootingModalClose}} />
67+
</ModalBackdrop>
68+
{{/if}}

app/components/course-page/setup-step/repository-setup-card/index.ts

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
import Component from '@glimmer/component';
2+
import { action } from '@ember/object';
23
import { service } from '@ember/service';
34
import type RepositoryModel from 'codecrafters-frontend/models/repository';
45
import type CoursePageStateService from 'codecrafters-frontend/services/course-page-state';
56
import type { StepDefinition } from 'codecrafters-frontend/components/step-list';
7+
import type FeatureFlagsService from 'codecrafters-frontend/services/feature-flags';
8+
import { tracked } from '@glimmer/tracking';
69

710
export interface Signature {
811
Element: HTMLDivElement;
@@ -30,6 +33,14 @@ class CloneRepositoryStepDefinition extends BaseStep implements StepDefinition {
3033
}
3134
}
3235

36+
class TestCliConnectionStepDefinition extends BaseStep implements StepDefinition {
37+
id = 'test-cli-connection';
38+
39+
get titleMarkdown() {
40+
return 'Test CLI connection';
41+
}
42+
}
43+
3344
class PushEmptyCommitStepDefinition extends BaseStep implements StepDefinition {
3445
id = 'push-empty-commit';
3546

@@ -40,6 +51,9 @@ class PushEmptyCommitStepDefinition extends BaseStep implements StepDefinition {
4051

4152
export default class RepositorySetupCard extends Component<Signature> {
4253
@service declare coursePageState: CoursePageStateService;
54+
@service declare featureFlags: FeatureFlagsService;
55+
56+
@tracked isTroubleshootingModalOpen = false;
4357

4458
get isComplete() {
4559
return this.coursePageState.currentStep.status === 'complete';
@@ -48,9 +62,16 @@ export default class RepositorySetupCard extends Component<Signature> {
4862
get steps() {
4963
return [
5064
new CloneRepositoryStepDefinition(this.args.repository, this.isComplete),
51-
new PushEmptyCommitStepDefinition(this.args.repository, this.isComplete),
65+
this.featureFlags.canViewCLIPingFlow
66+
? new TestCliConnectionStepDefinition(this.args.repository, this.isComplete)
67+
: new PushEmptyCommitStepDefinition(this.args.repository, this.isComplete),
5268
];
5369
}
70+
71+
@action
72+
handleTroubleshootingModalClose() {
73+
this.isTroubleshootingModalOpen = false;
74+
}
5475
}
5576

5677
declare module '@glint/environment-ember-loose/registry' {

0 commit comments

Comments
 (0)