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

New: Migration Scripts (fixes #208) #213

Open
wants to merge 17 commits into
base: master
Choose a base branch
from
225 changes: 225 additions & 0 deletions migrations/v2.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,225 @@
import { describe, whereContent, whereFromPlugin, mutateContent, checkContent, updatePlugin } from 'adapt-migrations';

describe('adapt-contrib-assessment - v2.0.0 > v2.0.3', async () => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think references to v2.0.0 should be v2.0.1?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just to double check, I have been starting mine at 2.0.0 per the discussion here. Happy to start at 2.0.1, but I don't think that's what we agreed upon.
adaptlearning/adapt-contrib-accordion#159 (comment)

Copy link
Contributor

@chris-steele chris-steele Feb 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, meant to say should this use 2.0.0 as this supports framework v2 (there is no v2.0.0)

let articles, assessments;

whereFromPlugin('adapt-contrib-assessment - from v2.0.0', { name: 'adapt-contrib-assessment', version: '<2.0.3' });

whereContent('adapt-contrib-assessment - where assessment', async content => {
articles = content.filter(({ _type }) => _type === 'article');
assessments = articles.filter(({ _type, _assessment }) => _type === 'article' && _assessment !== undefined);
return assessments.length;
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could probably simplify this further to a single filter.

Thoughts on changing assessments to assessmentArticles?

Suggested change
whereContent('adapt-contrib-assessment - where assessment', async content => {
articles = content.filter(({ _type }) => _type === 'article');
assessments = articles.filter(({ _type, _assessment }) => _type === 'article' && _assessment !== undefined);
return assessments.length;
});
whereContent('adapt-contrib-assessment - where assessment', async content => {
assessmentArticles = content.filter(({ _type, _assessment }) => _type === 'article' && _assessment !== undefined);
return assessments.length;
});

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good call.


/**
* * Add field to each item in a JSON array and set blank.
*/
mutateContent('adapt-contrib-assessment - add assessment._questions._canShowModelAnswer attribute', async () => {
assessments.forEach(assessment => {
assessment._questions.forEach(item => {
item._canShowModelAnswer = true;
});
});
return true;
});

checkContent('adapt-contrib-assessment - check assessment._questions._canShowModelAnswer attribute', async () => {
const isValid = assessments.every(assessment =>
assessment._questions.every(item =>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

assessment._questions.every(item => item._canShowModelAnswer) is a little shorter

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess what I'm trying to achieve here is the possibility that there may be multiple assessments in a given build. So first, I want to grab all the assessments and then look for every instance of assessment._questions within each possible assessment. Most of the time there will only be 1 assessment, but we do allow multiple.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is !== false necessary when item._canShowModelAnswer would be a truthy?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, I see now.

item._canShowModelAnswer !== false
)
);
if (!isValid) throw new Error('adapt-contrib-assessment - _canShowModelAnswer not added to every instance of assessment._questions');
return true;
});

updatePlugin('adapt-contrib-assessment - update to v2.0.3', { name: 'adapt-contrib-assessment', version: '2.0.3', framework: '>=2.0.0' });
});

describe('adapt-contrib-assessment - v2.0.3 > v2.1.0', async () => {
let articles, assessments;

whereFromPlugin('adapt-contrib-assessment - from v2.0.3', { name: 'adapt-contrib-assessment', version: '<2.1.0' });

whereContent('adapt-contrib-assessment - where assessment', async content => {
articles = content.filter(({ _type }) => _type === 'article');
assessments = articles.filter(({ _type, _assessment }) => _type === 'article' && _assessment !== undefined);
return assessments.length;
});

/**
* * Add JSON field and set attribute.
*/
mutateContent('adapt-contrib-assessment - add assessment._suppressMarking', async () => {
assessments.forEach(assessment => {
assessment._suppressMarking = true;
});
return true;
});

checkContent('adapt-contrib-assessment - check assessment._suppressMarking attribute', async () => {
const isValid = assessments.every(assessment =>
assessment._suppressMarking === true
);
if (!isValid) throw new Error('adapt-contrib-assessment - _suppressMarking not added to every instance of assessment as true.');
return true;
});

updatePlugin('adapt-contrib-assessment - update to v2.1.0', { name: 'adapt-contrib-assessment', version: '2.1.0', framework: '>=2.0.18' });
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The update to v2.1.0 requires framework v2.1.3.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good looking out. Thanks.

});

describe('adapt-contrib-assessment - v2.1.0 > v2.1.1', async () => {
let articles, assessments;

whereFromPlugin('adapt-contrib-assessment - from v2.1.0', { name: 'adapt-contrib-assessment', version: '<2.1.1' });

whereContent('adapt-contrib-assessment - where assessment', async content => {
articles = content.filter(({ _type }) => _type === 'article');
assessments = articles.filter(({ _type, _assessment }) => _type === 'article' && _assessment !== undefined);
if (assessments) return true;
});

/**
* * Modify existing attribute to new value.
*/
mutateContent('adapt-contrib-assessment - add assessment._isPercentageBased', async () => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think the following properties were changed at all from v2.1.0 to v2.1.1: _isPercentageBased, _canShowFeedback, _canShowMarking, and _canShowModelAnswer

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a weird one. Defaults look like like they were added to attributes in this.
14daa5a#diff-bbbc797064913d6074c5515fadbef42b3c90e0b677002d065500e190e0d3bf88R221

Copy link
Contributor

@chris-steele chris-steele Feb 19, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@joe-replin I think the schema was reorganised, which makes it really difficult to read (hate when that happens!):, but you can see _isPercentageBased removed and added in the same diff

assessments.forEach(assessment => {
if (assessment._isPercentageBased !== true) assessment._isPercentageBased = true;
});
return true;
});

checkContent('adapt-contrib-assessment - check assessment._isPercentageBased attribute', async () => {
const isValid = assessments.every(assessment =>
assessment._isPercentageBased === true
);
if (!isValid) throw new Error('adapt-contrib-assessment - _isPercentageBased has been modified to true');
return true;
});

/**
* * Modify existing attribute to new value.
*/
mutateContent('adapt-contrib-assessment - add assessment._canShowFeedback', async () => {
assessments.forEach(assessment => {
if (assessment._canShowFeedback !== false) assessment._canShowFeedback = false;
});
return true;
});

checkContent('adapt-contrib-assessment - check assessment._canShowFeedback attribute', async () => {
const isValid = assessments.every(assessment =>
assessment._canShowFeedback === false
);
if (!isValid) throw new Error('adapt-contrib-assessment - _canShowFeedback has been modified to false');
return true;
});

/**
* * Modify existing attribute to new value.
*/
mutateContent('adapt-contrib-assessment - add assessment._canShowMarking', async () => {
assessments.forEach(assessment => {
if (assessment._canShowMarking !== false) assessment._canShowMarking = false;
});
return true;
});

checkContent('adapt-contrib-assessment - check assessment._canShowMarking attribute', async () => {
const isValid = assessments.every(assessment =>
assessment._canShowMarking === false
);
if (!isValid) throw new Error('adapt-contrib-assessment - _canShowMarking has been modified to false');
return true;
});

/**
* * Modify existing attribute to new value.
*/
mutateContent('adapt-contrib-assessment - add assessment._canShowModelAnswer', async () => {
assessments.forEach(assessment => {
if (assessment._canShowModelAnswer !== false) assessment._canShowModelAnswer = false;
});
return true;
});

checkContent('adapt-contrib-assessment - check assessment._canShowModelAnswer attribute', async () => {
const isValid = assessments.every(assessment =>
assessment._canShowModelAnswer === false
);
if (!isValid) throw new Error('adapt-contrib-assessment - _canShowModelAnswer has been modified to false');
return true;
});

/**
* * Remove existing attribute.
*/
mutateContent('adapt-contrib-assessment - add assessment._requireAssessmentPassed', async () => {
assessments.forEach(assessment => {
delete assessment._requireAssessmentPassed;
});
return true;
});

checkContent('adapt-contrib-assessment - check assessment._requireAssessmentPassed attribute', async () => {
const isValid = assessments.every(assessment =>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just to go with convention, perhaps: const isValid = assessments.every(assessment => !_.has(assessment, '_requireAssessmentPassed'));

Copy link
Contributor Author

@joe-replin joe-replin Feb 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks. Not super familiar with lodash helpers. But will apply this to other, similar delete modifications.

assessment._requireAssessmentPassed === undefined
);
if (!isValid) throw new Error('adapt-contrib-assessment - _requireAssessmentPassed has been removed');
return true;
});

updatePlugin('adapt-contrib-assessment - update to v2.1.1', { name: 'adapt-contrib-assessment', version: '2.1.1', framework: '>=2.0.18' });
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The update to v2.1.1 requires framework v2.2.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

});

describe('adapt-contrib-assessment - v2.1.1 > v2.2.0', async () => {
let articles, assessments;

whereFromPlugin('adapt-contrib-assessment - from v2.1.1', { name: 'adapt-contrib-assessment', version: '<2.2.0' });

whereContent('adapt-contrib-assessment - where assessment', async content => {
articles = content.filter(({ _type }) => _type === 'article');
assessments = articles.filter(({ _type, _assessment }) => _type === 'article' && _assessment !== undefined);
if (assessments) return true;
});

/**
* * Modify Attribute
*/
mutateContent('adapt-contrib-assessment - add assessment._includeInTotalScore attribute', async () => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_includeInTotalScore is an existing property and I think we're avoiding changing boolean defaults.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Gotcha. Will remove.

assessments.forEach(assessment => {
assessment._includeInTotalScore = true;
});
return true;
});

checkContent('adapt-contrib-assessment - check assessment._includeInTotalScore attribute', async () => {
const isValid = assessments.every(({ _includeInTotalScore }) => _includeInTotalScore === true);
if (!isValid) throw new Error('adapt-contrib-assessment - _includeInTotalScore not modified to true in every assessment instance.');
return true;
});

/**
* * Add JSON field and set attribute.
*/
mutateContent('adapt-contrib-assessment - add assessment._allowResetIfPassed', async () => {
assessments.forEach(assessment => {
assessment._questions.forEach(item => {
item._allowResetIfPassed = false;
});
});
return true;
});

checkContent('adapt-contrib-assessment - check assessment._allowResetIfPassed attribute', async () => {
const isValid = assessments.every(assessment =>
assessment._questions.every(item =>
item._allowResetIfPassed === false
)
);
if (!isValid) throw new Error('adapt-contrib-assessment - _allowResetIfPassed not added to every instance of assessment._questions');
return true;
});

updatePlugin('adapt-contrib-assessment - update to v2.2.0', { name: 'adapt-contrib-assessment', version: 'v2.2.0', framework: '>=2.2.0' });
});
29 changes: 29 additions & 0 deletions migrations/v3.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { describe, whereContent, whereFromPlugin, mutateContent, checkContent, updatePlugin } from 'adapt-migrations';

