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

Fix #50: Check pull request for job files #78

Merged
merged 18 commits into from
Jun 17, 2020

Conversation

jameesjohn
Copy link
Contributor

Explanation

This PR checks for modification of job files and the job registry in a pull request.

This PR has been tested at jameesjohn/comment-on-pr#31, jameesjohn/comment-on-pr#32

Checklist

  • I have successfully deployed my own instance of Oppiabot.
    • You can find instructions for doing this here.
  • I have manually tested all the changes made in this PR following the manual tests matrix.

Copy link
Contributor

@ankita240796 ankita240796 left a comment

Choose a reason for hiding this comment

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

Hi @jameesjohn, took a pass, PTAL. Thanks!

Also, update the PR title and description to include Fixes #50.

Comment on lines 6 to 7
const REGISTRY_FILENAME = 'jobs_registry.py';
const JOB_FILE_FIX = '_jobs_';
Copy link
Contributor

Choose a reason for hiding this comment

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

Define these outside the function.

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.

Comment on lines 6 to 7
const REGISTRY_FILENAME = 'jobs_registry.py';
const JOB_FILE_FIX = '_jobs_';
Copy link
Contributor

Choose a reason for hiding this comment

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

What does FIX represent here?

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 wasn't sure of what to name since this isn't a prefix or suffix.

const commentBody = 'Hi @' + SERVER_JOBS_ADMIN + ', PTAL at this PR, ' +
'it adds/modifies a server job. Also @' + prAuthor + ', please ' +
'endeavour to fill the ' + serverJobsForm + ' for the new job ' +
'to get tested on the test server. Thanks!';
Copy link
Contributor

Choose a reason for hiding this comment

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

to get tested on the test server -> to be tested on the backup server. This PR can be merged only after the test is successful.

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.

'https://goo.gl/forms/XIj00RJ2h5L55XzU2');
const commentBody = 'Hi @' + SERVER_JOBS_ADMIN + ', PTAL at this PR, ' +
'it adds/modifies a server job. Also @' + prAuthor + ', please ' +
'endeavour to fill the ' + serverJobsForm + ' for the new job ' +
Copy link
Contributor

Choose a reason for hiding this comment

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

endeavour to fill the -> fill the.

Also, provide a link to the wiki page with instructions for new jobs: https://github.com/oppia/oppia/wiki/Running-jobs-in-production#submitting-a-pr-with-a-new-job.

Copy link
Contributor

Choose a reason for hiding this comment

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

Also, can we mark such PRs as unmergeable until approved by Server jobs admin?

\cc @seanlip @nithusha21 @DubeySandeep

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 think what we should do here is to assign the pr to the server jobs admin.

Copy link
Contributor

Choose a reason for hiding this comment

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

That makes sense. Let us do that.

body: commentBody,
});

await context.github.issues.createComment(commentParams);
Copy link
Contributor

Choose a reason for hiding this comment

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

We should also add the critical label to the PR.

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

/**
* @param {string} filename
*/
const isJobFile = (filename) => {
Copy link
Contributor

Choose a reason for hiding this comment

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

What if a job file is being modified but no new job is introduced? There can be a case when an issue is being fixed or some variables are being renamed. We only need the test procedure for new jobs.

Further, we need to have a check which ensures that whenever a new job is added, the name of the job is added to jobs_registry file.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We may not be able to check the exact content of the job_registry file. What we can do is to ensure that a new job file is accompanied with a change in the job_registry.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We may not be able to check the exact content of the job_registry file. What we can do is to ensure that a new job file is accompanied with a change in the job_registry.

I've been able to do this.

@jameesjohn
Copy link
Contributor Author

PTAL @ankita240796.
I've addressed all comments. The implementation here is significantly different from the initial one.

Thanks.

@jameesjohn jameesjohn requested a review from ankita240796 June 9, 2020 14:23
@jameesjohn jameesjohn assigned ankita240796 and unassigned jameesjohn Jun 9, 2020
@jameesjohn
Copy link
Contributor Author

I also got some inputs from @seanlip regarding the oppiabot message.

Copy link
Contributor

@ankita240796 ankita240796 left a comment

Choose a reason for hiding this comment

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

Hi @jameesjohn, took a pass, PTAL. Thanks!

return checksWhitelist;
};

Copy link
Contributor

Choose a reason for hiding this comment

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

Revert.

Copy link
Contributor

