Skip to content

Commit 7b2e4b8

Browse files
authored
Merge pull request #187 from topcoder-platform/PROD-2437_patch-challenge
PROD-2437 Patch Challenge -> PROD-2321 Bug Hunt Intake
2 parents 4381537 + 473f89e commit 7b2e4b8

24 files changed

+378
-129
lines changed

src-ts/tools/work/work-lib/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
export * from './work-constants'
12
export * from './work-images'
23
export * from './work-pdfs'
34
export * from './work-provider'
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { default as WorkStrings } from './strings.json'
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"INFO_NOT_PROVIDED": "Information not provided",
3+
"MARKDOWN_SUBMISSION_GUIDELINES": "## Final Submission Guidelines \n\n Please submit a zip file containing your analysis/solution.",
4+
"NOT_PROVIDED": "Not provided"
5+
}

src-ts/tools/work/work-lib/work-provider/work-functions/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ export * from './work-factory'
22
export {
33
type Challenge,
44
ChallengeMetadataName,
5+
ChallengeMetadataTitle,
56
type Work,
67
workBugHuntConfig,
78
workPriceData,
@@ -27,4 +28,5 @@ export {
2728
getGroupedByStatus as workGetGroupedByStatus,
2829
getPricesConfig as workGetPricesConfig,
2930
getStatusFilter as workGetStatusFilter,
31+
updateAsync as workUpdateAsync,
3032
} from './work.functions'

src-ts/tools/work/work-lib/work-provider/work-functions/work-factory/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ export * from './work-solution.model'
22
export {
33
create as workFactoryCreate,
44
buildCreateBody as workFactoryBuildCreateBody,
5+
buildUpdateBody as workFactoryBuildUpdateBody,
56
getStatus as workFactoryGetStatus,
67
mapFormData as workFactoryMapFormData,
78
} from './work.factory'

src-ts/tools/work/work-lib/work-provider/work-functions/work-factory/work.factory.ts

Lines changed: 173 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,20 @@
11
import moment from 'moment'
22

3+
import { WorkStrings } from '../../../work-constants'
34
import {
45
Challenge,
56
ChallengeCreateBody,
67
ChallengeMetadata,
78
ChallengeMetadataName,
9+
ChallengeMetadataTitle,
810
ChallengePhase,
911
ChallengePhaseName,
12+
ChallengeUpdateBody,
1013
Work,
1114
WorkPrice,
15+
WorkPriceBreakdown,
1216
WorkPricesType,
17+
WorkPrize,
1318
WorkProgress,
1419
WorkProgressStep,
1520
WorkStatus,
@@ -74,7 +79,7 @@ export function buildCreateBody(workTypeConfig: WorkTypeConfig): ChallengeCreate
7479
}
7580

7681
return {
77-
description: 'Information not provided',
82+
description: WorkStrings.INFO_NOT_PROVIDED,
7883
discussions: [
7984
{
8085
name: 'new-self-service-project',
@@ -97,6 +102,69 @@ export function buildCreateBody(workTypeConfig: WorkTypeConfig): ChallengeCreate
97102
}
98103
}
99104

105+
export function buildUpdateBody(workTypeConfig: WorkTypeConfig, challenge: Challenge, formData: any): ChallengeUpdateBody {
106+
107+
const type: WorkType = workTypeConfig.type
108+
109+
const intakeForm: ChallengeMetadata | undefined = findMetadata(challenge, ChallengeMetadataName.intakeForm) || undefined
110+
const form: IntakeForm = !!intakeForm?.value ? JSON.parse(intakeForm.value)?.form : {}
111+
form.basicInfo = formData
112+
113+
// TODO: Add the progress.currentStep to form to determine if it's in the review phase (review page)
114+
// or not. The legacy intakes use currentStep 7 for review and 5 for taking the user to the login step
115+
// as those numbers map to the routes configured for each work type (see IntakeForm.jsx for an example).
116+
// We can probably clean that up as we don't need that many routes
117+
118+
// --- Build Metadata --- //
119+
const intakeMetadata: Array<ChallengeMetadata> = [
120+
{
121+
name: ChallengeMetadataName.intakeForm,
122+
value: JSON.stringify({ form }),
123+
},
124+
]
125+
126+
Object.keys(formData).forEach((key) => {
127+
intakeMetadata.push({
128+
name: ChallengeMetadataName[key as keyof typeof ChallengeMetadataName],
129+
value: formData[key] || '',
130+
})
131+
})
132+
// ---- End Build Metadata ---- //
133+
134+
// --- Build the Markdown string that gets displayed in Work Manager app and others --- //
135+
const templateString: Array<string> = []
136+
137+
const data: ReadonlyArray<FormDetail> = mapFormData(
138+
type,
139+
formData
140+
)
141+
142+
data.forEach((formDetail) => {
143+
if (Object.keys(formDetail).length <= 0) { return }
144+
145+
const formattedValue: string = formatFormDataOption(formDetail.value)
146+
templateString.push(`### ${formDetail.title}\n\n${formattedValue}\n\n`)
147+
})
148+
149+
if (getTypeCategory(type) === WorkTypeCategory.data) {
150+
templateString.push(
151+
WorkStrings.MARKDOWN_SUBMISSION_GUIDELINES
152+
)
153+
}
154+
// ---- End Build Markdown string ---- //
155+
156+
const body: ChallengeUpdateBody = {
157+
description: templateString.join(''),
158+
id: challenge.id,
159+
metadata: intakeMetadata,
160+
name: formData.projectTitle,
161+
phases: workTypeConfig.timeline,
162+
prizeSets: getPrizes(workTypeConfig),
163+
}
164+
165+
return body
166+
}
167+
100168
export function getStatus(challenge: Challenge): WorkStatus {
101169

102170
switch (challenge.status) {
@@ -135,11 +203,50 @@ export function mapFormData(type: string, formData: any): ReadonlyArray<FormDeta
135203
case (WorkType.design):
136204
return buildFormDataDesign(formData)
137205
case (WorkType.bugHunt):
138-
return buildFormBughunt(formData)
206+
return buildFormDataBugHunt(formData)
139207
default:
140208
return formData
141209
}
142210
}
211+
function buildFormDataBugHunt(formData: any): ReadonlyArray<FormDetail> {
212+
return [
213+
{
214+
key: ChallengeMetadataName.projectTitle,
215+
title: ChallengeMetadataTitle.projectTitle,
216+
value: formData.projectTitle,
217+
},
218+
{
219+
key: ChallengeMetadataName.websiteURL,
220+
title: ChallengeMetadataTitle.websiteURL,
221+
value: formData.websiteURL,
222+
},
223+
{
224+
key: ChallengeMetadataName.goals,
225+
title: ChallengeMetadataTitle.bugHuntGoals,
226+
value: formData.goals,
227+
},
228+
{
229+
key: ChallengeMetadataName.featuresToTest,
230+
title: ChallengeMetadataTitle.featuresToTest,
231+
value: formData.featuresToTest,
232+
},
233+
{
234+
key: ChallengeMetadataName.deliveryType,
235+
title: ChallengeMetadataTitle.bugDeliveryType,
236+
value: `${formData.deliveryType}${formData.repositoryLink ? ': ' + formData.repositoryLink : ''}`,
237+
},
238+
{
239+
key: ChallengeMetadataName.additionalInformation,
240+
title: ChallengeMetadataTitle.additionalInformation,
241+
value: formData.additionalInformation,
242+
},
243+
{
244+
key: ChallengeMetadataName.packageType,
245+
title: ChallengeMetadataTitle.bugHuntPackage,
246+
value: formData.packageType,
247+
},
248+
]
249+
}
143250

144251
function buildFormDataData(formData: any): ReadonlyArray<FormDetail> {
145252
return [
@@ -207,41 +314,6 @@ function buildFormDataDesign(formData: any): ReadonlyArray<FormDetail> {
207314
]
208315
}
209316

210-
function buildFormBughunt(formData: any): ReadonlyArray<FormDetail> {
211-
212-
return [
213-
{
214-
key: 'projectTitle',
215-
...formData.projectTitle,
216-
},
217-
{
218-
key: 'websiteURL',
219-
...formData.websiteURL,
220-
},
221-
{
222-
key: 'goals',
223-
...formData.goals,
224-
},
225-
{
226-
key: 'features',
227-
...formData.features,
228-
},
229-
{
230-
key: 'bugDelivery',
231-
title: 'Bug Delivery',
232-
value: `[${formData.deliveryType.value}]: ${formData.deliveryUrl.value}`,
233-
},
234-
{
235-
key: 'additionalInformation',
236-
...formData.additionalInformation,
237-
},
238-
{
239-
key: 'packageType',
240-
...formData.packageType,
241-
},
242-
]
243-
}
244-
245317
function buildFormDataFindData(formData: any): ReadonlyArray<FormDetail> {
246318
const isPrimaryDataChallengeOther: boolean = formData.primaryDataChallenge?.value === 3
247319
return [
@@ -289,6 +361,22 @@ function buildFormDataProblem(formData: any): ReadonlyArray<FormDetail> {
289361
]
290362
}
291363

364+
/**
365+
* This function checks if the param provided is empty based on its type
366+
* The param is empty if: is falsey || is an empty string || is an empty array || is an empty object
367+
* This is used for determining if a form field entry is emtpy after being formatted for display
368+
*/
369+
function checkFormDataOptionEmpty(detail: any): boolean {
370+
return (
371+
!detail ||
372+
(typeof detail === 'string' && detail.trim().length === 0) ||
373+
(Array.isArray(detail) && detail.length === 0) ||
374+
(typeof detail === 'object' &&
375+
Object.values(detail).filter((val: any) => val && val.trim().length !== 0)
376+
.length === 0)
377+
)
378+
}
379+
292380
function findMetadata(challenge: Challenge, metadataName: ChallengeMetadataName): ChallengeMetadata | undefined {
293381
return challenge.metadata?.find((item: ChallengeMetadata) => item.name === metadataName)
294382
}
@@ -326,6 +414,27 @@ function findPhase(challenge: Challenge, phases: Array<string>): ChallengePhase
326414
return phase
327415
}
328416

417+
function formatFormDataOption(detail: Array<string> | { [key: string]: any }): string {
418+
const noInfoProvidedText: string = WorkStrings.NOT_PROVIDED
419+
const isEmpty: boolean = checkFormDataOptionEmpty(detail)
420+
421+
if (isEmpty) {
422+
return noInfoProvidedText
423+
}
424+
if (Array.isArray(detail)) {
425+
return detail.join('\n\n')
426+
}
427+
if (typeof detail === 'object') {
428+
return Object.keys(detail)
429+
.map((key: string) => {
430+
const value: string = detail[key] || noInfoProvidedText
431+
return `${key}: ${value}`
432+
})
433+
.join('\n\n')
434+
}
435+
return detail
436+
}
437+
329438
function getCost(challenge: Challenge, priceConfig: WorkPrice, type: WorkType): number | undefined {
330439

331440
switch (type) {
@@ -357,6 +466,33 @@ function getDescription(challenge: Challenge, type: WorkType): string | undefine
357466
}
358467
}
359468

469+
function getPrizes(workTypeConfig: WorkTypeConfig): Array<WorkPrize> {
470+
const priceConfig: WorkPriceBreakdown =
471+
workTypeConfig.usePromo && workTypeConfig.promo
472+
? workTypeConfig.promo
473+
: workTypeConfig.base
474+
475+
return [
476+
{
477+
description: 'Challenge Prizes',
478+
prizes: priceConfig.placementDistributions.map((percentage) => ({
479+
type: 'USD',
480+
value: Math.round(percentage * priceConfig.price),
481+
})),
482+
type: 'placement',
483+
},
484+
{
485+
description: 'Reviewer Payment',
486+
prizes:
487+
priceConfig.reviewerDistributions.map((percentage) => ({
488+
type: 'USD',
489+
value: Math.round(percentage * priceConfig.price),
490+
})),
491+
type: 'reviewer',
492+
},
493+
]
494+
}
495+
360496
function getProgress(challenge: Challenge, workStatus: WorkStatus): WorkProgress {
361497

362498
const steps: ReadonlyArray<WorkProgressStep> = [
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,15 @@
11
export enum ChallengeMetadataName {
2+
additionalInformation = 'additionalInformation',
3+
deliveryType = 'deliveryType',
24
deviceCount = 'basicInfo.numberOfDevices',
35
description = 'websitePurpose.description',
6+
featuresToTest = 'featuresToTest',
47
feedback = 'customerFeedback',
58
goals = 'goals',
69
intakeForm = 'intake-form',
710
pageCount = 'basicInfo.numberOfPages',
11+
packageType = 'packageType',
12+
projectTitle = 'projectTitle',
13+
repositoryLink = 'repositoryLink',
14+
websiteURL = 'websiteURL',
815
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
export enum ChallengeMetadataTitle {
2+
additionalInformation = 'Additional Information',
3+
bugDeliveryType = 'Bug Delivery',
4+
bugHuntGoals = 'Bug Hunt Goals',
5+
bugHuntPackage = 'Bug Hunt Package',
6+
featuresToTest = 'Features to Test',
7+
projectTitle = 'Project Title',
8+
websiteURL = 'Website URL',
9+
}

src-ts/tools/work/work-lib/work-provider/work-functions/work-store/challenge.model.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,21 @@
11
import { ChallengeMetadata } from './challenge-metadata.model'
22
import { ChallengePhase } from './challenge-phase'
33
import { ChallengeTag } from './challenge-tag.enum'
4+
import { WorkPrize } from './work-prize.model'
5+
import { WorkTimelinePhase } from './work-timeline-phase.model'
46

57
export interface Challenge {
68
created: string
79
description: string
810
discussions?: Array<{ [key: string]: string }>
911
id: string
10-
legacy?: { [key: string]: any },
12+
legacy?: { [key: string]: any }
1113
metadata: Array<ChallengeMetadata>
1214
name: string
1315
numOfRegistrants?: number
1416
numOfSubmissions?: number
1517
phases: Array<ChallengePhase>
18+
prizeSets?: Array<WorkPrize>
1619
status: string
1720
tags: Array<ChallengeTag>
1821
timelineTemplateId?: string
@@ -33,3 +36,14 @@ export type ChallengeCreateBody = Pick<
3336
'trackId' |
3437
'typeId'
3538
>
39+
40+
export type ChallengeUpdateBody = Pick<
41+
Challenge,
42+
'description' |
43+
'id' |
44+
'metadata' |
45+
'name' |
46+
'prizeSets'
47+
> & {
48+
phases: ReadonlyArray<WorkTimelinePhase>
49+
}

src-ts/tools/work/work-lib/work-provider/work-functions/work-store/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
export * from './challenge-metadata-name.enum'
2+
export * from './challenge-metadata-title.enum'
23
export * from './challenge-metadata.model'
34
export * from './challenge-phase'
45
export * from './challenge-phase-name.enum'
@@ -14,6 +15,7 @@ export {
1415
problem as workPriceProblem,
1516
getPricesConfig as workGetPricesConfig,
1617
} from './work-prices.store'
18+
export * from './work-prize.model'
1719
export * from './work-progress.model'
1820
export * from './work-progress-step.model'
1921
export * from './work-status-filter.enum'
@@ -31,4 +33,5 @@ export {
3133
deleteAsync as workStoreDeleteAsync,
3234
getAsync as workStoreGetAsync,
3335
getFilteredByStatus as workStoreGetFilteredByStatus,
36+
updateAsync as workStoreUpdateAsync,
3437
} from './work.store'

0 commit comments

Comments
 (0)