describe('adapt-contrib-assessment - v2.2.0 > v3.0.0', async () => {
let course, assessmentConfig;

whereFromPlugin('adapt-contrib-assessment - from v2.2.0', { name: 'adapt-contrib-assessment', version: '<3.0.0' });

whereContent('adapt-contrib-assessment - where assessment', async content => {
course = content.filter(({ _type }) => _type === 'course');
assessmentConfig = course.find(({ _assessment }) => _assessment);
if (assessmentConfig) return true;
});

/**
* * Remove JSON attribute from Course file.
*/
mutateContent('adapt-contrib-assessment - remove course._postTotalScoreToLms attribute', async () => {
delete assessmentConfig._postTotalScoreToLms;
return true;
});

checkContent('adapt-contrib-assessment - check course._postTotalScoreToLms attribute', async () => {
const isValid = !assessmentConfig._postTotalScoreToLms;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

const isValid = !_.has(assessmentConfig, '_postTotalScoreToLms');

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!

if (!isValid) throw new Error('adapt-contrib-assessment - _postTotalScoreToLms removed from course file.');
return true;
});

updatePlugin('adapt-contrib-assessment - update to v3.0.0', { name: 'adapt-contrib-assessment', version: '3.0.0', framework: '>=3.2.0' });
});
123 changes: 123 additions & 0 deletions migrations/v4.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
import { describe, whereContent, whereFromPlugin, mutateContent, checkContent, updatePlugin } from 'adapt-migrations';

