diff --git a/backend/api/views.py b/backend/api/views.py index 8bb0cdc3..f7eac998 100644 --- a/backend/api/views.py +++ b/backend/api/views.py @@ -722,12 +722,9 @@ def compilation_pubsub_call(self, request, team, league_id, pk=None): submission.save() id = submission.id - # call to compile server - print('attempting call to compile server') - print('id:', id) + # Notify compile server through pubsub queue. data = str(id) data_bytestring = data.encode('utf-8') - print(type(data_bytestring)) pub(GCLOUD_PROJECT, GCLOUD_SUB_COMPILE_NAME, data_bytestring) # indicate submission being queued @@ -762,7 +759,7 @@ def compilation_update(self, request, team, league_id, pk=None): # Only if this submission is newer than what's already been processed, # update the submission history. # (to prevent reverting to older submissions that took longer to process) - if submission.id > team_sub.last_1_id: + if team_sub.last_1_id is None or submission.id > team_sub.last_1_id: team_sub.last_3_id = team_sub.last_2_id team_sub.last_2_id = team_sub.last_1_id team_sub.last_1_id = submission @@ -837,20 +834,6 @@ def team_compilation_status(self, request, team, league_id, pk=None): else: return Response({'status': None}, status.HTTP_200_OK) - @action(methods=['get'], detail=True) - def team_compilation_id(self, request, team, league_id, pk=None): - if pk != str(team.id): - return Response({'message': "Not authenticated"}, status.HTTP_401_UNAUTHORIZED) - - team_data = self.get_queryset().get(pk=pk) - comp_id = team_data.compiling_id - if comp_id is not None: - return Response({'compilation_id': comp_id}, status.HTTP_200_OK) - else: - # this is bad, replace with something thats actually None - # ^ TODO should address this - return Response({'compilation_id': -1}, status.HTTP_200_OK) - class ScrimmageViewSet(viewsets.GenericViewSet, mixins.ListModelMixin, diff --git a/backend/docs/SETUP.md b/backend/docs/SETUP.md index 2a250df9..29b705a0 100644 --- a/backend/docs/SETUP.md +++ b/backend/docs/SETUP.md @@ -91,7 +91,7 @@ Set the contents of this file into dev_settings_sensitive.py, as GOOGLE_APPLICAT From Google Cloud console, "Compute Engine" -> “Instance Templates”. Click on an old backend template, and then click on “Create similar”. Change the name to something descriptive enough and conventional. ("bc21-backend-template", for example, works well. Also I’ve found that including the current date and time in the name can help keep things straight.) For machine type, we've found the `n1-standard-n1` to be cheap and work well, especially providing enough memory. Check the checkbox of "Deploy a container image to this VM instance", and change the container image to the image name you've just written in the cloud build trigger. -Then, click "Advanced container options" to see a place to set environment variables. Find the variables set in `dev_settings_sensitive.py`, and set all of those keys/values here, too. (Here, these values should not be enclosed in quotes.) Note that these are un-editable; if you ever change environment variables, you'll have to make a new instance template. ("Create Similar" on the instance template's page is helpful here.) +Then, click "Advanced container options" to see a place to set environment variables. Find the variables set in `dev_settings_sensitive.py`, and set all of those keys/values here, too. (Here, these values should not be enclosed in quotes.) Note that these are un-editable; if you ever change environment variables, you'll have to make a new instance template. See the "Deploying new instance template" for more info on this. (For now, keep the boot disk the same; it may be good to change it to a later version down the road. Be sure to test that the VMs still work, though.) @@ -126,3 +126,9 @@ Make sure the CORS policy and Google Application credentials are all set up, as Delete old instance groups: go to "Compute Engine" -> "Instance groups", check any old instance groups that are no longer in use, and click "delete". Delete old instance template: go to "Compute Engine" -> "Instance templates", check any old templates that are no longer in use, and click "delete". Delete old, unused backend services and buckets, if you're up to it, instructions in previous section. But this can be a pain and is certainly not necessary. + +## Deploying new instance template +Sometimes you'll have to change your instance template (for example, if you change an environment variable). To do so: +Create a new instance template (if you're looking to make just small changes, "Create Similar" on the original instance template's page is helpful here). +Click on your already-present instance group in use, and on its page, click "Edit Group". Find the "instance template" dropdown and change to the newly created instance template. +Finally, click on "rolling restart/replace". Change operation from `Restart` to `Replace`, let maximum surge be 1 and **maximum unavailable be 0** (we don't want our server to go down). Wait for the spinning icons to become checkmarks. diff --git a/frontend/src/api.js b/frontend/src/api.js index b5719ddd..47079366 100644 --- a/frontend/src/api.js +++ b/frontend/src/api.js @@ -18,19 +18,13 @@ class Api { //----SUBMISSIONS---- - // TODO clean up a lot of old comments, print statements - // TODO provide more explanatory comments - // TODO there's a better wayy to work with 'submitting' in cookies - // TODO 'submitting' could probably use a better name - // TODO review code in the submissions js - // TODO errors in these callbacks should also display messages in frontend - //uploads a new submission to the google cloud bucket static newSubmission(submissionfile, callback){ - // submissionfile.append('_method', 'PUT'); - // get the url from the real api + // URLs which files are uploaded to are generated by the backend; + // call the backend api to get this link $.post(`${URL}/api/${LEAGUE}/submission/`) .done((data, status) => { + // Upload to the bucket console.log("got URL") Cookies.set('submission_id', data['submission_id']); $.ajax({ @@ -41,23 +35,26 @@ class Api { contentType: false }) .done((data, status) => { + // After upload is done, need to queue for compilation. + // See corresponding method of backend/api/views.py for more explanation. console.log(data, status) $.post(`${URL}/api/${LEAGUE}/submission/` +Cookies.get('submission_id') + `/compilation_pubsub_call/`) .done((data, status) => { - console.log("Definitely done!") - // console.log(data, status) - Cookies.set('submitting', 0) + Cookies.set('submission_upload_status', 1) }) .fail((xhr, status, error) => { console.log("Error in compilation update callback: ", xhr, status, error) + Cookies.set('submission_upload_status', 3) }) }) .fail((xhr, status, error) => { console.log("Error in put request of file to bucket: ", xhr, status, error) + Cookies.set('submission_upload_status', 3) }) }) .fail((xhr, status, error) => { console.log("Error in post request for upload: ", xhr, status, error) + Cookies.set('submission_upload_status', 3) }); } @@ -102,12 +99,6 @@ class Api { }); } - static getCompilationID(callback) { - $.get(`${URL}/api/${LEAGUE}/teamsubmission/${Cookies.get("team_id")}/team_compilation_id/`).done((data, status) => { - return data['compilation_id'] - }); - } - // note that this is a submission, not a teamsubmission, thing static getSubmissionStatus(callback) { $.get(`${URL}/api/${LEAGUE}/submission/${Cookies.get("submission_id")}/get_status/`).done((data, status) => { diff --git a/frontend/src/views/submissions.js b/frontend/src/views/submissions.js index 2e8884b8..0aefbfd3 100755 --- a/frontend/src/views/submissions.js +++ b/frontend/src/views/submissions.js @@ -13,6 +13,7 @@ class Submissions extends Component { super(props); this.state = { selectedFile: null, + currentSubmission: null, lastSubmissions: null, tourSubmissions: null, numLastSubmissions: 0, @@ -30,7 +31,6 @@ class Submissions extends Component { } componentDidMount() { - Api.getCompilationStatus(this.gotStatus); Api.getTeamSubmissions(this.gotSubmissions); Api.getLeague(function (l) { this.setState({ league: l}); @@ -46,44 +46,49 @@ class Submissions extends Component { // makes an api call to upload the selected file - // TODO clean this method up - // TODO add explanation - // TODO submission table should be what exactly? - // Latest submission in progress, and last 3 good submissions? (and then make this clear in frontend) -- think I'm leaning towards this one - // Last 3 submissions, period? (this might need revisions in backend) - // TODO update how we display the most recent submission (including its status.) - // Also now that we have new statuses, we need to figue out what we should display in the frontend for each of them. - // (eg if user navigates away before the upload link is returned / before the upload finishes, or if submission fails to get queued/compiled, - // what should the user do? what should we tell them?) uploadData = () => { - // let status_str = "Submitting..." - Cookies.set('submitting', 1) - // console.log("submitting...") + // 'submission_upload_status' in Cookies is used to communicate between the functions in api.js and those in submissions.js. + // A value of 0 indicates that the submission is still in progress. + // When a submission finishes, api.js changes this value to something else. + Cookies.set('submission_upload_status', 0) + // The sub_status state is used internally by this component, to keep track of the submission upload process. + // (Currently, it mirrors submission_upload_status, but is part of state.) this.setState({sub_status: 0}) + + // Disable submission (for now), to prevent concurrent submissions. this.renderHelperSubmissionForm() this.renderHelperSubmissionStatus() Api.newSubmission(this.state.selectedFile, null) + // The method in api.js will change Cookies' submission_upload_status during the process of an upload. + // To check changes, we poll periodically. this.interval = setInterval(() => { - if (Cookies.get('submitting') != 1) { - // console.log("out of time loop") + let submission_upload_status = Cookies.get('submission_upload_status'); + if (submission_upload_status != 0) { + // Submission process terminated (see api.js). + + // refresh the submission status, for use on this component + if (submission_upload_status == 1) { + this.setState({sub_status: 1}) + } + if (submission_upload_status == 3) { + this.setState({sub_status: 3}) + } - // refresh the submission button and status - this.setState({sub_status: 1}) + // refresh the submission button, etc, to allow for a new submission this.renderHelperSubmissionForm() this.renderHelperSubmissionStatus() - // refresh team submission listing + // refresh team submission tables, to display the submission that just occured Api.getTeamSubmissions(this.gotSubmissions); + this.renderHelperCurrentTable() this.renderHelperLastTable() + // Done waiting for changes to submission_upload_status, so stop polling. clearInterval(this.interval) } - else { - // console.log("in time loop") - } - }, 1000); + }, 1000); // Poll every second } // change handler called when file is selected @@ -100,19 +105,15 @@ class Submissions extends Component { //---GETTING TEAMS SUBMISSION DATA---- + KEYS_CURRENT = ['compiling'] KEYS_LAST = ['last_1', 'last_2', 'last_3'] KEYS_TOUR = ['tour_final', 'tour_qual', 'tour_seed', 'tour_sprint', 'tour_hs', 'tour_intl_qual', 'tour_newbie'] - // called when status of teams compilation request is received - // 0 = in progress, 1 = succeeded, 2 = failed, 3 = server failed - gotStatus = (data) => { - this.setState(data) - } - // called when submission data is initially received // this will be maps of the label of type of submission to submission id // this function then makes calles to get the specific data for each submission gotSubmissions = (data) => { + this.setState({currentSubmission: new Array(this.submissionHelper(this.KEYS_CURRENT, data)).fill({})}) this.setState({lastSubmissions: new Array(this.submissionHelper(this.KEYS_LAST, data)).fill({})}) this.setState({tourSubmissions: new Array(this.submissionHelper(this.KEYS_TOUR, data)).fill([])}) } @@ -135,7 +136,13 @@ class Submissions extends Component { setSubmissionData = (key, data) => { let index, add_data - if (this.KEYS_LAST.includes(key)) { + if (this.KEYS_CURRENT.includes(key)) { + index = 0 + const arr = this.state["currentSubmission"] + let newArr = arr.slice(0, index) + newArr.push(data) + this.setState({["currentSubmission"]: newArr.concat(arr.slice(index + 1))}) + } else if (this.KEYS_LAST.includes(key)) { switch (key) { case 'last_1': index = 0 @@ -223,6 +230,7 @@ class Submissions extends Component { button = } } + // Make sure to disable concurrent submission uploads. if (this.state.sub_status != 0) { file_button_sub =