REST Service for POPCORN - ILIAS
alex
8 hours ago db2a97df620408dc8d76cf37c287bc5e7bf0e14d
GS-2555
2 files modified
348 ■■■■■ changed files
lib/libLp.js 124 ●●●●● patch | view | raw | blame | history
test/testKursLp.js 224 ●●●●● patch | view | raw | blame | history
lib/libLp.js
@@ -1,9 +1,12 @@
const _ = require("lodash")
const dayjs = require("dayjs")
/////////////////////////////////////////////////////////////////////////
module.exports = {
    alleAuswerten,
   alleAuswerten,
   auswerten,
   partitionDates,
}
/////////////////////////////////////////////////////////////////////////
@@ -14,58 +17,93 @@
 * @return {[usr_id,firstname,lastname,status,status_changed]}
 */
function alleAuswerten(data) {
    const grouped = _.groupBy(data, "usr_id")
    const ret = []
    for (const items of Object.values(grouped)) {
        if (!items.length) continue
        const unterStatusse = items.map(it => it.status)
   const grouped = _.groupBy(data, "usr_id")
   const ret = []
   for (const items of Object.values(grouped)) {
      if (!items.length) continue
        // TODO GS-2555 Datum berücksichtigen für die Auswertung
        const newStatus = auswerten(unterStatusse)
        const status_changed = _.max(items.map(u => u.status_changed))
      // const unterStatusse = items.map(it => it.status)
      // const newStatus = auswerten(unterStatusse)
      // const status_changed = _.max(items.map(u => u.status_changed))
        const {usr_id, login, firstname, lastname,} = items[0]
        const newItem = {
            usr_id, login, firstname, lastname, status: newStatus, status_changed
        }
        ret.push(newItem)
    }
    return ret
      // TODO GS-2555 Datum berücksichtigen für die Auswertung
      const statusData = items.map(({status_changed, status}) => ({status_changed, status}))
      const newStatus = auswerten(statusData)
      const status_changed = _.max(items.map(u => u.status_changed))
      const {usr_id, login, firstname, lastname,} = items[0]
      const newItem = {
         usr_id, login, firstname, lastname, status: newStatus, status_changed
      }
      ret.push(newItem)
   }
   return ret
}
function auswerten(unterStatusse) {
    // unter Status auswerten
    // 0 = noch nicht bearbeitet
    // 1 = in Bearbeitung
    // 2 = bestanden
    // 3 = nicht bestanden
/**
 * Neuen Status berechnen
 * @param {{status,status_changed}[]} statusData
 * @return {number}
 */
function auswerten(statusData) {
   // unter Status auswerten
   // 0 = noch nicht bearbeitet
   // 1 = in Bearbeitung
   // 2 = bestanden
   // 3 = nicht bestanden
    // Fall 1: keine Unterstatussse vorhanden -> 0
    if (!unterStatusse.length) {
        return 0
    }
    // Fall 2: eines nicht bestanden -> alles nicht bestanden // 0,1,2,3 -> 3
    if (unterStatusse.some(u => u === 3)) {
        return 3
    }
   // Fall 1: keine Unterstatussse vorhanden -> 0
   if (!statusData.length) {
      return 0
   }
    // Fall 3: alle statusse gleich -> status // 0,0,0 1,1,1 2,2,2 3,3,3
    if (isAllSame(unterStatusse)) {
        return unterStatusse[0]
    }
   const maxDate = dayjs(_.max(statusData.map(it => it.status_changed)))
   const {inside, outside} = partitionDates(maxDate, statusData)
    // Fall 4: wenn eines in Bearbeitung -> in Bearbeitung // 0,1,0 2,1,2
    if (unterStatusse.some(u => u === 1 || u === 2)) {
        return 1
    }
   // Fall 2: eines nicht bestanden -> alles nicht bestanden // 0,1,2,3 -> 3
   // hier kommt es nicht darauf an ob noch Daten in outside sind
   if (inside.map(it => it.status).some(u => u === 3)) {
      return 3
   }
    // Fall 5: sonst 0
    return 0
   // Fall 3: alle statusse gleich -> status // 0,0,0 1,1,1 2,2,2 3,3,3
   // wenn outside nicht leer ist -> in Bearbeitung
   if (isAllSame(inside.map(it => it.status))) {
      // wenn keine Einträge in outside vorhanden sind einfach den Status zurückgeben
      if(!outside.length) return statusData[0].status
      // ansonsten in Bearbeitung
      return 1
   }
   // Fall 4: wenn eines in Bearbeitung -> in Bearbeitung // 0,1,0 2,1,2
   if (inside.map(it=>it.status).some(u => u === 1 || u === 2)) {
      return 1
   }
   // Fall 5: sonst 0
   return 0
}
/**
 * @param  {Date} maxDate
 * @param {{status, status_changed}[]} items
 */
function partitionDates(maxDate, items) {
   const [inside, outside] = _.partition(items, function (it) {
      return isInBallpark(maxDate, it.status_changed)
   })
   return {inside, outside}
}
function isInBallpark(maxDate, date) {
   const ballpark = {value: 1, unit: "month"}
   const diff = maxDate.diff(date, ballpark.unit, true)
   return diff <= ballpark.value
}
function isAllSame(items) {
    if (!items.length) return false
    const first = items[0]
    return items.every(it => it === first)
   if (!items.length) return false
   const first = items[0]
   return items.every(it => it === first)
}
test/testKursLp.js
@@ -4,6 +4,8 @@
const libIlias = require("../lib/libIlias")
const db = require("../lib/db")
const testData = require("./data")
const dayjs = require("dayjs");
const {auswerten, partitionDates} = require("../lib/libLp");
/////////////////////////////////////////////////////////////////////////
@@ -18,30 +20,208 @@
   afterEach(async function () {
   })
    describe("the function getKursUnterobjektLp", function () {
        it("should deliver the LP for the Kurs Unterobjekte", async function () {
            const res = await db.getKursUnterobjektLp(kursId)
            // console.table(res)
            expect(res).to.be.a("array")
            for(const item of res) {
                expect(item).to.have.property("obj_id").and.to.be.a("number")
                expect(item).to.have.property("item_id").and.to.be.a("number")
                expect(item).to.have.property("lpmode").and.to.be.a("number")
                expect(item).to.have.property("item_obj_id").and.to.be.a("number")
                expect(item).to.have.property("usr_id").and.to.be.a("number")
                expect(item).to.have.property("status").and.to.be.a("number")
                expect(item).to.have.property("status_changed").and.to.be.a("date")
            }
        })
    })
   describe("the function getKursUnterobjektLp", function () {
      it("should deliver the LP for the Kurs Unterobjekte", async function () {
         const res = await db.getKursUnterobjektLp(kursId)
         // console.table(res)
         expect(res).to.be.a("array")
         for (const item of res) {
            expect(item).to.have.property("obj_id").and.to.be.a("number")
            expect(item).to.have.property("item_id").and.to.be.a("number")
            expect(item).to.have.property("lpmode").and.to.be.a("number")
            expect(item).to.have.property("item_obj_id").and.to.be.a("number")
            expect(item).to.have.property("usr_id").and.to.be.a("number")
            expect(item).to.have.property("status").and.to.be.a("number")
            expect(item).to.have.property("status_changed").and.to.be.a("date")
         }
      })
   })
    describe("the function getKursLp", function () {
        it("should return the unified LP of a Kurs", async function () {
            const res = await db.getKursLp(kursId)
            console.table(res)
        })
    })
   describe("the function getKursLp", function () {
      it("should return the unified LP of a Kurs", async function () {
         const res = await db.getKursLp(kursId)
         console.table(res)
      })
   })
   describe('the function partitionDates()', function () {
      const maxDate = dayjs("2025-06-15T12:00:00")
      const item = (status, date) => ({status, status_changed: date})
      it("should return an object with inside and outside arrays", function () {
         const res = partitionDates(maxDate, [])
         expect(res).to.be.an("object")
         expect(res.inside).to.be.an("array")
         expect(res.outside).to.be.an("array")
      })
      it("should put an empty item list into empty arrays", function () {
         const {inside, outside} = partitionDates(maxDate, [])
         expect(inside).to.have.lengthOf(0)
         expect(outside).to.have.lengthOf(0)
      })
      it("should put all items inside when all are within the last month", function () {
         const items = [
            item(0, dayjs(maxDate).subtract(1, "day").toDate()),
            item(1, dayjs(maxDate).subtract(2, "week").toDate()),
            item(2, dayjs(maxDate).subtract(3, "week").toDate()),
         ]
         const {inside, outside} = partitionDates(maxDate, items)
         expect(inside).to.have.lengthOf(3)
         expect(outside).to.have.lengthOf(0)
      })
      it("should put all items outside when all are older than a month", function () {
         const items = [
            item(0, dayjs(maxDate).subtract(2, "month").toDate()),
            item(1, dayjs(maxDate).subtract(1, "year").toDate()),
         ]
         const {inside, outside} = partitionDates(maxDate, items)
         expect(inside).to.have.lengthOf(0)
         expect(outside).to.have.lengthOf(2)
      })
      it("should treat an item exactly one month before maxDate as inside", function () {
         const it0 = item(0, dayjs(maxDate).subtract(1, "month").toDate())
         const {inside, outside} = partitionDates(maxDate, [it0])
         expect(inside).to.deep.equal([it0])
         expect(outside).to.have.lengthOf(0)
      })
      it("should treat an item slightly more than one month before maxDate as outside", function () {
         const it0 = item(0, dayjs(maxDate).subtract(1, "month").subtract(1, "second").toDate())
         const {inside, outside} = partitionDates(maxDate, [it0])
         expect(inside).to.have.lengthOf(0)
         expect(outside).to.deep.equal([it0])
      })
      it("should treat an item at the same instant as maxDate as inside", function () {
         const it0 = item(2, maxDate.toDate())
         const {inside, outside} = partitionDates(maxDate, [it0])
         expect(inside).to.deep.equal([it0])
         expect(outside).to.have.lengthOf(0)
      })
      it("should treat an item after maxDate as inside", function () {
         const it0 = item(1, dayjs(maxDate).add(1, "day").toDate())
         const {inside, outside} = partitionDates(maxDate, [it0])
         expect(inside).to.deep.equal([it0])
         expect(outside).to.have.lengthOf(0)
      })
      it("should split a mixed list correctly", function () {
         const recent = item(2, dayjs(maxDate).subtract(10, "day").toDate())
         const old = item(3, dayjs(maxDate).subtract(6, "month").toDate())
         const veryOld = item(1, dayjs(maxDate).subtract(1, "year").toDate())
         const {inside, outside} = partitionDates(maxDate, [recent, old, veryOld])
         expect(inside).to.deep.equal([recent])
         expect(outside).to.deep.equal([old, veryOld])
      })
      it("should preserve the original item objects", function () {
         const recent = item(2, dayjs(maxDate).subtract(1, "day").toDate())
         const old = item(3, dayjs(maxDate).subtract(1, "year").toDate())
         const {inside, outside} = partitionDates(maxDate, [recent, old])
         expect(inside[0]).to.equal(recent)
         expect(outside[0]).to.equal(old)
      })
      it("should not mutate the input array", function () {
         const items = [
            item(0, dayjs(maxDate).subtract(1, "day").toDate()),
            item(2, dayjs(maxDate).subtract(2, "year").toDate()),
         ]
         const copy = items.slice()
         partitionDates(maxDate, items)
         expect(items).to.deep.equal(copy)
      })
   })
   describe('the function auswerten()', function () {
      const recent = () => dayjs().subtract(1, "day").toDate()
      const stale = () => dayjs().subtract(2, "month").toDate()
      const item = (status, date) => ({status, status_changed: date})
      // an "outside" element used to trigger the outside-handling branches
      const outsideItem = () => item(0, stale())
      /////// Fall 1: no sub statuses
      it("Fall 1: should return 0 for an empty list", function () {
         expect(auswerten([])).to.equal(0)
      })
      /////// Fall 2: any 'not passed' (3) -> 3
      it("Fall 2: should return 3 when a 3 is present", function () {
         expect(auswerten([item(3, recent()), item(2, recent())])).to.equal(3)
      })
      it("Fall 2: should return 3 when all statuses are 3", function () {
         expect(auswerten([item(3, recent()), item(3, recent())])).to.equal(3)
      })
      it("Fall 2: should return 3 for mixed 0,1,2,3", function () {
         expect(auswerten([item(0, recent()), item(1, recent()), item(2, recent()), item(3, recent())])).to.equal(3)
      })
      /////// Fall 3: all statuses equal
      it("Fall 3: should return 0 when all are 0 (no outside)", function () {
         expect(auswerten([item(0, recent()), item(0, recent())])).to.equal(0)
      })
      it("Fall 3: should return 1 when all are 1 (no outside)", function () {
         expect(auswerten([item(1, recent()), item(1, recent())])).to.equal(1)
      })
      it("Fall 3: should return 2 when all are 2 (no outside)", function () {
         expect(auswerten([item(2, recent()), item(2, recent())])).to.equal(2)
      })
      it("Fall 3: should return 3 when all are 3 (no outside)", function () {
         expect(auswerten([item(3, recent()), item(3, recent())])).to.equal(3)
      })
      it("Fall 3: should return the status of a single status (no outside)", function () {
         expect(auswerten([item(0, recent())])).to.equal(0)
         expect(auswerten([item(1, recent())])).to.equal(1)
         expect(auswerten([item(2, recent())])).to.equal(2)
         expect(auswerten([item(3, recent())])).to.equal(3)
      })
      it("Fall 3: should return 1 when all are equal but an outside element exists", function () {
         expect(auswerten([item(2, recent()), item(2, recent()), outsideItem()])).to.equal(1)
      })
      /////// Fall 4: something is in progress (1 or 2)
      it("Fall 4: should return 1 for 0,1,0", function () {
         expect(auswerten([item(0, recent()), item(1, recent()), item(0, recent())])).to.equal(1)
      })
      it("Fall 4: should return 1 for 2,1,2", function () {
         expect(auswerten([item(2, recent()), item(1, recent()), item(2, recent())])).to.equal(1)
      })
      it("Fall 4: should return 1 for 0,2", function () {
         expect(auswerten([item(0, recent()), item(2, recent())])).to.equal(1)
      })
      it("Fall 4: should return 1 for 0,1,2", function () {
         expect(auswerten([item(0, recent()), item(1, recent()), item(2, recent())])).to.equal(1)
      })
      /////// Fall 5: everything else
      it("Fall 5: should return 0 when only 0 and no 3/1/2 mixed in", function () {
         expect(auswerten([item(0, recent()), item(0, recent()), item(0, recent())])).to.equal(0)
      })
   })
})