@testgorilla/tgo-coding-test 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (163) hide show
  1. package/.eslintrc.json +45 -0
  2. package/README.md +257 -0
  3. package/jest.config.ts +21 -0
  4. package/ng-package.json +14 -0
  5. package/package.json +33 -0
  6. package/project.json +36 -0
  7. package/src/assets/i18n/en.json +124 -0
  8. package/src/index.ts +30 -0
  9. package/src/lib/components/.gitkeep +0 -0
  10. package/src/lib/components/code-editor/code-editor.component.html +10 -0
  11. package/src/lib/components/code-editor/code-editor.component.scss +21 -0
  12. package/src/lib/components/code-editor/code-editor.component.spec.ts +136 -0
  13. package/src/lib/components/code-editor/code-editor.component.ts +369 -0
  14. package/src/lib/components/code-editor/code-editor.mocks.ts +28 -0
  15. package/src/lib/components/code-editor/code-editor.service.spec.ts +160 -0
  16. package/src/lib/components/code-editor/code-editor.service.ts +94 -0
  17. package/src/lib/components/code-editor/helpers/c-helper.spec.ts +39 -0
  18. package/src/lib/components/code-editor/helpers/c-helper.ts +51 -0
  19. package/src/lib/components/code-editor/helpers/code-editor-helper.base.spec.ts +30 -0
  20. package/src/lib/components/code-editor/helpers/code-editor-helper.base.ts +16 -0
  21. package/src/lib/components/code-editor/helpers/code-editor-helper.mocks.ts +24 -0
  22. package/src/lib/components/code-editor/helpers/code-editor-helper.model.ts +67 -0
  23. package/src/lib/components/code-editor/helpers/cpp-helper.spec.ts +40 -0
  24. package/src/lib/components/code-editor/helpers/cpp-helper.ts +52 -0
  25. package/src/lib/components/code-editor/helpers/csharp-helper.spec.ts +42 -0
  26. package/src/lib/components/code-editor/helpers/csharp-helper.ts +55 -0
  27. package/src/lib/components/code-editor/helpers/go-helper.spec.ts +41 -0
  28. package/src/lib/components/code-editor/helpers/go-helper.ts +54 -0
  29. package/src/lib/components/code-editor/helpers/index.ts +15 -0
  30. package/src/lib/components/code-editor/helpers/java-helper.spec.ts +41 -0
  31. package/src/lib/components/code-editor/helpers/java-helper.ts +54 -0
  32. package/src/lib/components/code-editor/helpers/javascript-helper.spec.ts +39 -0
  33. package/src/lib/components/code-editor/helpers/javascript-helper.ts +32 -0
  34. package/src/lib/components/code-editor/helpers/kotlin-helper.spec.ts +41 -0
  35. package/src/lib/components/code-editor/helpers/kotlin-helper.ts +54 -0
  36. package/src/lib/components/code-editor/helpers/php-helper.spec.ts +39 -0
  37. package/src/lib/components/code-editor/helpers/php-helper.ts +32 -0
  38. package/src/lib/components/code-editor/helpers/python-helper.spec.ts +39 -0
  39. package/src/lib/components/code-editor/helpers/python-helper.ts +32 -0
  40. package/src/lib/components/code-editor/helpers/r-helper.spec.ts +39 -0
  41. package/src/lib/components/code-editor/helpers/r-helper.ts +32 -0
  42. package/src/lib/components/code-editor/helpers/ruby-helper.spec.ts +39 -0
  43. package/src/lib/components/code-editor/helpers/ruby-helper.ts +32 -0
  44. package/src/lib/components/code-editor/helpers/scala-helper.spec.ts +41 -0
  45. package/src/lib/components/code-editor/helpers/scala-helper.ts +53 -0
  46. package/src/lib/components/code-editor/helpers/sql-helper.spec.ts +87 -0
  47. package/src/lib/components/code-editor/helpers/sql-helper.ts +44 -0
  48. package/src/lib/components/code-editor/helpers/swift-helper.spec.ts +40 -0
  49. package/src/lib/components/code-editor/helpers/swift-helper.ts +51 -0
  50. package/src/lib/components/code-editor/helpers/typescript-helper.spec.ts +40 -0
  51. package/src/lib/components/code-editor/helpers/typescript-helper.ts +52 -0
  52. package/src/lib/components/code-editor/models/code-editor.model.ts +9 -0
  53. package/src/lib/components/code-editor/models/coding-snapshot.model.ts +4 -0
  54. package/src/lib/components/coding-question/coding-question.component.html +78 -0
  55. package/src/lib/components/coding-question/coding-question.component.scss +76 -0
  56. package/src/lib/components/coding-question/coding-question.component.spec.ts +85 -0
  57. package/src/lib/components/coding-question/coding-question.component.ts +102 -0
  58. package/src/lib/components/coding-section/coding-section.component.html +82 -0
  59. package/src/lib/components/coding-section/coding-section.component.scss +64 -0
  60. package/src/lib/components/coding-section/coding-section.component.spec.ts +257 -0
  61. package/src/lib/components/coding-section/coding-section.component.ts +187 -0
  62. package/src/lib/components/coding-test.module.ts +124 -0
  63. package/src/lib/components/common/truncated-text/truncated-text.component.html +6 -0
  64. package/src/lib/components/common/truncated-text/truncated-text.component.scss +18 -0
  65. package/src/lib/components/common/truncated-text/truncated-text.component.spec.ts +84 -0
  66. package/src/lib/components/common/truncated-text/truncated-text.component.ts +37 -0
  67. package/src/lib/components/configurations/configurations.component.html +57 -0
  68. package/src/lib/components/configurations/configurations.component.scss +42 -0
  69. package/src/lib/components/configurations/configurations.component.spec.ts +186 -0
  70. package/src/lib/components/configurations/configurations.component.ts +98 -0
  71. package/src/lib/components/instructions/instructions.component.html +41 -0
  72. package/src/lib/components/instructions/instructions.component.scss +167 -0
  73. package/src/lib/components/instructions/instructions.component.spec.ts +106 -0
  74. package/src/lib/components/instructions/instructions.component.ts +138 -0
  75. package/src/lib/components/panel/panel.component.html +19 -0
  76. package/src/lib/components/panel/panel.component.scss +41 -0
  77. package/src/lib/components/panel/panel.component.spec.ts +40 -0
  78. package/src/lib/components/panel/panel.component.ts +34 -0
  79. package/src/lib/components/runnable-editor/runnable-editor.component.html +75 -0
  80. package/src/lib/components/runnable-editor/runnable-editor.component.scss +55 -0
  81. package/src/lib/components/runnable-editor/runnable-editor.component.spec.ts +124 -0
  82. package/src/lib/components/runnable-editor/runnable-editor.component.ts +155 -0
  83. package/src/lib/components/tests/test-cases/test-cases.component.html +135 -0
  84. package/src/lib/components/tests/test-cases/test-cases.component.scss +220 -0
  85. package/src/lib/components/tests/test-cases/test-cases.component.spec.ts +401 -0
  86. package/src/lib/components/tests/test-cases/test-cases.component.ts +205 -0
  87. package/src/lib/components/tests/test-cases-content/test-cases-content.component.html +94 -0
  88. package/src/lib/components/tests/test-cases-content/test-cases-content.component.scss +103 -0
  89. package/src/lib/components/tests/test-cases-content/test-cases-content.component.spec.ts +122 -0
  90. package/src/lib/components/tests/test-cases-content/test-cases-content.component.ts +102 -0
  91. package/src/lib/components/tests/test-cases-status/test-cases-status.component.html +16 -0
  92. package/src/lib/components/tests/test-cases-status/test-cases-status.component.scss +49 -0
  93. package/src/lib/components/tests/test-cases-status/test-cases-status.component.spec.ts +22 -0
  94. package/src/lib/components/tests/test-cases-status/test-cases-status.component.ts +18 -0
  95. package/src/lib/components/tests/test-results.component.html +119 -0
  96. package/src/lib/components/tests/test-results.component.scss +189 -0
  97. package/src/lib/components/tests/test-results.component.spec.ts +140 -0
  98. package/src/lib/components/tests/test-results.component.ts +98 -0
  99. package/src/lib/components/tgo-coding-test/tgo-coding-test.component.html +96 -0
  100. package/src/lib/components/tgo-coding-test/tgo-coding-test.component.scss +6 -0
  101. package/src/lib/components/tgo-coding-test/tgo-coding-test.component.spec.ts +599 -0
  102. package/src/lib/components/tgo-coding-test/tgo-coding-test.component.ts +279 -0
  103. package/src/lib/components/tgo-coding-test-candidate-view/tgo-coding-test-candidate-view.component.html +36 -0
  104. package/src/lib/components/tgo-coding-test-candidate-view/tgo-coding-test-candidate-view.component.scss +183 -0
  105. package/src/lib/components/tgo-coding-test-candidate-view/tgo-coding-test-candidate-view.component.spec.ts +883 -0
  106. package/src/lib/components/tgo-coding-test-candidate-view/tgo-coding-test-candidate-view.component.ts +575 -0
  107. package/src/lib/config/index.ts +3 -0
  108. package/src/lib/config/tgo-coding-test.config.ts +26 -0
  109. package/src/lib/config/tgo-coding-test.provider.ts +38 -0
  110. package/src/lib/config/tgo-coding-test.token.ts +21 -0
  111. package/src/lib/models/.gitkeep +0 -0
  112. package/src/lib/models/auto-saved-data.ts +51 -0
  113. package/src/lib/models/code-event.ts +17 -0
  114. package/src/lib/models/coderunner-execution-results.ts +58 -0
  115. package/src/lib/models/coding-lib.mocks.ts +246 -0
  116. package/src/lib/models/configs.ts +18 -0
  117. package/src/lib/models/language-change-action.ts +4 -0
  118. package/src/lib/models/lat-languages.ts +12 -0
  119. package/src/lib/models/mixpanel-events.ts +3 -0
  120. package/src/lib/models/mode.ts +5 -0
  121. package/src/lib/models/paste-data.ts +4 -0
  122. package/src/lib/models/programming-language.ts +9 -0
  123. package/src/lib/models/test-cases.ts +74 -0
  124. package/src/lib/models/theme.ts +5 -0
  125. package/src/lib/models/translations.ts +1 -0
  126. package/src/lib/models/view-mode.ts +6 -0
  127. package/src/lib/pipes/memoize-func.pipe.ts +34 -0
  128. package/src/lib/services/.gitkeep +0 -0
  129. package/src/lib/services/candidate-coding-test-services/candidature-api.service.spec.ts +40 -0
  130. package/src/lib/services/candidate-coding-test-services/candidature-api.service.ts +15 -0
  131. package/src/lib/services/candidate-coding-test-services/coderunner-api.service.spec.ts +134 -0
  132. package/src/lib/services/candidate-coding-test-services/coderunner-api.service.ts +105 -0
  133. package/src/lib/services/candidate-coding-test-services/coding-test-tour.service.spec.ts +161 -0
  134. package/src/lib/services/candidate-coding-test-services/coding-test-tour.service.ts +100 -0
  135. package/src/lib/services/candidate-coding-test-services/coding-test.service.spec.ts +1524 -0
  136. package/src/lib/services/candidate-coding-test-services/coding-test.service.ts +843 -0
  137. package/src/lib/services/candidate-coding-test-services/index.ts +4 -0
  138. package/src/lib/services/coding-test-config.service.ts +48 -0
  139. package/src/lib/services/configurations.service.mocks.ts +77 -0
  140. package/src/lib/services/configurations.service.spec.ts +79 -0
  141. package/src/lib/services/configurations.service.ts +111 -0
  142. package/src/lib/services/index.ts +0 -0
  143. package/src/lib/services/lib-coding-test.service.spec.ts +265 -0
  144. package/src/lib/services/lib-coding-test.service.ts +157 -0
  145. package/src/lib/services/local-storage.service.mocks.ts +22 -0
  146. package/src/lib/services/storage.service.spec.ts +1120 -0
  147. package/src/lib/services/storage.service.ts +729 -0
  148. package/src/lib/services/test-cases.service.spec.ts +53 -0
  149. package/src/lib/services/test-cases.service.ts +29 -0
  150. package/src/lib/services/theme.service.spec.ts +76 -0
  151. package/src/lib/services/theme.service.ts +34 -0
  152. package/src/lib/styles/mixins.scss +86 -0
  153. package/src/lib/styles/styles.scss +112 -0
  154. package/src/lib/styles/variables.scss +105 -0
  155. package/src/lib/utils/.gitkeep +0 -0
  156. package/src/lib/utils/additional-languages/erlang.ts +115 -0
  157. package/src/lib/utils/resize-element.ts +15 -0
  158. package/src/lib/utils/time-to-ms.util.ts +10 -0
  159. package/src/test-setup.ts +1 -0
  160. package/tsconfig.json +16 -0
  161. package/tsconfig.lib.json +12 -0
  162. package/tsconfig.lib.prod.json +9 -0
  163. package/tsconfig.spec.json +13 -0
