Missing Assignments Observer

jsimon3
Community Participant

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:

https://youtu.be/K-1KWvnWXAY

 

. 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=30`;
      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 tableClasses = [`ic-Table`, `ic-Table--condensed`];
      const filteringConfig = {
        'l8 sub after': `2020-08-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);
        });
      });
missingSubmissionsObserver()

 

 

NOTES:

The custom loading animation the pacMan, but you can easily replace with the canvas native loader. 

 

 

/*
 _ __   __ _  ___ _ __ ___   __ _ _ __  
| '_ \ / _` |/ __| '_ ` _ \ / _` | '_ \ 
| |_) | (_| | (__| | | | | | (_| | | | |
| .__/ \__,_|\___|_| |_| |_|\__,_|_| |_|
| |                                     
|_|                                     
*/
.gameLoader {
  width: 15px;
  height: 15px;
  position: relative;
  display: inline-block;
  margin-left: 7px;
}
.gameLoader::before,
.gameLoader::after {
  content: "";
  position: absolute;
}
.pacMan {
  border-radius: 50%;
  width: 4px;
  height: 4px;
  -webkit-animation-name: pacmanDot;
  animation-name: pacmanDot;
  -webkit-transform: translateX(14px);
  transform: translateX(14px);
}
.pacMan::before,
.pacMan::after {
  border-radius: 50%;
  border: 14px solid #005299;
  border-right-color: transparent;
  top: -12px;
  left: -24px;
}
.pacMan,
.pacMan::before,
.pacMan::after {
  -webkit-animation-duration: 0.5s;
  animation-duration: 0.5s;
  -webkit-animation-timing-function: linear;
  animation-timing-function: linear;
  -webkit-animation-iteration-count: infinite;
  animation-iteration-count: infinite;
  animation-play-state: var(--playState, paused);
}
.pacMan::before {
  -webkit-animation-name: upperJaw;
  animation-name: upperJaw;
}
.pacMan::after {
  -webkit-animation-name: lowerJaw;
  animation-name: lowerJaw;
}
@-webkit-keyframes pacmanDot {
  0%,
  50% {
    background: #008000;
  }
  51%,
  100% {
    background: none;
  }
  0%,
  100% {
    -webkit-transform: translateX(19px);
    transform: translateX(19px);
  }
  50% {
    -webkit-transform: translateX(12px);
    transform: translateX(12px);
  }
}
@keyframes pacmanDot {
  0%,
  50% {
    background: #008000;
  }
  51%,
  100% {
    background: none;
  }
  0%,
  100% {
    -webkit-transform: translateX(19px);
    transform: translateX(19px);
  }
  50% {
    -webkit-transform: translateX(12px);
    transform: translateX(12px);
  }
}
@-webkit-keyframes upperJaw {
  50% {
    -webkit-transform: rotate(50deg) translate(2px, -2px);
    transform: rotate(50deg) translate(2px, -2px);
  }
}
@keyframes upperJaw {
  50% {
    -webkit-transform: rotate(50deg) translate(2px, -2px);
    transform: rotate(50deg) translate(2px, -2px);
  }
}
@-webkit-keyframes lowerJaw {
  50% {
    -webkit-transform: rotate(-50deg) translate(2px, 2px);
    transform: rotate(-50deg) translate(2px, 2px);
  }
}
@keyframes lowerJaw {
  50% {
    -webkit-transform: rotate(-50deg) translate(2px, 2px);
    transform: rotate(-50deg) translate(2px, 2px);
  }
}

 

 

-  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  @r_carroll ‌ the var is filteringConfig