Jewell Simon

Missing Assignments Observer

Discussion created by Jewell Simon on Feb 12, 2020

New endpoint humans so let's get cooking. 

GET /api/v1/users/:user_id/missing_submissions

 

This endpoint is what I have been needing for some time in K-12. In fact I have had to bake it myself with a now retired c# LTI application that provided the parents with the same information.

 

proof of concept:

 

 

Now the look has changed a bit since that video was recorded (was still tweaking development) added total points possible as well, reduced text size from 1.5em to 1em, etc... but the core things are still there. (thank you Sara Mungall Thank You  for the vid). Enough chat here is the code:

 

function missingSubmissionsObserver() {
  const checkIfNull = async selector => {
    while (document.querySelector(selector) === null) {
      await new Promise(resolve => requestAnimationFrame(resolve));
    }
    return document.querySelector(selector);
  };
  checkIfNull(".recent-activity-header").then(() => {
    if (ENV.current_user_roles.includes("observer")) {

      const observeePage = document.querySelector(".recent-activity-header");
      const fetchObservees = `/api/v1/users/self/observees?per_page=20`;
      const options = {
        credentials: "same-origin",
        headers: { accept: "application/json" },
        timeout: 5000
      };
      let observees;
      let dataCollection = [];
      let missingSubmissionsContainer;
      let observeesListContainer;
      const borderStyle =`border-bottom: 1px solid #005299;padding-left:8px;`;
      const filteringConfig = {
        'l8 sub after': `2020-01-01T01:01:01Z`, //ISO or function
        'locked for a user': false, //bool
        'omit from final grade': false, //bool
        'display points possible if greater or equal to': 0, //num
        'submittable':`&filter[]=submittable` //either `&filter[]=submittable` || ``
      };
      //------------------------------------------------------------------
      const nextURL = linkTxt => {
        if (linkTxt) {
          let links = linkTxt.split(",");
          let nextRegEx = new RegExp('^<(.*)>; rel="next"$');

          for (let i = 0; i < links.length; i++) {
            let matches = nextRegEx.exec(links[i]);
            if (matches && matches[1]) {
              //return right away
              //console.log(matches[1]);
              return matches[1];
            }
          }
        } else {
          return false;
        }
      };
      //--------------------------------------------------------------------
      const fetchObserveesID = async () => {
        const response = await fetch(fetchObservees, options);
        const info = await response.json();
        let rsp = {
          data: info,
          ok: response.ok,
          headers: response.headers
        };
        observees = rsp.data;
      };

      fetchObserveesID().then(async () => {
        observeePage.insertAdjacentHTML( 'beforebegin',
        `
        <h2 id="being-observed">Students Being Observed</h2>
        <a href="/grades" class="Button">View Grades</a>
        <div class="gameLoader pacMan"></div>
        <br>
        <h5>Click a name below to see missing submissions</h5>
        <div class="observees-list-container"></div>
        <div class="missing-submissions-container"></div>
        `
        )
        ;
        observeesListContainer = document.querySelector(
          ".observees-list-container"
        );
        missingSubmissionsContainer = document.querySelector(
          ".missing-submissions-container"
        );

        //LOOP
        observees.forEach(kid => {
          let missingListItem = document.createElement("li"); //create li
          let classesToAddOB = [`Button`]; //add more classes
          missingListItem.classList.add(...classesToAddOB);
          missingListItem.textContent = `${kid.name}`; //replace
          missingListItem.addEventListener(
            "click",
            async () =>
              await addMissingSub(kid.id).then(async () => {
                scrubMissingSubs(kid.name);
              }),
            { once: true }
          );
          observeesListContainer.appendChild(missingListItem);
        });
      });
      //------------------------------------------------------------------------------------------------------
      const addMissingSub = async progeny => {
        //console.log(progeny);
        //loading animation
        const pMan = document.querySelector(".gameLoader.pacMan");
        pMan.style.setProperty("--playState", "play");
        //loading animation
        const response = await fetch(
          `/api/v1/users/${progeny}/missing_submissions?include[]=course${filteringConfig['submittable']}&per_page=100`,
          options
        );
        const data1 = await response.json();
        let res1 = {
          data: data1,
          ok: response.ok,
          headers: response.headers
        };
        dataCollection = res1.data;
        //console.log(dataCollection);
        await loopy(res1);
      };
      //-----===========---------===========--------

      const loopy = async kidd => {
        if (nextURL(kidd.headers.get("Link")) === undefined) {
          return;
          //otherwise keep going.
        } else {
          const rspns = await fetch(nextURL(kidd.headers.get("Link")), options);
          let moreData = await rspns.json();
          let resP = {
            data: moreData,
            ok: rspns.ok,
            headers: rspns.headers
          };
          if (resP.ok) {
            //console.log(resP.data);
            dataCollection = dataCollection.concat(resP.data);
            // console.info(Object.keys(kidd.data).length);
            // console.info(Object.keys(dataCollection).length);
          }
          await loopy(resP);
        }
      };
      const scrubMissingSubs = async nomenclature => {
        let docFrag = new DocumentFragment();
        const dataView = document.createElement("div");
        let classesToAddDV = [`ic-Dashboard-Activity`]; //add more classes
        dataView.classList.add(...classesToAddDV);
        docFrag.appendChild(dataView);
        //dataView.appendChild(tHead);

        //header
        //LOOP
        let filteredDataCollection = dataCollection.filter(
          lex =>
            lex.due_at >= filteringConfig["l8 sub after"] &&
            lex.locked_for_user === filteringConfig["locked for a user"] &&
            lex.omit_from_final_grade === filteringConfig["omit from final grade"] &&
            lex.points_possible >= filteringConfig["display points possible if greater or equal to"]
        );
        filteredDataCollection.reverse();
        // console.log(filteredDataCollection);
        let totLen = Object.keys(filteredDataCollection).length;
        let totes = document.createElement("h3");
        totes.style.color = "#005299";
        totes.textContent = `${nomenclature} -> Current Total of Missing Assignments (${totLen})`;
        dataView.appendChild(totes);
        const kidTable = document.createElement("table");
        const kidHeaderRow = document.createElement("tr");
        const kidHeader1 = document.createElement("th");
        kidHeader1.style = borderStyle;
        kidHeader1.textContent = `Due as of`;
        const kidHeader2 = document.createElement("th");
        kidHeader2.style = borderStyle;
        kidHeader2.textContent = `Course Name`;
        const kidHeader3 = document.createElement("th");
        kidHeader3.style = borderStyle;
        kidHeader3.textContent = `Assignment Link`;
        const kidHeader4 = document.createElement("th");
        kidHeader4.style = borderStyle;
        kidHeader4.textContent = `Points Possible`;
        kidHeaderRow.appendChild(kidHeader1);
        kidHeaderRow.appendChild(kidHeader2);
        kidHeaderRow.appendChild(kidHeader3);
        kidHeaderRow.appendChild(kidHeader4);
        kidTable.appendChild(kidHeaderRow);
        //LOOP
        filteredDataCollection.forEach(lateSub => {
          let kidR = document.createElement("tr");
          let dT = String(new Date(lateSub.due_at).toDateString());
          let nameTrunc = lateSub.name.substring(0, 50);
          let kid1 = document.createElement("td");
          kid1.style = borderStyle;
          let kid2 = document.createElement("td");
          kid2.style = borderStyle;
          let kid3 = document.createElement("td");
          kid3.style = borderStyle;
          let kidA = document.createElement("a");
          kidA.setAttribute(`href`, `${lateSub.html_url}`);
          let kid4 = document.createElement("td");
          kid4.style = borderStyle;
          kid1.textContent = ` ${dT} `;
          kid2.textContent = ` ${lateSub.course.name} `;
          kidA.textContent += ` ${nameTrunc} `;
          kid4.textContent = ` ${lateSub.points_possible} `;
          kidTable.appendChild(kidR);
          kidR.appendChild(kid1);
          kidR.appendChild(kid2);
          kid3.appendChild(kidA);
          kidR.appendChild(kid3);
          kidR.appendChild(kid4);
          dataView.appendChild(kidTable);
        });
        //loading animation
        document
          .querySelector(".gameLoader.pacMan")
          .style.setProperty("--playState", "paused");
        //loading animation
        missingSubmissionsContainer.after(docFrag);
      };
    }
  });
}
missingSubmissionsObserver();

NOTES:

-  anywhere there is a  ``` // //  ``` that is a custom loading animation the pacMan, but you can easily replace with the canvas native loader. If any of the 5 people that read this would like to use the filly animations that I use let me know and I will post as well.

-  there is a bit more filtering that is available for the grades displayed it is commented out under the ``` dataCollection ``` filter 

-  I don't like my appendChild process but it is easier to read that way vs. me extraping and throwing into convoluted functions so I have to force myself to get use to seeing such an eyesore (easier to troubleshoot incase I abandoned ship). But I probably will anyway it hurts my soul...

 

Edit: 2020-02-15T15:58:38Z implemented proposed changes Robert Carroll the var is filteringConfig

Outcomes