Choose a reason for hiding this comment

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

Ditto.

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.
My formatter added these.

Copy link
Contributor

Choose a reason for hiding this comment

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

No issues, thanks for fixing! I think a line is still being removed. We should have a new line at file end,right? Let us revert that please.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

There is a new line at the end of the file.

Copy link
Contributor

Choose a reason for hiding this comment

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

Ah ok I see, there was an extra line.

@@ -0,0 +1,210 @@
const {SERVER_JOBS_ADMIN} = require('../userWhitelist.json');
Copy link
Contributor

Choose a reason for hiding this comment

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

Space after brackets.

Copy link
Contributor

Choose a reason for hiding this comment

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

Missed this one.

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.

@@ -44,7 +44,8 @@
"nodemonConfig": {
"exec": "npm start",
"watch": [
".env"
".env",
"."
Copy link
Contributor

Choose a reason for hiding this comment

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

Why this change?

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 noticed that nodemon was not restarting when the implementation code changed so adding . tells nodemon to watch the directory for the files which are expected (.js, .ts, .json) files.

Copy link
Contributor

Choose a reason for hiding this comment

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

Ok, Thanks for doing this!

constants.js Outdated
@@ -22,25 +23,16 @@ const checksWhitelist = {
[labelEvent]: [],
[synchronizeEvent]: [],
[closeEvent]: [],
[editEvent]: []
[editEvent]: [],
Copy link
Contributor

Choose a reason for hiding this comment

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

We should not have terminating , in an array list in JavaScript. Revert here and below.

Copy link
Contributor

Choose a reason for hiding this comment

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

Ditto.

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.

Comment on lines +23 to +29
// Filename is the relative path to the file, we need to
// get the exact file name.
const substrings = filename.split('/');
const actualFileNameWithExtension = substrings[substrings.length - 1];
const extensionIndex = actualFileNameWithExtension.indexOf('.py');

return actualFileNameWithExtension.substring(0, extensionIndex);
Copy link
Contributor

Choose a reason for hiding this comment

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

I am a bit confused on what this function is doing, are you obtaining the name of a job being added or the name of the file without any dir paths in it? Can you add some examples in comments and update the name of the function accordingly if it is being used to obtain file name without extensions.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It is getting the name of the job from the file name and the name of the job does not include the file extension.

Copy link
Contributor

Choose a reason for hiding this comment

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

Can you add an example in the comments?

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.

// This function will never be called when there are no job files.
if (newJobFiles.length === 1) {
const serverAdminPing = 'Hi @' + SERVER_JOBS_ADMIN + ', PTAL at this PR, ' +
'it adds a new server job.' + jobNameString;
Copy link
Contributor

Choose a reason for hiding this comment

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

server job -> one off job

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.

const prAuthorPing = 'Also @' + prAuthor + registryReminder +
'please make sure to fill in the ' + serverJobsForm +
' for the new job to be tested on the backup server. ' +
'This PR can be merged only after the test is successful.';
Copy link
Contributor

Choose a reason for hiding this comment

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

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.

const prAuthorPing = 'Also @' + prAuthor + registryReminder +
'please make sure to fill in the ' + serverJobsForm +
' for the new jobs to be tested on the backup server.' +
' This PR can be merged only after the test is successful.';
Copy link
Contributor

Choose a reason for hiding this comment

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

Ditto.

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.

if (hasNotAddedFileToRegistry) {
const fileText = newJobFiles.length > 1 ? 'files' : 'file';
registryReminder +=
'endeavour to add the new job ' + fileText + ' to the job registry and ';
Copy link
Contributor

Choose a reason for hiding this comment

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

Please add the new job...

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.

'https://github.com/oppia/oppia/blob/67fb4a973b318882af3b5a894130e110d7e9833c/core/domain/exp_jobs_oppiabot_off.py',
raw_url:
'https://github.com/oppia/oppia/raw/67fb4a973b318882af3b5a894130e110d7e9833c/core/domain/exp_jobs_oppiabot_off.py',
contents_url:
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we use this to obtain the content of the file?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Probably
The changes in the file are in the patch

Copy link
Contributor

Choose a reason for hiding this comment

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

Ok, so we can use patch for finding new jobs being added in an existing file, right?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes.
Done

Copy link
Contributor

@ankita240796 ankita240796 left a comment

Choose a reason for hiding this comment

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

Took a pass, PTAL @jameesjohn. Thanks!

@@ -0,0 +1,210 @@
const {SERVER_JOBS_ADMIN} = require('../userWhitelist.json');
Copy link
Contributor

Choose a reason for hiding this comment

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

Missed this one.

constants.js Outdated
@@ -22,25 +23,16 @@ const checksWhitelist = {
[labelEvent]: [],
[synchronizeEvent]: [],
[closeEvent]: [],
[editEvent]: []
[editEvent]: [],
Copy link
Contributor

Choose a reason for hiding this comment

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

Ditto.

return checksWhitelist;
};

Copy link
Contributor

Choose a reason for hiding this comment

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

Ditto.

Comment on lines +23 to +29
// Filename is the relative path to the file, we need to
// get the exact file name.
const substrings = filename.split('/');
const actualFileNameWithExtension = substrings[substrings.length - 1];
const extensionIndex = actualFileNameWithExtension.indexOf('.py');

return actualFileNameWithExtension.substring(0, extensionIndex);
Copy link
Contributor

Choose a reason for hiding this comment

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

Can you add an example in the comments?

const doesRegistryContainFile = (filename, files) => {
const jobName = getJobNameFromFileName(filename);

const registryFile = files.find(({filename}) => {
Copy link
Contributor

Choose a reason for hiding this comment

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

Ok, Thanks for explaining.

});
});

describe('When pull request creates multiple job files', () => {
Copy link
Contributor

Choose a reason for hiding this comment

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

Ditto.

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.

*/
let app;

const newJobFileObj = {
Copy link
Contributor

Choose a reason for hiding this comment

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

firstNewJobFileObj

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('When pull request creates a new job file and updates registry', () => {
Copy link
Contributor

Choose a reason for hiding this comment

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

When pull request creates a new job file and updates registry -> When a new job file is created and registry is updated in a pull request

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('When pull request modifies an existing job file', () => {
Copy link
Contributor

Choose a reason for hiding this comment

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

We will need to refactor this into two cases:

  1. No new job being added in an existing job file
  2. New job being added in an existing job file

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('when pull request does not modify job file', () => {
Copy link
Contributor

Choose a reason for hiding this comment

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

When no job file is modified in a pull request

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.

@ankita240796
Copy link
Contributor

Please update the PR title and description to include Fixes #50.

Copy link
Contributor

@ankita240796 ankita240796 left a comment

Choose a reason for hiding this comment

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

Quite close, a few more comments @jameesjohn, PTAL. Thanks!

constants.js Outdated
[closeEvent]: [allMergeConflictCheck],
[editEvent]: [wipCheck]
}
},
Copy link
Contributor

Choose a reason for hiding this comment

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

Please remove the comma. here as well.

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.

return checksWhitelist;
};

Copy link
Contributor

Choose a reason for hiding this comment

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

No issues, thanks for fixing! I think a line is still being removed. We should have a new line at file end,right? Let us revert that please.

* @type {import('probot').Octokit.PullsGetResponse}
*/
const pullRequest = context.payload.pull_request;
// Using 100 here since that is the maximum files per
Copy link
Contributor

Choose a reason for hiding this comment

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

100 is used here....

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.

*/
const pullRequest = context.payload.pull_request;
// Using 100 here since that is the maximum files per
// request that can be gotten from the API.
Copy link
Contributor

Choose a reason for hiding this comment

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

gotten -> obtained

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.

let registryReminder = ', ';

const hasNotAddedFileToRegistry = newJobFiles.some(
(file) => !doesRegistryContainFile(file.filename, changedFiles)
Copy link
Contributor

Choose a reason for hiding this comment

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

This should actually be checking that registry contains the job name and not only the filename. If you check the registry file, it has jobs listed in the format filename.jobname. So, I would suggest have a function which returns a list of new jobs which are added irrespective of whether it is in a new file or an existing file and then use that list here.

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.
The function is checking the patch, hence it will only get the new job since that is what we want.

const classIdentifier = 'class';

// A new job class should start with `class `
return changesArray.some(change => change.startsWith(classIdentifier));
Copy link
Contributor

Choose a reason for hiding this comment

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

A better check would be to also ensure that the class name ends with OneOffJob. Obtain the list of all such class names instead of file names. (Example: If you check prod validation jobs, there are classes in the file which are validators and not one off jobs.)

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.

Copy link
Contributor

@ankita240796 ankita240796 left a comment

Choose a reason for hiding this comment

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

Just a few more minor nits, PTAL @jameesjohn!

return checksWhitelist;
};

Copy link
Contributor

Choose a reason for hiding this comment

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

Ah ok I see, there was an extra line.


// New job definitions should look like:
// class OppiabotContributionsOneOffJob(jobs.BaseMapReduceOneOffJobManager):
// To get the actual job name, we need to remove the unneeded items.
Copy link
Contributor

Choose a reason for hiding this comment

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

unneeded items -> class prefix and superclass

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.

* @param {string} job
* @param {import('probot').Octokit.PullsListFilesResponse} files
*/
const doesRegistryContainFile = (job, files) => {
Copy link
Contributor

Choose a reason for hiding this comment

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

Rename to isRegistryUpdated since it is checking two things whether registry is modified and if modified does it include the new job.

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.

let payloadData = JSON.parse(
JSON.stringify(require('../fixtures/pullRequestPayload.json'))
);
const {SERVER_JOBS_ADMIN} = require('../userWhitelist.json');
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 you missed this.

@nishantwrp
Copy link

I've tested this functionality. Everything seems fine! But there is no comment when an existing job is modified as suggested in the description of this pull request.

@jameesjohn
Copy link
Contributor Author

Thanks @nishantwrp.

But there is no comment when an existing job is modified as suggested in the description of this pull request.

For existing job files, we are only commenting when the modifications includes a new job being added.

Copy link
Contributor

@ankita240796 ankita240796 left a comment

Choose a reason for hiding this comment

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

Final comments, PTAL @jameesjohn, Thanks!

// A new job class should start with class and include OneOffJob in its name.
const newJobDefinitions = changesArray.filter(
(change) =>
change.startsWith(classIdentifier) && change.includes(oneOffJobIdentifier)
Copy link
Contributor

Choose a reason for hiding this comment

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

Let us have a regex instead of this and grab the name using a match group. Would be more readable.

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 to regex helped reveal a bug so it was a good call.
Thanks.

const serverJobsForm = 'server jobs form'.link(
'https://goo.gl/forms/XIj00RJ2h5L55XzU2');
const wikiLinkText = (
'This PR can be merged only after the test is successful'.link(
Copy link
Contributor

Choose a reason for hiding this comment

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

test -> test run is successful. Please refer this guide [link to this guide] for details

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.

if (hasNotAddedFileToRegistry) {
const fileText = newJobFiles.length > 1 ? 'files' : 'file';
registryReminder +=
'please add the new job ' + fileText + ' to the job registry and ';
Copy link
Contributor

Choose a reason for hiding this comment

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

to the job registry [Link to jobs_registry.py]

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.

Copy link
Contributor

@ankita240796 ankita240796 left a comment

Choose a reason for hiding this comment

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

LGTM, Thanks @jameesjohn!


if (hasNotAddedFileToRegistry) {
const fileText = newJobFiles.length > 1 ? 'files' : 'file';
const jobRegistryLink = 'job registry'.link(
Copy link
Contributor

Choose a reason for hiding this comment

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

job registry file.

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.

const serverJobsForm = 'server jobs form'.link(
'https://goo.gl/forms/XIj00RJ2h5L55XzU2');
const wikiLinkText = (
'Please refer to this guide for details'.link(
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we just link to this guide instead of the complete line.

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.

Comment on lines +29 to +39
const jobRegex = /(?<classDefinition>class\s)(?<jobName>[a-zA-Z]{2,256})(?<jobSuffix>OneOffJob)/;

const newJobDefinitions = changesArray.filter(change => {
const matches = jobRegex.exec(change);
return matches !== null;
});

const newJobs = newJobDefinitions.map(definition => {
const matches = jobRegex.exec(definition);
return matches.groups.jobName + matches.groups.jobSuffix;
});
Copy link
Contributor

Choose a reason for hiding this comment

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

Thanks for doing this!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah...
Nitish's article helped a lot.

Copy link
Contributor

Choose a reason for hiding this comment

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

@bansalnitish ping :D

@jameesjohn
Copy link
Contributor Author

Hi @ankita240796, this is ready to be merged right?

@ankita240796
Copy link
Contributor

Yes, thanks @jameesjohn! Thanks for doing this so well!

@ankita240796 ankita240796 merged commit d9c7fce into oppia:master Jun 17, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Oppiabot should detect PRs which affect datastore layers
3 participants