Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FEATURE] Ajouter la fonctionnalité d'import ONDE aux organisations ajoutées depuis le script de création en masse (PIX-12563). #9449

Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ const createOrganizationsWithTagsAndTargetProfiles = async function ({
dataProtectionOfficerRepository,
organizationInvitationRepository,
organizationRepository,
organizationForAdminRepository,
organizationTagRepository,
schoolRepository,
tagRepository,
Expand All @@ -46,7 +47,7 @@ const createOrganizationsWithTagsAndTargetProfiles = async function ({

createdOrganizations = await _createOrganizations({
domainTransaction,
organizationRepository,
organizationForAdminRepository,
transformedOrganizationsData,
});

Expand Down Expand Up @@ -84,27 +85,36 @@ const createOrganizationsWithTagsAndTargetProfiles = async function ({

export { createOrganizationsWithTagsAndTargetProfiles };

async function _createOrganizations({ transformedOrganizationsData, domainTransaction, organizationRepository }) {
try {
const createdOrganizations = await organizationRepository.batchCreateOrganizations(
transformedOrganizationsData,
domainTransaction,
);
return createdOrganizations;
} catch (error) {
_monitorError(error.message, { error, event: 'create-organizations' });

if (error.code === PGSQL_FOREIGN_KEY_VIOLATION_ERROR) {
const createdByUserId = error.detail.match(/\d+/g);
throw new InvalidInputDataError({ message: `User with ID "${createdByUserId}" does not exist` });
}

if (error instanceof DomainError) {
throw error;
async function _createOrganizations({
transformedOrganizationsData,
domainTransaction,
organizationForAdminRepository,
}) {
return bluebird.map(transformedOrganizationsData, async (organizationToCreate) => {
try {
const createdOrganization = await organizationForAdminRepository.save(
organizationToCreate.organization,
domainTransaction,
);
return {
createdOrganization,
organizationToCreate,
};
} catch (error) {
_monitorError(error.message, { error, event: 'create-organizations' });

if (error.code === PGSQL_FOREIGN_KEY_VIOLATION_ERROR) {
const createdByUserId = error.detail.match(/\d+/g);
throw new InvalidInputDataError({ message: `User with ID "${createdByUserId}" does not exist` });
}

if (error instanceof DomainError) {
throw error;
}

throw new DomainError(error.message);
}

throw new DomainError(error.message);
}
});
}

function _transformOrganizationsCsvData(organizationsCsvData) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -143,13 +143,25 @@ const get = async function (id, domainTransaction = DomainTransaction.emptyTrans
* @param {OrganizationForAdmin} organization
* @return {Promise<OrganizationForAdmin>}
*/
const save = async function (organization) {
const data = _.pick(organization, ['name', 'type', 'documentationUrl', 'credit', 'createdBy']);
const [organizationCreated] = await knex(ORGANIZATIONS_TABLE_NAME).returning('*').insert(data);
const save = async function (organization, domainTransaction = DomainTransaction.emptyTransaction()) {
const knexConn = domainTransaction.knexTransaction ?? knex;
const data = _.pick(organization, [
'name',
'type',
'email',
'externalId',
'provinceCode',
'isManagingStudents',
'identityProviderForCampaigns',
'credit',
'createdBy',
'documentationUrl',
]);
const [organizationCreated] = await knexConn(ORGANIZATIONS_TABLE_NAME).returning('*').insert(data);
const savedOrganization = _toDomain(organizationCreated);

if (!_.isEmpty(savedOrganization.features)) {
await _enableFeatures(knex, savedOrganization.features, savedOrganization.id);
await _enableFeatures(knexConn, savedOrganization.features, savedOrganization.id);
}
return savedOrganization;
};
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
import bluebird from 'bluebird';
import _ from 'lodash';

import { knex } from '../../../../db/knex-database-connection.js';
import { DomainTransaction } from '../../../../lib/infrastructure/DomainTransaction.js';
import { Organization } from '../../../organizational-entities/domain/models/Organization.js';
import { Tag } from '../../../organizational-entities/domain/models/Tag.js';
import { NotFoundError } from '../../domain/errors.js';
import { CONCURRENCY_HEAVY_OPERATIONS } from '../constants.js';
import { fetchPage } from '../utils/knex-utils.js';

const ORGANIZATIONS_TABLE_NAME = 'organizations';
Expand Down Expand Up @@ -75,56 +72,6 @@ const create = function (organization) {
.then(([organization]) => _toDomain(organization));
};

// Uses OrganizationForAdmin to centralize features enablement by organization type
const batchCreateOrganizations = async function (
organizations,
domainTransaction = DomainTransaction.emptyTransaction(),
) {
const knexConn = domainTransaction.knexTransaction ?? knex;
const featuresByKey = _.keyBy(await knexConn('features'), (feature) => feature.key);

return bluebird.map(
organizations,
async (organizationCsvData) => {
const { organization } = organizationCsvData;
const [createdOrganization] = await knexConn(ORGANIZATIONS_TABLE_NAME)
.insert(
_.pick(organization, [
'name',
'type',
'email',
'externalId',
'provinceCode',
'isManagingStudents',
'identityProviderForCampaigns',
'credit',
'createdBy',
'documentationUrl',
]),
)
.returning('*');

const enabledFeatures = _.keys(organization.features).filter((key) => organization.features[key] === true);

for (const featureKey of enabledFeatures) {
const feature = featuresByKey[featureKey];
await knexConn('organization-features').insert({
organizationId: createdOrganization.id,
featureId: feature.id,
});
}

return {
createdOrganization,
organizationToCreate: organizationCsvData,
};
},
{
concurrency: CONCURRENCY_HEAVY_OPERATIONS,
},
);
};

const update = async function (organization) {
const organizationRawData = _.pick(organization, [
'name',
Expand Down Expand Up @@ -243,7 +190,6 @@ const findPaginatedFilteredByTargetProfile = async function ({ targetProfileId,
};

export {
batchCreateOrganizations,
create,
findActiveScoOrganizationsByExternalId,
findByExternalIdsFetchingIdsOnly,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import * as dataProtectionOfficerRepository from '../../../../lib/infrastructure
import * as organizationTagRepository from '../../../../lib/infrastructure/repositories/organization-tag-repository.js';
import * as tagRepository from '../../../../lib/infrastructure/repositories/tag-repository.js';
import * as targetProfileShareRepository from '../../../../lib/infrastructure/repositories/target-profile-share-repository.js';
import { organizationForAdminRepository } from '../../../../src/organizational-entities/infrastructure/repositories/organization-for-admin.repository.js';
import * as schoolRepository from '../../../../src/school/infrastructure/repositories/school-repository.js';
import { ORGANIZATION_FEATURE } from '../../../../src/shared/domain/constants.js';
import { EntityValidationError } from '../../../../src/shared/domain/errors.js';
Expand All @@ -25,11 +26,18 @@ const { omit } = lodash;

describe('Integration | UseCases | create-organizations-with-tags-and-target-profiles', function () {
let missionFeature;
let importStudentsFeature;
let ondeImportFormat;
let userId;

beforeEach(async function () {
databaseBuilder.factory.buildFeature(ORGANIZATION_FEATURE.COMPUTE_ORGANIZATION_LEARNER_CERTIFICABILITY);
missionFeature = databaseBuilder.factory.buildFeature(ORGANIZATION_FEATURE.MISSIONS_MANAGEMENT);
importStudentsFeature = databaseBuilder.factory.buildFeature(ORGANIZATION_FEATURE.LEARNER_IMPORT);
ondeImportFormat = databaseBuilder.factory.buildOrganizationLearnerImportFormat({
name: ORGANIZATION_FEATURE.LEARNER_IMPORT.FORMAT.ONDE,
});

userId = databaseBuilder.factory.buildUser().id;
await databaseBuilder.commit();
});
Expand All @@ -45,6 +53,7 @@ describe('Integration | UseCases | create-organizations-with-tags-and-target-pro
domainTransaction,
organizations,
organizationRepository,
organizationForAdminRepository,
tagRepository,
targetProfileShareRepository,
organizationTagRepository,
Expand Down Expand Up @@ -84,6 +93,7 @@ describe('Integration | UseCases | create-organizations-with-tags-and-target-pro
domainTransaction,
organizations: organizationsWithEmptyValues,
organizationRepository,
organizationForAdminRepository,
tagRepository,
targetProfileShareRepository,
organizationTagRepository,
Expand Down Expand Up @@ -185,6 +195,7 @@ describe('Integration | UseCases | create-organizations-with-tags-and-target-pro
domainTransaction,
organizations: organizationsWithTagsWithOneMissingExternalId,
organizationRepository,
organizationForAdminRepository,
tagRepository,
targetProfileShareRepository,
organizationTagRepository,
Expand Down Expand Up @@ -258,6 +269,7 @@ describe('Integration | UseCases | create-organizations-with-tags-and-target-pro
domainTransaction,
organizations: organizationsWithTagsWithOneMissingName,
organizationRepository,
organizationForAdminRepository,
tagRepository,
targetProfileShareRepository,
organizationTagRepository,
Expand Down Expand Up @@ -334,6 +346,7 @@ describe('Integration | UseCases | create-organizations-with-tags-and-target-pro
domainTransaction,
organizations: organizationsWithTagsNotExists,
organizationRepository,
organizationForAdminRepository,
tagRepository,
targetProfileShareRepository,
organizationTagRepository,
Expand Down Expand Up @@ -413,6 +426,7 @@ describe('Integration | UseCases | create-organizations-with-tags-and-target-pro
domainTransaction,
organizations: organizationsWithTagsAlreadyExist,
organizationRepository,
organizationForAdminRepository,
tagRepository,
targetProfileShareRepository,
organizationTagRepository,
Expand Down Expand Up @@ -511,6 +525,7 @@ describe('Integration | UseCases | create-organizations-with-tags-and-target-pro
domainTransaction,
organizations: organizationsWithNonExistingTargetProfile,
organizationRepository,
organizationForAdminRepository,
tagRepository,
targetProfileShareRepository,
organizationTagRepository,
Expand Down Expand Up @@ -589,6 +604,7 @@ describe('Integration | UseCases | create-organizations-with-tags-and-target-pro
domainTransaction,
organizations: organizationsWithExistingTargetProfiles,
organizationRepository,
organizationForAdminRepository,
tagRepository,
targetProfileShareRepository,
organizationTagRepository,
Expand Down Expand Up @@ -675,6 +691,7 @@ describe('Integration | UseCases | create-organizations-with-tags-and-target-pro
domainTransaction,
organizations: organizationsWithInvitationRole,
organizationRepository,
organizationForAdminRepository,
tagRepository,
targetProfileShareRepository,
organizationTagRepository,
Expand All @@ -697,7 +714,7 @@ describe('Integration | UseCases | create-organizations-with-tags-and-target-pro
});

describe('when organization type is SCO-1D', function () {
it('should add mission management feature to organization', async function () {
it('should add mission management and ONDE import features to organization', async function () {
// given
databaseBuilder.factory.buildTag({ name: 'TAG1' });
await databaseBuilder.commit();
Expand All @@ -719,10 +736,11 @@ describe('Integration | UseCases | create-organizations-with-tags-and-target-pro
];

// when
await createOrganizationsWithTagsAndTargetProfiles({
const createdOrganizations = await createOrganizationsWithTagsAndTargetProfiles({
domainTransaction,
organizations,
organizationRepository,
organizationForAdminRepository,
tagRepository,
targetProfileShareRepository,
organizationTagRepository,
Expand All @@ -735,12 +753,20 @@ describe('Integration | UseCases | create-organizations-with-tags-and-target-pro

// then
const savedOrganizationFeatures = await knex('organization-features');
//TODO organization create with batch, see https://1024pix.atlassian.net/jira/software/c/projects/PIX/boards/107?selectedIssue=PIX-12563
expect(savedOrganizationFeatures.length).to.equal(1);
const savedOrganizationFeatureIds = savedOrganizationFeatures.map(
(organizationFeature) => organizationFeature.featureId,
);
expect(savedOrganizationFeatureIds).to.include(missionFeature.id);
expect(savedOrganizationFeatures.length).to.equal(2);
const organizationId = createdOrganizations[0].id;
expect(savedOrganizationFeatures.map((organizationFeature) => omit(organizationFeature, 'id'))).to.deep.equal([
{
featureId: missionFeature.id,
params: null,
organizationId,
},
{
featureId: importStudentsFeature.id,
params: { organizationLearnerImportFormatId: ondeImportFormat.id },
organizationId,
},
]);
});

it('should create schools associated to organizations', async function () {
Expand Down Expand Up @@ -782,6 +808,7 @@ describe('Integration | UseCases | create-organizations-with-tags-and-target-pro
domainTransaction,
organizations,
organizationRepository,
organizationForAdminRepository,
tagRepository,
targetProfileShareRepository,
organizationTagRepository,
Expand Down Expand Up @@ -843,6 +870,7 @@ describe('Integration | UseCases | create-organizations-with-tags-and-target-pro
domainTransaction,
organizations,
organizationRepository,
organizationForAdminRepository,
tagRepository,
targetProfileShareRepository,
organizationTagRepository,
Expand Down
Loading