Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions __tests__/shared/utils/challenge-listing/constants.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import {
getVisibleChallengeTypes,
sanitizeChallengeTypeFilter,
} from 'utils/challenge-listing/constants';

describe('challenge listing constants', () => {
test('returns only the supported challenge types once and in filter order', () => {
const challengeTypes = [
{ name: 'AI', abbreviation: 'AI' },
{ name: 'AI Engineering', abbreviation: 'AIENG' },
{ name: 'Marathon Match', abbreviation: 'MM' },
{ name: 'Challenge', abbreviation: 'CH' },
{ name: 'Marathon Match', abbreviation: 'MM' },
{ name: 'Task', abbreviation: 'TSK' },
{ name: 'First2Finish', abbreviation: 'F2F' },
{ name: 'type-1778748614529', abbreviation: 'type-1778748614529' },
];

expect(getVisibleChallengeTypes(challengeTypes)).toEqual([
{ name: 'Challenge', abbreviation: 'CH' },
{ name: 'First2Finish', abbreviation: 'F2F' },
{ name: 'Marathon Match', abbreviation: 'MM' },
{ name: 'Task', abbreviation: 'TSK' },
]);
});

test('removes hidden and duplicated selected challenge type filters', () => {
expect(sanitizeChallengeTypeFilter([
'AI',
'CH',
'F2F',
'MM',
'MM',
'TSK',
'type-1778748614529',
])).toEqual(['CH', 'F2F', 'MM', 'TSK']);
});
});
114 changes: 113 additions & 1 deletion __tests__/shared/utils/mm-review-summations.test.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
/* eslint-env jest */
import { buildMmSubmissionData } from '../../../src/shared/utils/mm-review-summations';
import {
buildMmSubmissionData,
buildStatisticsData,
} from '../../../src/shared/utils/mm-review-summations';

describe('buildMmSubmissionData', () => {
it('keeps newer raw submissions that do not have review summations yet', () => {
Expand Down Expand Up @@ -280,3 +283,112 @@ describe('buildMmSubmissionData', () => {
]);
});
});

describe('buildStatisticsData', () => {
it('omits processing and failed scorer updates from dashboard graph data', () => {
const reviewSummations = [
{
aggregateScore: 0,
id: 'summation-processing-zero',
isProvisional: true,
metadata: {
testStatus: 'IN PROGRESS',
testType: 'provisional',
},
reviewedDate: '2026-05-29T01:00:00.000Z',
submissionId: 'submission-processing',
submitterHandle: 'alpha',
submitterId: '1001',
},
{
aggregateScore: -1,
id: 'summation-failed',
isProvisional: true,
metadata: {
testStatus: 'FAILED',
testType: 'provisional',
},
reviewedDate: '2026-05-29T01:05:00.000Z',
submissionId: 'submission-failed',
submitterHandle: 'alpha',
submitterId: '1001',
},
{
aggregateScore: 0,
id: 'summation-success-zero',
isProvisional: true,
metadata: {
testStatus: 'SUCCESS',
testType: 'provisional',
},
reviewedDate: '2026-05-29T01:10:00.000Z',
submissionId: 'submission-success-zero',
submitterHandle: 'beta',
submitterId: '1002',
},
{
aggregateScore: 84.25,
id: 'summation-success',
isProvisional: true,
metadata: {
testStatus: 'SUCCESS',
testType: 'provisional',
},
reviewedDate: '2026-05-29T01:15:00.000Z',
submissionId: 'submission-success',
submitterHandle: 'alpha',
submitterId: '1001',
},
];

const result = buildStatisticsData(reviewSummations);

expect(result).toHaveLength(2);
expect(result).toEqual(expect.arrayContaining([
expect.objectContaining({
handle: 'alpha',
submissions: [
expect.objectContaining({
score: 84.25,
submissionId: 'submission-success',
}),
],
}),
expect.objectContaining({
handle: 'beta',
submissions: [
expect.objectContaining({
score: 0,
submissionId: 'submission-success-zero',
}),
],
}),
]));
});

it('keeps legacy non-negative summations when scorer status metadata is absent', () => {
const result = buildStatisticsData([
{
aggregateScore: 72.5,
id: 'summation-legacy',
isProvisional: true,
reviewedDate: '2026-05-29T02:00:00.000Z',
submissionId: 'submission-legacy',
submitterHandle: 'gamma',
submitterId: '1003',
},
]);

expect(result).toEqual([
expect.objectContaining({
handle: 'gamma',
submissions: [
expect.objectContaining({
score: 72.5,
submissionId: 'submission-legacy',
}),
],
}),
]);
});
});
35 changes: 35 additions & 0 deletions __tests__/shared/utils/terms.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import {
getDocuSignTemplateIdForTerm,
isNdaTerm,
} from 'utils/terms';