@@ -0,0 +1,843 @@
1
+ import { Injectable, signal } from '@angular/core';
2
+ import { translate } from '@ngneat/transloco';
3
+ import {
4
+ ApplicationTheme,
5
+ ConfirmDialogComponent,
6
+ DialogService,
7
+ SnackbarService,
8
+ } from '@testgorilla/tgo-ui';
9
+ import {
10
+ MixpanelService,
11
+ TestResultRead,
12
+ } from '@testgorilla/tgo-test-shared';
13
+ import {
14
+ CustomTestCase,
15
+ ExampleTestCase,
16
+ TestCaseDeletionAction,
17
+ TestCaseDeletionData,
18
+ } from '../../models/test-cases';
19
+ import { LibCodingTestService, ResetCodeAction } from '../lib-coding-test.service';
20
+ import { CodeEditorLanguages } from '../../components/code-editor/helpers/code-editor-helper.model';
21
+ import {
22
+ BehaviorSubject,
23
+ Observable,
24
+ Subject,
25
+ filter,
26
+ of,
27
+ takeUntil,
28
+ throwError,
29
+ } from 'rxjs';
30
+ import { catchError, map, switchMap, take, tap } from 'rxjs/operators';
31
+ import { CodeEvent, CodeEventResponse } from '../../models/code-event';
32
+ import {
33
+ CodeRunnerTestCaseExecutionOutputModel,
34
+ CoderunnerAttemptModel,
35
+ CoderunnerTestCaseExecutionModel,
36
+ CodingTestsResultsModel,
37
+ TIMEOUT_SIGNAL,
38
+ } from '../../models/coderunner-execution-results';
39
+ import { MixpanelEvents } from '../../models/mixpanel-events';
40
+ import {
41
+ ProgrammingLanguage,
42
+ ProgrammingLanguageVersioned,
43
+ } from '../../models/programming-language';
44
+ import {
45
+ GroupedTestCasesResult,
46
+ LATFormattedTestCases,
47
+ SqlTestCase,
48
+ Testcase,
49
+ } from '../../models/test-cases';
50
+ import { ROOT_TRANSLATIONS_SCOPE } from '../../models/translations';
51
+ import { CoderunnerApiService } from './coderunner-api.service';
52
+
53
+ interface CustomError {
54
+ detail?: string;
55
+ }
56
+
57
+ @Injectable()
58
+ export class CodingTestService {
59
+ readonly translationContext = `${ROOT_TRANSLATIONS_SCOPE}.CODING_LIB.`;
60
+
61
+ private codingTestResults: CodingTestsResultsModel = {
62
+ listResponse: [],
63
+ resume: {
64
+ ok: 0,
65
+ error: 0,
66
+ total: 0,
67
+ },
68
+ };
69
+ private currentLanguage!: ProgrammingLanguage;
70
+ private codingTestResutsSubj$ = new BehaviorSubject(this.codingTestResults);
71
+ codingTestResults$ = this.codingTestResutsSubj$.asObservable();
72
+
73
+ isTestResultsLoading = signal<boolean>(false);
74
+
75
+ private unsubscribe$ = new Subject<void>();
76
+
77
+ private sqlTestCases!: SqlTestCase[];
78
+ private isSQLTest = false;
79
+
80
+ constructor(
81
+ private coderunnerApiService: CoderunnerApiService,
82
+ private libCodingTestService: LibCodingTestService,
83
+ private mixpanel: MixpanelService,
84
+ private snackBar: SnackbarService,
85
+ private dialog: DialogService
86
+ ) {}
87
+
88
+ private getBaseExampleTestCase = (id: number, index: number) => ({
89
+ id: id ?? 0,
90
+ name: `Example ${index + 1}`,
91
+ preloaded: true,
92
+ });
93
+
94
+ transformToExampleLATtestCase = (
95
+ testcase: Testcase,
96
+ index: number
97
+ ): ExampleTestCase => ({
98
+ ...this.getBaseExampleTestCase(testcase.id!, index),
99
+ expectedOutput: testcase.solution ?? '',
100
+ input: testcase.input,
101
+ });
102
+
103
+ transformToExampleSQLTestCase = (
104
+ testcase: SqlTestCase,
105
+ index: number
106
+ ): ExampleTestCase => ({
107
+ ...this.getBaseExampleTestCase(testcase.id!, index),
108
+ expectedOutput: testcase.result ?? '',
109
+ });
110
+
111
+ setSqlTestCases(sqlTestCases: SqlTestCase[]) {
112
+ this.sqlTestCases = sqlTestCases;
113
+ }
114
+
115
+ getSqlTestCases(): SqlTestCase[] {
116
+ return this.sqlTestCases;
117
+ }
118
+
119
+ handleResetCodeModal(applicationTheme: ApplicationTheme, companyColor: string): void {
120
+ const modalTranslationContext = `${ROOT_TRANSLATIONS_SCOPE}.MODALS.RESET_CODE.`;
121
+ const modalData = {
122
+ confirmButtonText: translate(`${modalTranslationContext}SUBMIT`),
123
+ title: translate(`${modalTranslationContext}TITLE`),
124
+ message: translate(`${modalTranslationContext}MESSAGE`),
125
+ color: companyColor,
126
+ };
127
+ const dialogRef = this.dialog.open(ConfirmDialogComponent, {
128
+ applicationTheme,
129
+ size: 'small',
130
+ extraData: modalData,
131
+ });
132
+
133
+ dialogRef
134
+ .afterClosed()
135
+ .pipe(filter(Boolean), take(1))
136
+ .subscribe(() => {
137
+ this.libCodingTestService.confirmReset();
138
+ });
139
+ }
140
+
141
+ listenDeleteCustomTestCases(): Observable<TestCaseDeletionData> {
142
+ return this.libCodingTestService.testCaseDeletion$.pipe(
143
+ filter(({ action }) => action === TestCaseDeletionAction.Try)
144
+ );
145
+ }
146
+
147
+ handleDeleteTestCaseModal(
148
+ index: number,
149
+ applicationTheme: ApplicationTheme,
150
+ companyColor: string
151
+ ): void {
152
+ const deleteTestCaseTranslationContext = `${ROOT_TRANSLATIONS_SCOPE}.MODALS.DELETE_TEST_CASE.`;
153
+ const modalData = {
154
+ title: translate(`${deleteTestCaseTranslationContext}TITLE`),
155
+ message: translate(`${deleteTestCaseTranslationContext}MESSAGE`),
156
+ confirmButtonText: translate(`${deleteTestCaseTranslationContext}SUBMIT`),
157
+ color: companyColor,
158
+ };
159
+ const dialogRef = this.dialog.open(ConfirmDialogComponent, {
160
+ applicationTheme,
161
+ size: 'small',
162
+ extraData: modalData,
163
+ });
164
+
165
+ dialogRef
166
+ .afterClosed()
167
+ .pipe(filter(Boolean), take(1))
168
+ .subscribe(() => {
169
+ this.libCodingTestService.confirmTestCaseDeletion(index);
170
+ });
171
+ }
172
+
173
+ handleLanguageChangeModal(
174
+ questionResultId: number,
175
+ selectedLanguage?: ProgrammingLanguage,
176
+ applicationTheme: ApplicationTheme = 'light',
177
+ companyColor?: string,
178
+ ): void {
179
+ const modalLangChangeTranslationContext = `${ROOT_TRANSLATIONS_SCOPE}.MODALS.LANGUAGE_CHANGE.`;
180
+ const modalData = {
181
+ confirmButtonText: translate(
182
+ `${modalLangChangeTranslationContext}SUBMIT`
183
+ ),
184
+ title: translate(`${modalLangChangeTranslationContext}TITLE`),
185
+ message: translate(`${modalLangChangeTranslationContext}MESSAGE`),
186
+ ...(companyColor && { color: companyColor }),
187
+ };
188
+
189
+ const dialogRef = this.dialog.open(ConfirmDialogComponent, {
190
+ applicationTheme,
191
+ size: 'small',
192
+ extraData: modalData,
193
+ });
194
+
195
+ dialogRef
196
+ .afterClosed()
197
+ .pipe(take(1))
198
+ .subscribe((result) => {
199
+ if (result) {
200
+ this.libCodingTestService.confirmLanguageChange();
201
+ if (selectedLanguage) {
202
+ this.setCurrentLanguageLAT(selectedLanguage);
203
+ this.sendLanguageChangeEvent$(questionResultId);
204
+ }
205
+ } else {
206
+ this.libCodingTestService.cancelLanguageChange();
207
+ }
208
+ });
209
+ }
210
+
211
+ setCurrentLanguageLAT(language: ProgrammingLanguage): void {
212
+ this.currentLanguage = language;
213
+ }
214
+
215
+ listenCodingTestChanges(): Observable<ResetCodeAction> {
216
+ return this.libCodingTestService.codeReset$.pipe(
217
+ filter((val) => val === ResetCodeAction.Try)
218
+ );
219
+ }
220
+
221
+ runTest(
222
+ resultId: number,
223
+ code: string,
224
+ isLAT = true,
225
+ isSQL = false,
226
+ // todo: remove from everywhere or comment temporarily?
227
+ publicPreview = false,
228
+ preview = false,
229
+ id = 0,
230
+ testResultId = 0,
231
+ testData?: TestResultRead
232
+ ): void {
233
+ this.preRunTests();
234
+ if (isSQL) {
235
+ this.getSQLrequest(
236
+ resultId,
237
+ testResultId,
238
+ code,
239
+ id,
240
+ publicPreview,
241
+ preview
242
+ );
243
+ } else if (isLAT) {
244
+ const testCases: (ExampleTestCase | CustomTestCase)[] =
245
+ this.libCodingTestService.getValidTestCases();
246
+ const testCasesFormatted = testCases.map((testCase) => ({
247
+ input: testCase.input,
248
+ solution: testCase.expectedOutput,
249
+ })) as LATFormattedTestCases[];
250
+ this.libCodingTestService.currentLanguage$
251
+ .pipe(take(1))
252
+ .subscribe((language) => {
253
+ const runReq$ = this.getLATrequest(
254
+ id,
255
+ resultId,
256
+ testResultId,
257
+ code,
258
+ testCasesFormatted as LATFormattedTestCases[],
259
+ language,
260
+ publicPreview,
261
+ preview
262
+ );
263
+ this.runLAT(
264
+ runReq$,
265
+ publicPreview,
266
+ preview,
267
+ testData || ({} as TestResultRead),
268
+ testCases,
269
+ code,
270
+ resultId
271
+ );
272
+ });
273
+ }
274
+ }
275
+
276
+ sendCopypasteEvent$(
277
+ code: string,
278
+ copiedText: string,
279
+ questionResultId: number
280
+ ): Observable<CodeEventResponse> {
281
+ const event: CodeEvent = this.getBaseEvent({
282
+ code,
283
+ event_type: 'COPYPASTE',
284
+ extra_payload: copiedText,
285
+ question_result_id: questionResultId,
286
+ });
287
+
288
+ return this.coderunnerApiService.sendCodeEvent$(event);
289
+ }
290
+
291
+ sendAutosaveEvent$(
292
+ code: string,
293
+ questionResultId: number
294
+ ): Observable<CodeEventResponse> {
295
+ const event: CodeEvent = this.getBaseEvent({
296
+ code,
297
+ event_type: 'AUTOSAVE',
298
+ question_result_id: questionResultId,
299
+ });
300
+
301
+ return this.coderunnerApiService
302
+ .sendCodeEvent$(event)
303
+ .pipe(catchError((err) => of(err)));
304
+ }
305
+
306
+ private sendLanguageChangeEvent$(questionResultId: number): void {
307
+ const event: CodeEvent = this.getBaseEvent({
308
+ event_type: 'SWITCH',
309
+ question_result_id: questionResultId,
310
+ });
311
+
312
+ this.coderunnerApiService.sendCodeEvent$(event).pipe(take(1)).subscribe();
313
+ }
314
+
315
+ private getBaseEvent(event: Partial<CodeEvent>): CodeEvent {
316
+ let language = '';
317
+
318
+ if (this.isSQLTest) {
319
+ language = ProgrammingLanguageVersioned.SQLite;
320
+ } else {
321
+ const { name, version } = this.currentLanguage;
322
+ language = `${name}-${version}`;
323
+ }
324
+
325
+ const baseEvent: Partial<CodeEvent> = {
326
+ code: '',
327
+ extra_payload: '',
328
+ attempt_id: null,
329
+ language,
330
+ };
331
+
332
+ return { ...baseEvent, ...event } as CodeEvent;
333
+ }
334
+
335
+ private sendCodeEvent$(
336
+ attemptId: string,
337
+ questionResultId: number,
338
+ code: string
339
+ ): Observable<CodeEventResponse> {
340
+ const event: CodeEvent = this.getBaseEvent({
341
+ code,
342
+ attempt_id: attemptId,
343
+ event_type: 'ATTEMPT',
344
+ question_result_id: questionResultId,
345
+ });
346
+
347
+ return this.coderunnerApiService.sendCodeEvent$(event);
348
+ }
349
+
350
+ private countTestResults(
351
+ executions: CoderunnerTestCaseExecutionModel[] | undefined
352
+ ): {
353
+ passed: number;
354
+ error: number;
355
+ total: number;
356
+ } {
357
+ if (executions) {
358
+ return executions.reduce(
359
+ (acc, response) => {
360
+ if (response.evaluation.testcase) {
361
+ acc.passed++;
362
+ } else {
363
+ acc.error++;
364
+ }
365
+ acc.total++;
366
+ return acc;
367
+ },
368
+ { passed: 0, error: 0, total: 0 }
369
+ );
370
+ }
371
+ return { passed: 0, error: 0, total: 0 };
372
+ }
373
+
374
+ private handleTestCasesResult(
375
+ testCasesResultObject: CodingTestsResultsModel
376
+ ): void {
377
+ this.codingTestResults = testCasesResultObject;
378
+ this.publishTestResults();
379
+ this.isTestResultsLoading.set(false);
380
+ }
381
+
382
+ private getSQLrequest(
383
+ resultId: number,
384
+ testResultId: number,
385
+ code: string,
386
+ id: number,
387
+ publicPreview: boolean,
388
+ preview: boolean
389
+ ): void {
390
+ const testCases: SqlTestCase[] = this.getSqlTestCases();
391
+ let runReq$: Observable<CoderunnerAttemptModel>;
392
+ if (publicPreview) {
393
+ runReq$ = this.coderunnerApiService.runPublicTest$(
394
+ code,
395
+ testCases,
396
+ ProgrammingLanguageVersioned.SQLite
397
+ );
398
+ } else if (preview) {
399
+ runReq$ = this.coderunnerApiService.runPreviewTest$(
400
+ testResultId,
401
+ code,
402
+ testCases,
403
+ ProgrammingLanguageVersioned.SQLite
404
+ );
405
+ } else {
406
+ runReq$ = this.coderunnerApiService.runNonPreviewTest$(
407
+ id,
408
+ resultId,
409
+ code,
410
+ testCases,
411
+ ProgrammingLanguageVersioned.SQLite
412
+ );
413
+ }
414
+ this.runSql(runReq$, testCases, resultId, !publicPreview && !preview, code);
415
+ }
416
+
417
+ private runSql(
418
+ runReq$: Observable<CoderunnerAttemptModel>,
419
+ testCases: SqlTestCase[],
420
+ questionResultId: number,
421
+ isActualQuestion: boolean,
422
+ code: string
423
+ ) {
424
+ let attemptId = '';
425
+
426
+ runReq$
427
+ .pipe(
428
+ catchError((err) => {
429
+ this.finalizeReq(err);
430
+ return throwError(() => err);
431
+ }),
432
+ tap((res) => this.finalizeReq(res as CustomError)),
433
+ tap(({ id }) => (attemptId = id ?? '')),
434
+ switchMap((responseArray: CoderunnerAttemptModel) =>
435
+ of(responseArray.executions).pipe(
436
+ map((executions) => this.countTestResults(executions)),
437
+ map((countResult) => {
438
+ const sqlExecutedSuccessfullyCopy = translate(
439
+ `${this.translationContext}TEST_RESULTS.TEST_CASES.SQL_EXECUTED_SUCCESSFULLY`
440
+ );
441
+ const noOutputCopies = this.getNoOutputCopies();
442
+
443
+ return {
444
+ resume: {
445
+ ok: countResult.passed,
446
+ error: countResult.error,
447
+ total: countResult.total,
448
+ },
449
+ listResponse: responseArray.executions?.map(
450
+ ({ run, evaluation }: CoderunnerTestCaseExecutionModel) => ({
451
+ actualOutput: run.stdout
452
+ ? run.stdout
453
+ : run.stderr || run.signal === TIMEOUT_SIGNAL
454
+ ? noOutputCopies['noResponse']
455
+ : noOutputCopies['noOutput'],
456
+ logs: this.getCoderunnerLogs(
457
+ run,
458
+ TIMEOUT_SIGNAL,
459
+ noOutputCopies['timeoutError'],
460
+ sqlExecutedSuccessfullyCopy
461
+ ),
462
+ status: evaluation.testcase ? 'passed' : 'error',
463
+ })
464
+ ),
465
+ } as CodingTestsResultsModel;
466
+ }),
467
+ tap((testCasesResultObject) =>
468
+ this.handleTestCasesResult(testCasesResultObject)
469
+ )
470
+ )
471
+ ),
472
+ switchMap(() =>
473
+ this.getSendCodeEventStream$(
474
+ isActualQuestion,
475
+ attemptId,
476
+ questionResultId,
477
+ code
478
+ )
479
+ )
480
+ )
481
+ .subscribe();
482
+ }
483
+
484
+ private getLATrequest(
485
+ id: number,
486
+ resultId: number,
487
+ testResultId: number,
488
+ code: string,
489
+ testCasesFormatted: LATFormattedTestCases[],
490
+ language: CodeEditorLanguages,
491
+ publicPreview: boolean,
492
+ preview: boolean
493
+ ): Observable<CoderunnerAttemptModel> {
494
+ if (publicPreview) {
495
+ return this.coderunnerApiService.runPublicTest$(
496
+ code,
497
+ testCasesFormatted,
498
+ `${language}-${this.libCodingTestService.getVersion()}`
499
+ );
500
+ } else if (preview) {
501
+ return this.coderunnerApiService.runPreviewTest$(
502
+ testResultId,
503
+ code,
504
+ testCasesFormatted,
505
+ `${language}-${this.libCodingTestService.getVersion()}`
506
+ );
507
+ } else {
508
+ return this.coderunnerApiService.runNonPreviewTest$(
509
+ id,
510
+ resultId,
511
+ code,
512
+ testCasesFormatted,
513
+ `${language}-${this.libCodingTestService.getVersion()}`
514
+ );
515
+ }
516
+ }
517
+
518
+ setIsSQLTest(isSQL: boolean): void {
519
+ this.isSQLTest = isSQL;
520
+ }
521
+
522
+ private runLAT(
523
+ runCodeRequest$: Observable<CoderunnerAttemptModel>,
524
+ isPublicPreviewLAT: boolean,
525
+ isPractice = false,
526
+ testData: TestResultRead,
527
+ testCases: ExampleTestCase[] | CustomTestCase[],
528
+ code: string,
529
+ questionResultId: number
530
+ ): void {
531
+ const pistonRegex = /\/piston\/[^/ ]+(\/[^/ ]+)*/g;
532
+ const reqStartTime = performance.now();
533
+ let reqEndTime: number;
534
+ let attemptId = '';
535
+
536
+ runCodeRequest$
537
+ .pipe(
538
+ catchError((err) => {
539
+ this.finalizeReq(err);
540
+ return throwError(() => err);
541
+ }),
542
+ tap((res) => this.finalizeReq(res as CustomError)),
543
+ tap(() => (reqEndTime = performance.now())),
544
+ tap(({ id }) => (attemptId = id ?? '')),
545
+ switchMap((responseArray: CoderunnerAttemptModel) =>
546
+ of(responseArray.executions).pipe(
547
+ map((executions) => (executions ? executions : [])),
548
+ map((executions: CoderunnerTestCaseExecutionModel[]) =>
549
+ executions.map((x) => {
550
+ if (x.run.stderr) {
551
+ x.run.stderr = x.run.stderr.replace(pistonRegex, '');
552
+ }
553
+ if (x.run.stdout) {
554
+ x.run.stdout = x.run.stdout.replace(pistonRegex, '');
555
+ }
556
+ return x;
557
+ })
558
+ ),
559
+ map((executions) => this.countTestResults(executions)),
560
+ map((countResult) => {
561
+ if (!isPublicPreviewLAT) {
562
+ const responseTime = Math.round(reqEndTime - reqStartTime);
563
+ this.trackTestcasesRun(
564
+ testData,
565
+ countResult,
566
+ responseArray.executions || [],
567
+ responseTime,
568
+ testCases
569
+ );
570
+ }
571
+ const noOutputCopies = this.getNoOutputCopies();
572
+ const executedSuccessfullyCopy = translate(
573
+ `${this.translationContext}TEST_RESULTS.TEST_CASES.EXECUTED_SUCCESSFULLY`
574
+ );
575
+
576
+ return {
577
+ resume: {
578
+ ok: countResult.passed,
579
+ error: countResult.error,
580
+ total: countResult.total,
581
+ },
582
+ listResponse: responseArray.executions?.map(
583
+ ({
584
+ run,
585
+ evaluation,
586
+ compile,
587
+ }: CoderunnerTestCaseExecutionModel) => ({
588
+ actualOutput: run.stdout
589
+ ? run.stdout
590
+ : run.stderr || run.signal === TIMEOUT_SIGNAL
591
+ ? noOutputCopies['noResponse']
592
+ : noOutputCopies['noOutput'],
593
+ logs: this.getCoderunnerLogs(
594
+ run,
595
+ TIMEOUT_SIGNAL,
596
+ noOutputCopies['timeoutError'],
597
+ executedSuccessfullyCopy,
598
+ compile
599
+ ),
600
+ status: evaluation.testcase ? 'passed' : 'error',
601
+ })
602
+ ),
603
+ } as CodingTestsResultsModel;
604
+ }),
605
+ tap((testCasesResultObject) =>
606
+ this.handleTestCasesResult(testCasesResultObject)
607
+ )
608
+ )
609
+ ),
610
+ switchMap(() =>
611
+ this.getSendCodeEventStream$(
612
+ !isPractice && !isPublicPreviewLAT,
613
+ attemptId,
614
+ questionResultId,
615
+ code
616
+ )
617
+ )
618
+ )
619
+ .subscribe();
620
+ }
621
+
622
+ private finalizeReq(err: CustomError): void {
623
+ this.isTestResultsLoading.set(false);
624
+ if (err.detail) {
625
+ this.snackBar.error(err.detail);
626
+ }
627
+ }
628
+
629
+ private getNoOutputCopies(): { [key: string]: string } {
630
+ return {
631
+ noResponse: translate(
632
+ `${this.translationContext}TEST_RESULTS.TEST_CASES.NO_RESPONSE`
633
+ ),
634
+ noOutput: translate(
635
+ `${this.translationContext}TEST_RESULTS.TEST_CASES.NONE`
636
+ ),
637
+ timeoutError: translate(
638
+ `${this.translationContext}TEST_RESULTS.TEST_CASES.TIMEOUT_ERROR`
639
+ ),
640
+ };
641
+ }
642
+
643
+ private getSendCodeEventStream$(
644
+ isActualQuestion: boolean,
645
+ attemptId: string,
646
+ questionResultId: number,
647
+ code: string
648
+ ): Observable<CodeEventResponse> {
649
+ if (isActualQuestion) {
650
+ return this.sendCodeEvent$(attemptId, questionResultId, code);
651
+ } else {
652
+ return of({} as CodeEventResponse);
653
+ }
654
+ }
655
+
656
+ // todo: check if we need this anymore due to MFE providers specificity
657
+ unsubscribeAll(): void {
658
+ this.unsubscribe$.next();
659
+ this.unsubscribe$.complete();
660
+ }
661
+
662
+ resetRunTestSummary(): void {
663
+ this.codingTestResults = {
664
+ listResponse: [],
665
+ resume: {
666
+ ok: 0,
667
+ error: 0,
668
+ total: 0,
669
+ },
670
+ };
671
+
672
+ this.publishTestResults();
673
+ }
674
+
675
+ trackConfigChanged(isPracticeQuestion = false): void {
676
+ this.libCodingTestService.configFieldChanged$
677
+ .pipe(takeUntil(this.unsubscribe$))
678
+ .subscribe((config) => {
679
+ const { settingInstance, changedKey, newValueString } =
680
+ this.getMixpanelSettingsData(isPracticeQuestion, config);
681
+
682
+ this.mixpanel.track(MixpanelEvents.ChangedSettings, {
683
+ coderunner_setting: changedKey,
684
+ coderunner_setting_new_value: newValueString,
685
+ coderunner_setting_instance: settingInstance,
686
+ });
687
+ });
688
+ }
689
+
690
+ private getCoderunnerLogs(
691
+ run: CodeRunnerTestCaseExecutionOutputModel,
692
+ TIMEOUT_SIGNAL: string,
693
+ timeoutErrorCopy: string,
694
+ executedSuccessfullyCopy: string,
695
+ compile?: CodeRunnerTestCaseExecutionOutputModel,
696
+ isSQL?: boolean
697
+ ): string {
698
+ if (isSQL) {
699
+ return run?.stderr === TIMEOUT_SIGNAL
700
+ ? run.stderr
701
+ : executedSuccessfullyCopy;
702
+ } else if (run?.stderr) {
703
+ return run.stderr === compile?.stderr
704
+ ? run.stderr
705
+ : `${run.stderr}\n${compile?.stderr || ''}`;
706
+ } else if (run.signal === TIMEOUT_SIGNAL) {
707
+ return timeoutErrorCopy;
708
+ }
709
+ return executedSuccessfullyCopy;
710
+ }
711
+
712
+ private trackTestcasesRun(
713
+ testData: TestResultRead,
714
+ countResult: { [key: string]: number },
715
+ testCasesExecutions: CoderunnerTestCaseExecutionModel[],
716
+ responseTime: number,
717
+ testCases: ExampleTestCase[] | CustomTestCase[]
718
+ ): void {
719
+ const {
720
+ instance,
721
+ customPassed,
722
+ customFailed,
723
+ examplePassed,
724
+ exampleFailed,
725
+ customEmpty,
726
+ testUuid,
727
+ testName,
728
+ } = this.getMixpanelTestCasesData(
729
+ testData,
730
+ countResult,
731
+ testCasesExecutions,
732
+ testCases
733
+ );
734
+
735
+ this.mixpanel.track('Candidate ran tests in Coderunner', {
736
+ coding_test_uuid: testUuid,
737
+ coding_test_name: testName,
738
+ coderunner_test_instance: instance,
739
+ custom_testcases_passed: customPassed,
740
+ custom_testcases_failed: customFailed,
741
+ custom_testcases_empty: customEmpty,
742
+ example_testcases_passed: examplePassed,
743
+ example_testcases_failed: exampleFailed,
744
+ coderunner_response_time: responseTime,
745
+ });
746
+ }
747
+
748
+ private preRunTests() {
749
+ this.isTestResultsLoading.set(true);
750
+ this.resetRunTestSummary();
751
+ }
752
+
753
+ private getMixpanelTestCasesData(
754
+ testData: TestResultRead,
755
+ { total }: { [key: string]: number },
756
+ testCasesExecutions: CoderunnerTestCaseExecutionModel[],
757
+ validTestCases: ExampleTestCase[] | CustomTestCase[]
758
+ ) {
759
+ const allTestCasesCount = this.libCodingTestService.getTestCasesCount();
760
+ const customEmpty = allTestCasesCount - total || 0;
761
+ const instance = this.getMixpanelInstanceValue(!!testData.is_preview_mode);
762
+ const { uuid: testUuid } = testData;
763
+ const { name: testName } = testData.test ?? { name: '' };
764
+ const mixpanelResults = this.getGroupedTestCaseResult(
765
+ testCasesExecutions,
766
+ validTestCases
767
+ );
768
+
769
+ return {
770
+ testUuid: testUuid?.replace(/-/g, ''),
771
+ testName,
772
+ instance,
773
+ customEmpty,
774
+ customPassed: mixpanelResults.custom.passed,
775
+ customFailed: mixpanelResults.custom.failed,
776
+ examplePassed: mixpanelResults.example.passed,
777
+ exampleFailed: mixpanelResults.example.failed,
778
+ };
779
+ }
780
+
781
+ private getGroupedTestCaseResult(
782
+ testCasesExecutions: CoderunnerTestCaseExecutionModel[],
783
+ validTestCases: ExampleTestCase[] | CustomTestCase[]
784
+ ): GroupedTestCasesResult {
785
+ const groupedResult = {
786
+ custom: {
787
+ failed: 0,
788
+ passed: 0,
789
+ },
790
+ example: {
791
+ failed: 0,
792
+ passed: 0,
793
+ },
794
+ };
795
+
796
+ testCasesExecutions.forEach(({ input: resultInput, evaluation }) => {
797
+ const localTestCase = validTestCases.find(
798
+ ({ input }) => resultInput === input
799
+ );
800
+ const mixpanelResultBody = localTestCase?.preloaded
801
+ ? groupedResult.example
802
+ : groupedResult.custom;
803
+
804
+ if (evaluation?.testcase) {
805
+ mixpanelResultBody.passed++;
806
+ } else {
807
+ mixpanelResultBody.failed++;
808
+ }
809
+ });
810
+
811
+ return groupedResult;
812
+ }
813
+
814
+ private getMixpanelSettingsData(
815
+ isPracticeQuestion: boolean,
816
+ config: { [key: string]: boolean | string | number }
817
+ ) {
818
+ const settingInstance = this.getMixpanelInstanceValue(isPracticeQuestion);
819
+ const changedKey = Object.keys(config)[0];
820
+ const THEME_IDENTIFIER = 'theme-';
821
+ let changedValue: boolean | string | number = config[changedKey];
822
+ let newValueString;
823
+
824
+ if (typeof changedValue === 'boolean') {
825
+ newValueString = changedValue ? 'ON' : 'OFF';
826
+ } else {
827
+ changedValue = String(changedValue);
828
+ newValueString = changedValue.includes(THEME_IDENTIFIER)
829
+ ? changedValue.replace(THEME_IDENTIFIER, '')
830
+ : changedValue;
831
+ }
832
+
833
+ return { settingInstance, changedKey, newValueString };
834
+ }
835
+
836
+ private getMixpanelInstanceValue(isPracticeQuestion: boolean) {
837
+ return isPracticeQuestion ? 'practice question' : 'real test';
838
+ }
839
+
840
+ private publishTestResults() {
841
+ this.codingTestResutsSubj$.next({ ...this.codingTestResults });
842
+ }
843
+ }