describe('adapt-contrib-assessment - v3.0.0 > v4.3.0', async () => {
let articles, assessments;

whereFromPlugin('adapt-contrib-assessment - from v3.0.0', { name: 'adapt-contrib-assessment', version: '<4.3.0' });

whereContent('adapt-contrib-assessment - where assessment', async content => {
articles = content.filter(({ _type }) => _type === 'article');
assessments = articles.filter(({ _assessment }) => _assessment !== undefined);
return assessments.length;
});

/**
* * Add JSON field and set attribute.
*/
mutateContent('adapt-contrib-assessment - add assessment._scrollToOnReset', async () => {
assessments.forEach(assessment => {
assessment._scrollToOnReset = false;
});
return true;
});

checkContent('adapt-contrib-assessment - check assessment._scrollToOnReset attribute', async () => {
const isValid = assessments.every(assessment =>
assessment._scrollToOnReset === false
);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const isValid = assessments.every(assessment =>
assessment._scrollToOnReset === false
);
const isValid = assessments.every(({ assessment }) =>
assessment._scrollToOnReset === false
);

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added in a separate commit.

if (!isValid) throw new Error('adapt-contrib-assessment - _scrollToOnReset not added to every instance of assessment');
return true;
});

updatePlugin('adapt-contrib-assessment - update to v4.3.0', { name: 'adapt-contrib-assessment', version: '4.3.0', framework: '>=5.4.0' });
});