describe('terms utils', () => {
const NEW_NDA_TEMPLATE_ID = '400b989d-1c75-4889-b6f6-421e1f924709';

test('detects NDA terms by title', () => {
expect(isNdaTerm({ title: 'Appirio NDA v2.0' })).toBe(true);
expect(isNdaTerm({ title: 'Competition Non-Disclosure Agreement' })).toBe(true);
expect(isNdaTerm({ title: 'Assignment Terms' })).toBe(false);
});

test('uses configured DocuSign template for NDA terms', () => {
expect(getDocuSignTemplateIdForTerm({
docusignTemplateId: 'old-template-id',
title: 'Appirio NDA v2.0',
})).toBe(NEW_NDA_TEMPLATE_ID);
});

test('keeps terms-service template for non-NDA terms', () => {
expect(getDocuSignTemplateIdForTerm({
docusignTemplateId: 'assignment-template-id',
title: 'Assignment Terms',
})).toBe('assignment-template-id');
});

test('handles missing terms details', () => {
expect(isNdaTerm(null)).toBe(false);
expect(isNdaTerm()).toBe(false);
expect(getDocuSignTemplateIdForTerm(null)).toBe(undefined);
expect(getDocuSignTemplateIdForTerm()).toBe(undefined);
});
});
6 changes: 6 additions & 0 deletions config/backup-default.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,12 @@ module.exports = {
* agreement flow. */
MOCK_TERMS_SERVICE: false,

/* Optional DocuSign template override for NDA-like terms. When set, the
* terms modal requests this template for terms whose title includes NDA or
* Non-Disclosure, even if the Terms API still returns an older template id.
*/
NDA_DOCUSIGN_TEMPLATE_ID: '400b989d-1c75-4889-b6f6-421e1f924709',

/* Holds params to signup for different newsletters. */
NEWSLETTER_SIGNUP: {
DEFAUL_LIST_ID: '28bfd3c062',
Expand Down
1 change: 1 addition & 0 deletions config/custom-environment-variables.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ module.exports = {
DISABLE_SERVICE_WORKER: 'DISABLE_SERVICE_WORKER',
LOG_ENTRIES_TOKEN: 'LOG_ENTRIES_TOKEN',
MOCK_TERMS_SERVICE: 'MOCK_TERMS_SERVICE',
NDA_DOCUSIGN_TEMPLATE_ID: 'NDA_DOCUSIGN_TEMPLATE_ID',

NEWSLETTER_SIGNUP: {
COGNITIVE: {
Expand Down
6 changes: 6 additions & 0 deletions config/default.js
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,12 @@ module.exports = {
* agreement flow. */
MOCK_TERMS_SERVICE: false,

/* Optional DocuSign template override for NDA-like terms. When set, the
* terms modal requests this template for terms whose title includes NDA or
* Non-Disclosure, even if the Terms API still returns an older template id.
*/
NDA_DOCUSIGN_TEMPLATE_ID: '400b989d-1c75-4889-b6f6-421e1f924709',

/* Holds params to signup for different newsletters. */
NEWSLETTER_SIGNUP: {
DEFAUL_LIST_ID: '28bfd3c062',
Expand Down
1 change: 1 addition & 0 deletions config/production.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ module.exports = {
SECURE: true,
},
LOG_ENTRIES_TOKEN: '',
NDA_DOCUSIGN_TEMPLATE_ID: '8b101e82-87c0-42c9-8440-d922749c4076',
SERVER_API_KEY: 'aa9ccf36-3936-450c-9983-097ddba51bef',
GOOGLE_ANALYTICS_ID: 'UA-6340959-1',
URL: {
Expand Down
1 change: 1 addition & 0 deletions config/qa.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
module.exports = {
SEGMENT_IO_API_KEY: 'QBtLgV8vCiuRX1lDikbMjcoe9aCHkF6n',
NDA_DOCUSIGN_TEMPLATE_ID: '',
SERVER_API_KEY: '79b2d5eb-c1fd-42c4-9391-6b2c9780d591',
API: {
ENGAGEMENTS: 'https://api.topcoder-qa.com/v6/engagements/engagements',
Expand Down
16 changes: 10 additions & 6 deletions src/shared/components/Terms/TermDetails.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import React from 'react';
import PT from 'prop-types';
import LoadingIndicator from 'components/LoadingIndicator';
import { getDocuSignTemplateIdForTerm } from 'utils/terms';

import './TermDetails.scss';

Expand All @@ -20,8 +21,9 @@ export default class TermDetails extends React.Component {

componentWillMount() {
const { details, getDocuSignUrl } = this.props;
if (details.agreeabilityType !== 'Electronically-agreeable' && details.docusignTemplateId) {
getDocuSignUrl(details.docusignTemplateId);
const docusignTemplateId = getDocuSignTemplateIdForTerm(details);
if (docusignTemplateId) {
getDocuSignUrl(docusignTemplateId);
this.setState({ loadingFrame: true });
}
}
Expand All @@ -38,11 +40,14 @@ export default class TermDetails extends React.Component {
loadingDocuSignUrl,
} = this.props;
const { loadingFrame } = this.state;
const docusignTemplateId = getDocuSignTemplateIdForTerm(details);
const isDocuSignTerm = Boolean(docusignTemplateId);

return (
<div>
{
details.agreeabilityType === 'Electronically-agreeable'
&& !isDocuSignTerm
&& (
<div>
<div
Expand All @@ -53,13 +58,12 @@ export default class TermDetails extends React.Component {
)
}
{
details.agreeabilityType !== 'Electronically-agreeable'
&& details.docusignTemplateId === loadingDocuSignUrl
isDocuSignTerm
&& `${docusignTemplateId}` === loadingDocuSignUrl
&& <LoadingIndicator />
}
{
details.agreeabilityType !== 'Electronically-agreeable' && details.docusignTemplateId
&& !loadingDocuSignUrl && docuSignUrl
isDocuSignTerm && !loadingDocuSignUrl && docuSignUrl
&& (
<div>
{
Expand Down
5 changes: 4 additions & 1 deletion src/shared/components/Terms/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import PT from 'prop-types';
import { Modal, PrimaryButton, Button } from 'topcoder-react-ui-kit';
import LoadingIndicator from 'components/LoadingIndicator';
import FocusTrap from 'focus-trap-react';
import { getDocuSignTemplateIdForTerm } from 'utils/terms';
import TermDetails from './TermDetails';

import style from './styles.scss';
Expand Down Expand Up @@ -139,6 +140,7 @@ export default class Terms extends React.Component {
loadingDocuSignUrl, selectedTerm, viewOnly, checkingStatus,
description, defaultTitle,
} = this.props;
const isDocuSignTerm = Boolean(getDocuSignTemplateIdForTerm(details));

const handleHorizonalScroll = (e) => {
const scrollElement = e.target;
Expand Down Expand Up @@ -283,7 +285,8 @@ export default class Terms extends React.Component {
!isLoadingTerms && !checkingStatus && selectedTerm && details
&& !viewOnly
&& loadingTermId !== _.toString(selectedTerm.id)
&& details.agreeabilityType === 'Electronically-agreeable' ? (
&& details.agreeabilityType === 'Electronically-agreeable'
&& !isDocuSignTerm ? (
<div styleName="buttons">
{
selectedTerm.agreed
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@

.container {
:global {
ul,
ol {
padding-left: 20px;
}

table {
display: block;
width: 100%;
Expand Down
23 changes: 11 additions & 12 deletions src/shared/containers/challenge-listing/FilterPanel.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,10 @@ import { connect } from 'react-redux';
import qs from 'qs';
import _ from 'lodash';
import { createStaticRanges } from 'utils/challenge-listing/date-range';
import { EXCLUDED_CHALLENGE_TYPE_NAMES } from 'utils/challenge-listing/constants';
import {
getVisibleChallengeTypes,
sanitizeChallengeTypeFilter,
} from 'utils/challenge-listing/constants';

const MIN = 60 * 1000;

Expand Down Expand Up @@ -74,7 +77,9 @@ export class Container extends React.Component {
query.customDate = customDate;
}

if (query.types && query.types.length) {
if (query.types && sanitizeChallengeTypeFilter(
Array.isArray(query.types) ? query.types : [query.types],
).length) {
this.initialDefaultChallengeTypes = true;
}

Expand Down Expand Up @@ -105,8 +110,7 @@ export class Container extends React.Component {
});
this.initialDefaultChallengeTypes = true;
} else if (validTypes.length && currentTypes.length) {
const validAbbreviations = validTypes.map(item => item.abbreviation);
const sanitizedTypes = currentTypes.filter(type => validAbbreviations.includes(type));
const sanitizedTypes = sanitizeChallengeTypeFilter(currentTypes);
if (sanitizedTypes.length !== currentTypes.length) {
if (!sanitizedTypes.length) {
this.initialDefaultChallengeTypes = false;
Expand Down Expand Up @@ -235,16 +239,11 @@ function mapDispatchToProps(dispatch) {
function mapStateToProps(state, ownProps) {
const cl = state.challengeListing;
const tc = state.tcCommunities;
const filteredChallengeTypes = cl.challengeTypes
.filter(type => !EXCLUDED_CHALLENGE_TYPE_NAMES.includes(type.name));
const excludedTypeAbbreviations = cl.challengeTypes
.filter(type => EXCLUDED_CHALLENGE_TYPE_NAMES.includes(type.name))
.map(type => type.abbreviation);
const filteredChallengeTypes = getVisibleChallengeTypes(cl.challengeTypes);
let filterState = cl.filter;
const existingTypes = Array.isArray(cl.filter.types) ? cl.filter.types : [];
if (excludedTypeAbbreviations.length && existingTypes.length) {
const sanitizedTypes = existingTypes
.filter(type => !excludedTypeAbbreviations.includes(type));
if (existingTypes.length) {
const sanitizedTypes = sanitizeChallengeTypeFilter(existingTypes);
if (sanitizedTypes.length !== existingTypes.length) {
filterState = {
...cl.filter,
Expand Down
Loading
Loading