From 2e2c758bd29cbd9e7538bc3e49e31b5d0cd8b3f9 Mon Sep 17 00:00:00 2001 From: liuliquan Date: Thu, 19 Oct 2023 06:31:49 -0500 Subject: [PATCH 1/4] feat: add skills to challenge, and search via skills (#678) * feat: add skills to challenge * feat: search challenges by skills * ci: use DEV for CODEARTIFACT_ENV * ci: use DEV for CODEARTIFACT_ENV * feat: search challenges by skills, use same boost as tags * feat: Don't allow edit skills for Completed challenges * feat: Don't allow edit skills for Completed challenges * feat: versioned responses via transformer * ci: disable PLAT-3614 deploy * feat: remove request fields via transformer * ci: deploy PLAT-3614 --- .circleci/config.yml | 2 +- package.json | 5 +- src/common/challenge-helper.js | 62 ++++++++++++++++++ src/common/helper.js | 25 +++++++- src/common/transformer.js | 88 ++++++++++++++++++++++++++ src/controllers/ChallengeController.js | 19 +++--- src/services/ChallengeService.js | 39 ++++++++++-- yarn.lock | 48 ++++++++------ 8 files changed, 250 insertions(+), 38 deletions(-) create mode 100644 src/common/transformer.js diff --git a/.circleci/config.yml b/.circleci/config.yml index f01686dc..61542d09 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -90,7 +90,7 @@ workflows: branches: only: - dev - - PLAT-3368 + - PLAT-3614 - "build-qa": context: org-global diff --git a/package.json b/package.json index 6a8db892..fe6a9089 100644 --- a/package.json +++ b/package.json @@ -42,13 +42,14 @@ "dependencies": { "@grpc/grpc-js": "^1.8.12", "@opensearch-project/opensearch": "^2.2.0", - "@topcoder-framework/domain-challenge": "^0.23.0", - "@topcoder-framework/lib-common": "^0.23.0", + "@topcoder-framework/domain-challenge": "^v0.23.1-PLAT-3614.0", + "@topcoder-framework/lib-common": "^v0.23.1-PLAT-3614.0", "aws-sdk": "^2.1145.0", "axios": "^0.19.0", "axios-retry": "^3.4.0", "bluebird": "^3.5.1", "body-parser": "^1.15.1", + "compare-versions": "^6.1.0", "config": "^3.0.1", "cors": "^2.8.5", "decimal.js": "^10.4.3", diff --git a/src/common/challenge-helper.js b/src/common/challenge-helper.js index 6ee91781..4bca0223 100644 --- a/src/common/challenge-helper.js +++ b/src/common/challenge-helper.js @@ -99,6 +99,55 @@ class ChallengeHelper { await Promise.all(promises); } + /** + * Validate Challenge skills. + * @param {Object} challenge the challenge + * @param {oldChallenge} challenge the old challenge data + */ + async validateSkills(challenge, oldChallenge) { + if (!challenge.skills) { + return; + } + + const ids = _.uniq(_.map(challenge.skills, "id")); + + if (oldChallenge && oldChallenge.status === constants.challengeStatuses.Completed) { + // Don't allow edit skills for Completed challenges + if (!_.isEqual(ids, _.uniq(_.map(oldChallenge.skills, "id")))) { + throw new errors.BadRequestError("Cannot update skills for challenges with Completed status"); + } + } + + if (!ids.length) { + return; + } + + const standSkills = await helper.getStandSkills(ids); + + const skills = []; + for (const id of ids) { + const found = _.find(standSkills, (item) => item.id === id); + if (!found) { + throw new errors.BadRequestError("The skill id is invalid " + id); + } + + const skill = { + id, + name: found.name, + }; + + if (found.category) { + skill.category = { + id: found.category.id, + name: found.category.name, + }; + } + + skills.push(skill); + } + challenge.skills = skills; + } + async validateCreateChallengeRequest(currentUser, challenge) { // projectId is required for non self-service challenges if (challenge.legacy.selfService == null && challenge.projectId == null) { @@ -125,6 +174,9 @@ class ChallengeHelper { } } + // check skills + await this.validateSkills(challenge); + if (challenge.constraints) { await ChallengeHelper.validateChallengeConstraints(challenge.constraints); } @@ -151,6 +203,9 @@ class ChallengeHelper { } } + // check skills + await this.validateSkills(data, challenge); + // Ensure descriptionFormat is either 'markdown' or 'html' if (data.descriptionFormat && !_.includes(["markdown", "html"], data.descriptionFormat)) { throw new errors.BadRequestError( @@ -328,6 +383,13 @@ class ChallengeHelper { delete data.groups; } + if (data.skills != null) { + data.skillUpdate = { + skills: data.skills, + }; + delete data.skills; + } + return data; } diff --git a/src/common/helper.js b/src/common/helper.js index 46db3d85..b3333693 100644 --- a/src/common/helper.js +++ b/src/common/helper.js @@ -992,7 +992,11 @@ async function ensureUserCanModifyChallenge(currentUser, challenge, challengeRes // check groups authorization await ensureAccessibleByGroupsAccess(currentUser, challenge); // check full access - const isUserHasFullAccess = await userHasFullAccess(challenge.id, currentUser.userId, challengeResources); + const isUserHasFullAccess = await userHasFullAccess( + challenge.id, + currentUser.userId, + challengeResources + ); if ( !currentUser.isMachine && !hasAdminRole(currentUser) && @@ -1129,6 +1133,24 @@ async function getMembersByHandles(handles) { return res.data; } +/** + * Get standard skills by ids + * @param {Array} ids the skills ids + * @returns {Object} + */ +async function getStandSkills(ids) { + const token = await m2mHelper.getM2MToken(); + const res = await axios.get(`${config.API_BASE_URL}/v5/standardized-skills/skills`, { + headers: { Authorization: `Bearer ${token}` }, + params: { + page: 1, + perPage: ids.length, + skillId: ids, + }, + }); + return res.data; +} + /** * Send self service notification * @param {String} type the notification type @@ -1251,6 +1273,7 @@ module.exports = { sendSelfServiceNotification, getMemberByHandle, getMembersByHandles, + getStandSkills, submitZendeskRequest, updateSelfServiceProjectInfo, getFromInternalCache, diff --git a/src/common/transformer.js b/src/common/transformer.js new file mode 100644 index 00000000..14b6963f --- /dev/null +++ b/src/common/transformer.js @@ -0,0 +1,88 @@ +const _ = require("lodash"); +const { compareVersions } = require("compare-versions"); +const challengeService = require("../services/ChallengeService"); + +function transformData(data, fieldsToDelete) { + if (!fieldsToDelete || !fieldsToDelete.length) { + return data; + } + + if (_.isArray(data)) { + _.each(data, (item, index) => { + data[index] = transformData(item, fieldsToDelete); + }); + } else if (_.isObject(data)) { + for (const field of fieldsToDelete) { + delete data[field]; + } + if (data.result) { + data.result = transformData(data.result, fieldsToDelete); + } + } + + return data; +} + +function transformServices() { + _.each(services, (service, serviceName) => { + const serviceConfig = servicesConfig[serviceName]; + if (!serviceConfig) { + return; + } + + _.each(service, (method, methodName) => { + service[methodName] = async function () { + const args = Array.prototype.slice.call(arguments); + + // No transform need for this method + if (!serviceConfig.methods.includes(methodName)) { + return await method.apply(this, args.slice(1)); + } + + // args[0] is request, get version header + const request = args[0]; + const apiVersion = request.headers["challenge-api-version"] || "1.0.0"; + + const fieldsToDelete = []; + _.each(serviceConfig.fieldsVersion, (version, field) => { + // If input version less than required version, delete fields + if (compareVersions(apiVersion, version) < 0) { + fieldsToDelete.push(field); + } + }); + + // Transform request body by deleting fields + if (_.isArray(request.body) || _.isObject(request.body)) { + transformData(request.body, fieldsToDelete); + } + + const data = await method.apply(this, args.slice(1)); + + // Transform response data by deleting fields + return transformData(data, fieldsToDelete); + }; + service[methodName].params = ["req", ...method.params]; + }); + }); +} + +// Define the version config for services +const servicesConfig = { + challengeService: { + methods: ["searchChallenges", "getChallenge", "createChallenge", "updateChallenge"], + fieldsVersion: { + skills: "1.1.0", + payments: "2.0.0", + }, + }, +}; + +// Define the services to export +const services = { + challengeService, +}; + +// Transform services before export +transformServices(); + +module.exports = services; diff --git a/src/controllers/ChallengeController.js b/src/controllers/ChallengeController.js index fcf61e56..18c61937 100644 --- a/src/controllers/ChallengeController.js +++ b/src/controllers/ChallengeController.js @@ -2,7 +2,7 @@ * Controller for challenge endpoints */ const HttpStatus = require("http-status-codes"); -const service = require("../services/ChallengeService"); +const { challengeService: service } = require("../common/transformer"); const helper = require("../common/helper"); const logger = require("../common/logger"); @@ -12,7 +12,7 @@ const logger = require("../common/logger"); * @param {Object} res the response */ async function searchChallenges(req, res) { - let result = await service.searchChallenges(req.authUser, { + let result = await service.searchChallenges(req, req.authUser, { ...req.query, ...req.body, }); @@ -23,7 +23,7 @@ async function searchChallenges(req, res) { logger.debug(`Staring to get mm challengeId`); const legacyId = await helper.getProjectIdByRoundId(req.query.legacyId); logger.debug(`Get mm challengeId successfully ${legacyId}`); - result = await service.searchChallenges(req.authUser, { + result = await service.searchChallenges(req, req.authUser, { ...req.query, ...req.body, legacyId, @@ -50,7 +50,7 @@ async function createChallenge(req, res) { logger.debug( `createChallenge User: ${JSON.stringify(req.authUser)} - Body: ${JSON.stringify(req.body)}` ); - const result = await service.createChallenge(req.authUser, req.body, req.userToken); + const result = await service.createChallenge(req, req.authUser, req.body, req.userToken); res.status(HttpStatus.CREATED).send(result); } @@ -60,7 +60,7 @@ async function createChallenge(req, res) { * @param {Object} res the response */ async function sendNotifications(req, res) { - const result = await service.sendNotifications(req.authUser, req.params.challengeId); + const result = await service.sendNotifications(req, req.authUser, req.params.challengeId); res.status(HttpStatus.CREATED).send(result); } @@ -71,6 +71,7 @@ async function sendNotifications(req, res) { */ async function getChallenge(req, res) { const result = await service.getChallenge( + req, req.authUser, req.params.challengeId, req.query.checkIfExists @@ -84,7 +85,7 @@ async function getChallenge(req, res) { * @param {Object} res the response */ async function getChallengeStatistics(req, res) { - const result = await service.getChallengeStatistics(req.authUser, req.params.challengeId); + const result = await service.getChallengeStatistics(req, req.authUser, req.params.challengeId); res.send(result); } @@ -99,7 +100,7 @@ async function updateChallenge(req, res) { req.params.challengeId } - Body: ${JSON.stringify(req.body)}` ); - const result = await service.updateChallenge(req.authUser, req.params.challengeId, req.body); + const result = await service.updateChallenge(req, req.authUser, req.params.challengeId, req.body); res.send(result); } @@ -112,7 +113,7 @@ async function deleteChallenge(req, res) { logger.debug( `deleteChallenge User: ${JSON.stringify(req.authUser)} - ChallengeID: ${req.params.challengeId}` ); - const result = await service.deleteChallenge(req.authUser, req.params.challengeId); + const result = await service.deleteChallenge(req, req.authUser, req.params.challengeId); res.send(result); } @@ -122,7 +123,7 @@ async function deleteChallenge(req, res) { * @param {Object} res the response */ async function advancePhase(req, res) { - res.send(await service.advancePhase(req.authUser, req.params.challengeId, req.body)); + res.send(await service.advancePhase(req, req.authUser, req.params.challengeId, req.body)); } module.exports = { diff --git a/src/services/ChallengeService.js b/src/services/ChallengeService.js index 469d8806..35f3d7d4 100644 --- a/src/services/ChallengeService.js +++ b/src/services/ChallengeService.js @@ -244,7 +244,7 @@ async function searchChallenges(currentUser, criteria) { // exact match multi_match: { query: criteria.search, - fields: ["name.text^7", "tags^3", "description^2"], + fields: ["name.text^7", "tags^3", "skills.name^3", "description^2"], type: "phrase_prefix", boost: 5, }, @@ -253,7 +253,7 @@ async function searchChallenges(currentUser, criteria) { // match 100% words multi_match: { query: criteria.search, - fields: ["name.text^3.0", "tags^2.5", "description^1.0"], + fields: ["name.text^3.0", "tags^2.5", "skills.name^2.5", "description^1.0"], type: "most_fields", minimum_should_match: "100%", boost: 2.5, @@ -263,7 +263,7 @@ async function searchChallenges(currentUser, criteria) { // fuzzy match multi_match: { query: criteria.search, - fields: ["name.text^2.5", "tags^1.5", "description^1.0"], + fields: ["name.text^2.5", "tags^1.5", "skills.name^1.5", "description^1.0"], type: "most_fields", minimum_should_match: "50%", fuzziness: "AUTO", @@ -277,6 +277,7 @@ async function searchChallenges(currentUser, criteria) { { wildcard: { name: `${criteria.search}*` } }, { wildcard: { name: `*${criteria.search}` } }, { match_phrase: { tags: criteria.search } }, + { match_phrase: { "skills.name": criteria.search } }, ], }, }); @@ -1036,6 +1037,7 @@ async function createChallenge(currentUser, challenge, userToken) { if (challenge.startDate != null) challenge.startDate = challenge.startDate; if (challenge.endDate != null) challenge.endDate = challenge.endDate; if (challenge.discussions == null) challenge.discussions = []; + if (challenge.skills == null) challenge.skills = []; challenge.metadata = challenge.metadata.map((m) => ({ name: m.name, @@ -1203,6 +1205,15 @@ createChallenge.schema = { roleId: Joi.id(), }) ), + skills: Joi.array() + .items( + Joi.object() + .keys({ + id: Joi.id(), + }) + .unknown(true) + ) + .optional(), }) .required(), userToken: Joi.string().required(), @@ -1490,7 +1501,12 @@ async function updateChallenge(currentUser, challengeId, data) { const challengeResources = await helper.getChallengeResources(challengeId); - await challengeHelper.validateChallengeUpdateRequest(currentUser, challenge, data, challengeResources); + await challengeHelper.validateChallengeUpdateRequest( + currentUser, + challenge, + data, + challengeResources + ); validateTask(currentUser, challenge, data, challengeResources); let sendActivationEmail = false; @@ -2102,6 +2118,15 @@ updateChallenge.schema = { roleId: Joi.id(), }) ), + skills: Joi.array() + .items( + Joi.object() + .keys({ + id: Joi.id(), + }) + .unknown(true) + ) + .optional(), overview: Joi.any().forbidden(), }) .unknown(true) @@ -2157,6 +2182,7 @@ function sanitizeChallenge(challenge) { "groups", "cancelReason", "constraints", + "skills", ]); if (!_.isUndefined(sanitized.name)) { sanitized.name = xss(sanitized.name); @@ -2233,6 +2259,11 @@ function sanitizeData(data, challenge) { if (key === "phases") continue; if (challenge.hasOwnProperty(key)) { + if (key === "skills" && deepEqual(_.map(data.skills, "id"), _.map(challenge.skills, "id"))) { + delete data[key]; + continue; + } + if ( (typeof data[key] === "object" || Array.isArray(data[key])) && deepEqual(data[key], challenge[key]) diff --git a/yarn.lock b/yarn.lock index a52ead2b..eb97ec23 100644 --- a/yarn.lock +++ b/yarn.lock @@ -255,35 +255,35 @@ resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-2.0.0.tgz#f544a148d3ab35801c1f633a7441fd87c2e484bf" integrity sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A== -"@topcoder-framework/client-relational@^0.23.0": - version "0.23.0" - resolved "https://topcoder-409275337247.d.codeartifact.us-east-1.amazonaws.com/npm/topcoder-framework/@topcoder-framework/client-relational/-/client-relational-0.23.0.tgz#c15d102c47044e23df0d1823b5eec898d8a8bb15" - integrity sha512-/0K4TRTHCe3bRUM5H1wYlPbKZazg/PJNfHkb07i1tyi77YiH4YT8zgW9A23SAoW4/k3xsveSkI8U+YbmIqnALw== +"@topcoder-framework/client-relational@^0.23.1-PLAT-3614.0": + version "0.23.1-PLAT-3614.0" + resolved "https://topcoder-409275337247.d.codeartifact.us-east-1.amazonaws.com/npm/topcoder-framework/@topcoder-framework/client-relational/-/client-relational-0.23.1-PLAT-3614.0.tgz#1f658d534ab0e1ba0ca93f5c73980d27e678cdeb" + integrity sha512-iCKPgDZvefywhUjPai5yadlcZua52Tl6KaVFSrmAaXEODBP8KeqJAlpT73bQqBdt/PqVSkvUUiEo4Xt9QU7Z/w== dependencies: "@grpc/grpc-js" "^1.8.0" - "@topcoder-framework/lib-common" "^0.23.0" - topcoder-interface "github:topcoder-platform/plat-interface-definition#v0.0.58-beta-1" + "@topcoder-framework/lib-common" "^0.23.1-PLAT-3614.0" + topcoder-interface "0.1.0-PLAT-3614.1" tslib "^2.4.1" -"@topcoder-framework/domain-challenge@^0.23.0": - version "0.23.0" - resolved "https://topcoder-409275337247.d.codeartifact.us-east-1.amazonaws.com/npm/topcoder-framework/@topcoder-framework/domain-challenge/-/domain-challenge-0.23.0.tgz#31964a09864afb0f4969ea22addeb89ba47d9689" - integrity sha512-IRLM18PCekatQsea1pzfZWGiiO3+8vxQrM3BmWGGpls2BAOIrS+/y1MLJDM23DasXEmMOz+zFQe939VQyrZ7Qw== +"@topcoder-framework/domain-challenge@^v0.23.1-PLAT-3614.0": + version "0.23.1-PLAT-3614.0" + resolved "https://topcoder-409275337247.d.codeartifact.us-east-1.amazonaws.com/npm/topcoder-framework/@topcoder-framework/domain-challenge/-/domain-challenge-0.23.1-PLAT-3614.0.tgz#3054b5b5610a039bdbd08e137144325f98a53f5c" + integrity sha512-HK3WDtnE3IvygBrMX6FlP4dsZlx2xqs48/T4G1beH39w4Wo7tv3yvGgzCHgQt+nyzuEwMk+iQDsl7OkXFXZYhg== dependencies: "@grpc/grpc-js" "^1.8.0" - "@topcoder-framework/client-relational" "^0.23.0" - "@topcoder-framework/lib-common" "^0.23.0" - topcoder-interface "github:topcoder-platform/plat-interface-definition#v0.0.58-beta-1" + "@topcoder-framework/client-relational" "^0.23.1-PLAT-3614.0" + "@topcoder-framework/lib-common" "^0.23.1-PLAT-3614.0" + topcoder-interface "0.1.0-PLAT-3614.1" tslib "^2.4.1" -"@topcoder-framework/lib-common@^0.23.0": - version "0.23.0" - resolved "https://topcoder-409275337247.d.codeartifact.us-east-1.amazonaws.com/npm/topcoder-framework/@topcoder-framework/lib-common/-/lib-common-0.23.0.tgz#01fb1e9c2a32760387f3444ce991b9164c75f577" - integrity sha512-burWJaxo/rt1KtqxTlXRenG4qUK1QioSq8S7ouq6pUlhC1vyJSiBCum7QWR6E9H/sWit8wRl+gZBMEVVJVHbHQ== +"@topcoder-framework/lib-common@^0.23.1-PLAT-3614.0", "@topcoder-framework/lib-common@^v0.23.1-PLAT-3614.0": + version "0.23.1-PLAT-3614.0" + resolved "https://topcoder-409275337247.d.codeartifact.us-east-1.amazonaws.com/npm/topcoder-framework/@topcoder-framework/lib-common/-/lib-common-0.23.1-PLAT-3614.0.tgz#087d9abda8671e1ef3116af7f6fb5911f08d81c3" + integrity sha512-+ynrGjT7hnZ9fKudqPwyYx1v7e1YHjS1nfUFfeFW4Bvj7a3aAlCLeL6ElMHgquMndea4pmOYlUpGxdqbRnce+A== dependencies: "@grpc/grpc-js" "^1.8.0" rimraf "^3.0.2" - topcoder-interface "github:topcoder-platform/plat-interface-definition#v0.0.58-beta-1" + topcoder-interface "0.1.0-PLAT-3614.1" tslib "^2.4.1" "@types/body-parser@*": @@ -978,6 +978,11 @@ commondir@^1.0.1: resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b" integrity sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg== +compare-versions@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/compare-versions/-/compare-versions-6.1.0.tgz#3f2131e3ae93577df111dba133e6db876ffe127a" + integrity sha512-LNZQXhqUvqUTotpZ00qLSaify3b4VFD588aRr8MKFw4CMUr98ytzCW5wDH5qx/DEY5kCDXcbcRuCqL0szEf2tg== + component-emitter@^1.2.0, component-emitter@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.3.0.tgz#16e4070fba8ae29b679f2215853ee181ab2eabc0" @@ -3999,9 +4004,10 @@ topcoder-bus-api-wrapper@topcoder-platform/tc-bus-api-wrapper.git: superagent "^3.8.3" tc-core-library-js appirio-tech/tc-core-library-js.git#v2.6.4 -"topcoder-interface@github:topcoder-platform/plat-interface-definition#v0.0.58-beta-1": - version "1.0.0" - resolved "https://codeload.github.com/topcoder-platform/plat-interface-definition/tar.gz/474bcfa1d01f0f2d0a2658de21aa835f4c824c44" +topcoder-interface@0.1.0-PLAT-3614.1: + version "0.1.0-PLAT-3614.1" + resolved "https://registry.yarnpkg.com/topcoder-interface/-/topcoder-interface-0.1.0-PLAT-3614.1.tgz#80459431ffa97843f3004b9b700b523eb9681574" + integrity sha512-5KlsAqjBEBwPDtvQFsyZP00y2+FfftX6mbywf3cd214Zx2nrk2aLJXoYrMi5OQaNuYlYcCyb0kY9dqtl1qxsog== topo@3.x.x: version "3.0.3" From 34565297290216a1063ae3be6af9ecdbd6d8addc Mon Sep 17 00:00:00 2001 From: eisbilir Date: Wed, 1 Nov 2023 14:31:47 +0300 Subject: [PATCH 2/4] use groups keyword to filter by groups --- .circleci/config.yml | 1 + src/services/ChallengeService.js | 8 ++------ 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 61542d09..18ae8c04 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -91,6 +91,7 @@ workflows: only: - dev - PLAT-3614 + - feat/group-search - "build-qa": context: org-global diff --git a/src/services/ChallengeService.js b/src/services/ChallengeService.js index 35f3d7d4..712df13b 100644 --- a/src/services/ChallengeService.js +++ b/src/services/ChallengeService.js @@ -533,16 +533,12 @@ async function searchChallenges(currentUser, criteria) { mustNotQuery.push({ exists: { field: "groups" } }); } else if (!currentUser.isMachine && !_hasAdminRole) { // If the user is not M2M and is not an admin, return public + challenges from groups the user can access - _.each(accessibleGroups, (g) => { - groupsQuery.push({ match_phrase: { groups: g } }); - }); + groupsQuery.push({ terms: { "groups.keyword": accessibleGroups } }); // include public challenges groupsQuery.push({ bool: { must_not: { exists: { field: "groups" } } } }); } } else { - _.each(groupsToFilter, (g) => { - groupsQuery.push({ match_phrase: { groups: g } }); - }); + groupsQuery.push({ terms: { "groups.keyword": groupsToFilter } }); } if (criteria.ids) { From b6adf2dd0a4aa83df6e59d3f324426cec80d5922 Mon Sep 17 00:00:00 2001 From: Justin Gasper Date: Thu, 2 Nov 2023 07:11:39 +1100 Subject: [PATCH 3/4] =?UTF-8?q?Don=E2=80=99t=20require=20tags=20now=20that?= =?UTF-8?q?=20we=20have=20split=20out=20tags=20and=20skills?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit https://topcoder.atlassian.net/browse/PROD-4410 --- src/services/ChallengeService.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/services/ChallengeService.js b/src/services/ChallengeService.js index 712df13b..47616a6d 100644 --- a/src/services/ChallengeService.js +++ b/src/services/ChallengeService.js @@ -2073,7 +2073,7 @@ updateChallenge.schema = { .unknown(true) ) .min(1), - tags: Joi.array().items(Joi.string().required()).min(1), // tag names + tags: Joi.array().items(Joi.string()), // tag names projectId: Joi.number().integer().positive(), legacyId: Joi.number().integer().positive(), constraints: Joi.object() From 747e4f0ff028651422aea51a541e09ce599e7143 Mon Sep 17 00:00:00 2001 From: Justin Gasper Date: Thu, 9 Nov 2023 08:50:05 +1100 Subject: [PATCH 4/4] Fix circleci conflict --- .circleci/config.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 18ae8c04..61542d09 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -91,7 +91,6 @@ workflows: only: - dev - PLAT-3614 - - feat/group-search - "build-qa": context: org-global