describe('adapt-contrib-assessment - v4.3.0 > v4.4.0', async () => {
let course, assessmentConfig, assessments;
Copy link
Contributor

@chris-steele chris-steele Feb 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

assessments isn't initialized in this block

Copy link
Contributor Author

@joe-replin joe-replin Feb 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about the _scoreToPass and _correctToPass where it is used?


whereFromPlugin('adapt-contrib-assessment - from v4.3.0', { name: 'adapt-contrib-assessment', version: '<4.4.0' });

whereContent('adapt-contrib-assessment - where assessment', async content => {
course = content.filter(({ _type }) => _type === 'course');
assessmentConfig = course.find(({ _assessment }) => _assessment);
if (assessmentConfig) return true;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the _assessment configuration in course is optional, so we would still want to run the migrations on the _assessment configurations in articles

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All of the mutations taking place within this describe statement is at the course level per the previous comment about switching _correctToPass and _scoreToPass to assessmentConfig.

});

/**
* * Add JSON field and set attribute.
*/
mutateContent('adapt-contrib-assessment - add assessment._scoreToPass', async () => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should this have been _correctToPass and on assessmentConfig?

assessments.forEach(assessment => {
assessment._scoreToPass = 60;
});
return true;
});

checkContent('adapt-contrib-assessment - check assessment._scoreToPass attribute', async () => {
const isValid = assessments.every(assessment =>
assessment._scoreToPass === 60
);
if (!isValid) throw new Error('adapt-contrib-assessment - _scoreToPass not added to every instance of assessment and set as 60.');
return true;
});

/**
* * Add JSON field and set attribute.
*/
mutateContent('adapt-contrib-assessment - add assessment._correctToPass', async () => {
assessments.forEach(assessment => {
assessment._correctToPass = 60;
});
return true;
});

checkContent('adapt-contrib-assessment - check assessment._correctToPass attribute', async () => {
const isValid = assessments.every(assessment =>
assessment._correctToPass === 60
);
if (!isValid) throw new Error('adapt-contrib-assessment - _correctToPass not added to every instance of assessment and set as 60.');
return true;
});

updatePlugin('adapt-contrib-assessment - update to v4.4.0', { name: 'adapt-contrib-assessment', version: '4.4.0', framework: '>=5.4.0' });
});

describe('adapt-contrib-assessment - v4.4.0 > v4.6.1', async () => {
let articles, assessments;

whereFromPlugin('adapt-contrib-assessment - from v4.4.0', { name: 'adapt-contrib-assessment', version: '<4.6.1' });

whereContent('adapt-contrib-assessment - where assessment', async content => {
articles = content.filter(({ _type }) => _type === 'article');
assessments = articles.filter(({ _type, _assessment }) => _type === 'article' && _assessment !== undefined);
return assessments.length;
});

/**
* * Add JSON field and set attribute.
*/
mutateContent('adapt-contrib-assessment - modify assessment._questions._resetType value', async () => {
Copy link
Contributor

@chris-steele chris-steele Feb 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think your caution is justified; as with not changing boolean defaults, we should only change enumerated types if the default becomes invalid (and the invalid value is used). I would therefore remove this migration.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done!

/**
* ? Changing _resetType globally to 'hard' might have unintended consequences.
* ? This change assumes that a hard reset is preferred in all cases, which may not be true for all users.
*/
assessments.forEach(assessment => {
assessment._questions.forEach(item => {
item._resetType = 'hard';
});
});
return true;
});

checkContent('adapt-contrib-assessment - check assessment._questions._resetType value', async () => {
const isValid = assessments.every(assessment =>
assessment._questions.every(item =>
item._resetType === 'hard'
)
);
if (!isValid) throw new Error('adapt-contrib-assessment - _resetType not set to "hard" for every instance of assessment._questions');
return true;
});

updatePlugin('adapt-contrib-assessment - update to v4.6.1', { name: 'adapt-contrib-assessment', version: 'v4.6.1', framework: '>=5.11.0' });
});