diff --git a/src/config.js b/src/config.js
index 4489194..599dc29 100644
--- a/src/config.js
+++ b/src/config.js
@@ -84,4 +84,13 @@ module.exports.frontendConfigs = {
GITHUB_TEAM_URL: process.env.GITHUB_TEAM_URL || 'https://github.com/orgs/',
GITLAB_GROUP_URL: process.env.GITLAB_GROUP_URL || 'https://gitlab.com/groups/',
TC_API_V5_URL: process.env.TC_API_V5_URL || 'https://api.topcoder-dev.com/v5',
+ TC_API_V4_URL: {
+ dev: {
+ process.env.TC_API_V4_URL || 'https://api.topcoder-dev.com/v4',
+ },
+ prod: {
+ process.env.TC_API_V4_URL || 'https://api.topcoder.com/v4',
+ },
+ },
+ TOPCODER_ENV: process.env.TOPCODER_ENV || 'dev',
};
diff --git a/src/front/src/app/projects/project.service.js b/src/front/src/app/projects/project.service.js
index 24a06ef..523ab4b 100644
--- a/src/front/src/app/projects/project.service.js
+++ b/src/front/src/app/projects/project.service.js
@@ -178,5 +178,19 @@ angular.module('topcoderX')
});
};
+ /**
+ * Get technology tags
+ */
+ ProjectService.getTags = function() {
+ return $http({
+ method: 'GET',
+ url: $rootScope.appConfig.TC_API_V4_URL[$rootScope.appConfig.TOPCODER_ENV] + '/technologies,
+ headers: {
+ "Content-Type": "application/json",
+ "Authorization": "Bearer " + AuthService.getTokenV3()
+ }
+ });
+ };
+
return ProjectService;
}]);
diff --git a/src/front/src/app/upsertproject/upsertproject.controller.js b/src/front/src/app/upsertproject/upsertproject.controller.js
index 9596f60..d145dd6 100644
--- a/src/front/src/app/upsertproject/upsertproject.controller.js
+++ b/src/front/src/app/upsertproject/upsertproject.controller.js
@@ -30,9 +30,6 @@ angular.module('topcoderX').controller('ProjectController', ['currentUser', '$sc
if ($rootScope.project) {
$scope.title = 'Manage a Project';
$scope.project = $rootScope.project;
- $scope.project.id = $rootScope.project.id;
- $scope.project.copilot = $rootScope.project.copilot;
- $scope.project.owner = $rootScope.project.owner;
$scope.project.repoUrl = $rootScope.project.repoUrls.join(',');
$scope.editing = true;
if ($rootScope.project.tcDirectId) {
@@ -52,6 +49,14 @@ angular.module('topcoderX').controller('ProjectController', ['currentUser', '$sc
$scope.isAdminUser = Helper.isAdminUser(currentUser);
$scope.loadingConnectProjects = true;
+ $scope.tags = [];
+ $scope.fetchTags = function() {
+ ProjectService.getTags().then(function (resp) {
+ $scope.tags = resp.data.map(tag => tag.name);
+ });
+ }
+ $scope.fetchTags();
+
$scope.fetchConnectProjects = function($event) {
if (!$event) {
$scope.page = 1;
diff --git a/src/front/src/app/upsertproject/upsertproject.html b/src/front/src/app/upsertproject/upsertproject.html
index 848837e..ab6ec5c 100644
--- a/src/front/src/app/upsertproject/upsertproject.html
+++ b/src/front/src/app/upsertproject/upsertproject.html
@@ -77,6 +77,23 @@
{{title}}
TC Connect Project is required.
+
+
+
+
+ {{$item}}
+
+
+ {{tag}}
+
+
+ Select the Tags to be associated with.
+ The
+ Project/Challenge tags cannot be empty. [PATCH /challenges/:challengeId requires at least one tag]
+
+
The URL to the repository on Github or Gitlab. For example:
diff --git a/src/models/Project.js b/src/models/Project.js
index ea10c7a..06f1608 100644
--- a/src/models/Project.js
+++ b/src/models/Project.js
@@ -24,6 +24,11 @@ const schema = new Schema({
type: Number,
required: true
},
+ tags: {
+ type: Array,
+ required: true,
+ default: []
+ },
rocketChatWebhook: {type: String, required: false},
rocketChatChannelName: {type: String, required: false},
archived: {type: String, required: true},
diff --git a/src/services/ProjectService.js b/src/services/ProjectService.js
index bc702c2..13f7e2e 100644
--- a/src/services/ProjectService.js
+++ b/src/services/ProjectService.js
@@ -31,11 +31,13 @@ const currentUserSchema = Joi.object().keys({
handle: Joi.string().required(),
roles: Joi.array().required(),
});
-const projectSchema = {
+const updateProjectSchema = {
project: {
id: Joi.string().required(),
title: Joi.string().required(),
tcDirectId: Joi.number().required(),
+ //NOTE: `PATCH /challenges/:challengeId` requires the tags not empty
+ tags: Joi.array().items(Joi.string().required()).min(1).required(),
repoUrl: Joi.string().required(),
repoUrls: Joi.array().required(),
rocketChatWebhook: Joi.string().allow(null),
@@ -57,6 +59,8 @@ const createProjectSchema = {
project: {
title: Joi.string().required(),
tcDirectId: Joi.number().required(),
+ //NOTE: `PATCH /challenges/:challengeId` requires the tags not empty
+ tags: Joi.array().items(Joi.string().required()).min(1).required(),
repoUrl: Joi.string().required(),
copilot: Joi.string().allow(null),
rocketChatWebhook: Joi.string().allow(null),
@@ -125,7 +129,7 @@ async function _ensureEditPermissionAndGetInfo(projectId, currentUser) {
* @param {String} repoUrl the repository url
* @param {Object} project the new project
* @param {String} currentUser the topcoder current user
- * @returns {void}
+ * @returns {Array} challengeUUIDs
* @private
*/
async function _createOrMigrateRepository(repoUrl, project, currentUser) {
@@ -159,6 +163,9 @@ async function _createOrMigrateRepository(repoUrl, project, currentUser) {
catch (err) {
throw new Error(`Update ProjectId for Repository, Issue, CopilotPayment failed. Repo ${repoUrl}. Internal Error: ${err}`);
}
+
+ const oldProject = await dbHelper.getById(models.Project, oldRepo.projectId);
+ return _.isEqual(oldProject.tags, project.tags) ? [] : challengeUUIDs;
} else {
try {
await dbHelper.create(models.Repository, {
@@ -175,6 +182,8 @@ async function _createOrMigrateRepository(repoUrl, project, currentUser) {
throw new Error(`Project created. Adding the webhook, issue labels, and wiki rules failed. Repo ${repoUrl}. Internal Error: ${err}`);
}
}
+
+ return [];
}
/**
@@ -206,16 +215,32 @@ async function create(project, currentUser) {
const createdProject = await dbHelper.create(models.Project, project);
+ let challengeUUIDsList = [];
// TODO: The following db operation should/could be moved into one transaction
for (const repoUrl of repoUrls) { // eslint-disable-line no-restricted-syntax
try {
- await _createOrMigrateRepository(repoUrl, project, currentUser);
+ const challengeUUIDs = await _createOrMigrateRepository(repoUrl, project, currentUser);
+ if (!_.isEmpty(challengeUUIDs)) {
+ challengeUUIDsList.append(challengeUUIDs);
+ }
}
catch (err) {
throw new Error(`Create or migrate repository failed. Repo ${repoUrl}. Internal Error: ${err.message}`);
}
}
+ // NOTE: Will update challenge tags even if the project is created with archived at this step, currently.
+ if (!_.isEmpty(challengeUUIDsList)) {
+ const projectTagsUpdatedEvent = {
+ event: 'challengeTags.update',
+ data: {
+ challengeUUIDsList,
+ tags: project.tags,
+ },
+ };
+ await kafka.send(JSON.stringify(projectTagsUpdatedEvent));
+ }
+
return createdProject;
}
@@ -253,32 +278,50 @@ async function update(project, currentUser) {
*/
project.owner = dbProject.owner;
project.copilot = project.copilot !== undefined ? project.copilot.toLowerCase() : null;
- Object.entries(project).map((item) => {
- dbProject[item[0]] = item[1];
- return item;
- });
// TODO: move the following logic into one dynamoose transaction
- const repos = await dbHelper.queryRepositoriesByProjectId(dbProject.id);
+ const repos = await dbHelper.queryRepositoriesByProjectId(project.id);
+ let challengeUUIDsList = [];
for (const repoUrl of repoUrls) { // eslint-disable-line no-restricted-syntax
if (repos.find(repo => repo.url === repoUrl)) {
const repoId = repos.find(repo => repo.url === repoUrl).id
await dbHelper.update(models.Repository, repoId, {archived: project.archived});
+ if (!_.isEqual(dbProject.tags, project.tags)) {
+ // NOTE: delay query of challengeUUIDs into topcoder-x-processor
+ challengeUUIDsList.append(repoUrl);
+ }
} else {
try {
- await _createOrMigrateRepository(repoUrl, project, currentUser);
+ const challengeUUIDs = await _createOrMigrateRepository(repoUrl, project, currentUser);
+ if (!_.isEmpty(challengeUUIDs)) {
+ challengeUUIDsList.append(challengeUUIDs);
+ }
}
catch (err) {
throw new Error(`Create or migrate repository failed. Repo ${repoUrl}. Internal Error: ${err.message}`);
}
}
}
- dbProject.updatedAt = new Date();
- return await dbHelper.update(models.Project, dbProject.id, dbProject);
+ project.updatedAt = new Date();
+ const updatedProject = await dbHelper.update(models.Project, project.id, project);
+
+ // NOTE: Will update challenge tags even if the project is changed to archived at this step, currently.
+ if (!_.isEmpty(challengeUUIDsList)) {
+ const projectTagsUpdatedEvent = {
+ event: 'challengeTags.update',
+ data: {
+ challengeUUIDsList,
+ tags: project.tags,
+ },
+ };
+ await kafka.send(JSON.stringify(projectTagsUpdatedEvent));
+ }
+
+ return updatedProject;
}
-update.schema = projectSchema;
+update.schema = updateProjectSchema;
/**
* gets all projects