Skip to content
This repository was archived by the owner on Mar 13, 2025. It is now read-only.

issues/453 #455

Merged
merged 1 commit into from
Jun 2, 2022
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
9 changes: 9 additions & 0 deletions src/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -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',
};
14 changes: 14 additions & 0 deletions src/front/src/app/projects/project.service.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}]);
11 changes: 8 additions & 3 deletions src/front/src/app/upsertproject/upsertproject.controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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;
Expand Down
17 changes: 17 additions & 0 deletions src/front/src/app/upsertproject/upsertproject.html
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,23 @@ <h2>{{title}}</h2>
TC Connect Project is required.</span>
<br />
<br />
<label class="form-label">Tags:</label>
<!-- TODO: care about order or not
<ui-select multiple ng-model="project.tags" theme="bootstrap" close-on-select="false" on-select="$select.selected.sort()">
-->
<ui-select multiple ng-model="project.tags" theme="bootstrap" close-on-select="false">
<ui-select-match placeholder="Select...">
{{$item}}
</ui-select-match>
<ui-select-choices repeat="tag in tags | filter: $select.search">
{{tag}}
</ui-select-choices>
</ui-select>
<small class="form-hint">Select the Tags to be associated with.</small>
<span ng-show="projectForm.project.tags.$touched && projectForm.project.tags.$invalid">The
Project/Challenge tags cannot be empty. [PATCH /challenges/:challengeId requires at least one tag]</span>
<br />
<br />
<label class="form-label">Repo URLs:</label>
<input class="form-control" type="url" ng-model="project.repoUrl" required />
<small class="form-hint"> The URL to the repository on Github or Gitlab. For example:
Expand Down
5 changes: 5 additions & 0 deletions src/models/Project.js
Original file line number Diff line number Diff line change
Expand Up @@ -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},
Expand Down
67 changes: 55 additions & 12 deletions src/services/ProjectService.js
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand All @@ -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),
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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, {
Expand All @@ -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 [];
}

/**
Expand Down Expand Up @@ -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;
}

Expand Down Expand Up @@ -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
Expand Down