<commit_before>import * as React from "react"
import { disposeOnUnmount, observer } from "../src"
import { createTestRoot, asyncReactDOMRender } from "./"

const testRoot = createTestRoot()

async function testComponent(C, afterMount, afterUnmount) {
    const ref = React.createRef()
    await asyncReactDOMRender(<C ref={ref} />, testRoot)

    let cref = ref.current
    expect(cref.methodA).not.toHaveBeenCalled()
    expect(cref.methodB).not.toHaveBeenCalled()
    if (afterMount) {
        afterMount(cref)
    }

    await asyncReactDOMRender(null, testRoot)

    expect(cref.methodA).toHaveBeenCalledTimes(1)
    expect(cref.methodB).toHaveBeenCalledTimes(1)
    if (afterUnmount) {
        afterUnmount(cref)
    }
}

describe("without observer", () => {
    test("class without componentWillUnmount", async () => {
        class C extends React.Component {
            @disposeOnUnmount
            methodA = jest.fn()
            @disposeOnUnmount
            methodB = jest.fn()
            @disposeOnUnmount
            methodC = null
            @disposeOnUnmount
            methodD = undefined

            render() {
                return null
            }
        }

        await testComponent(C)
    })

    test("class with componentWillUnmount in the prototype", async () => {
        let called = 0

        class C extends React.Component {
            @disposeOnUnmount
            methodA = jest.fn()
            @disposeOnUnmount
            methodB = jest.fn()
            @disposeOnUnmount
            methodC = null
            @disposeOnUnmount
            methodD = undefined

            render() {
                return null
            }

            componentWillUnmount() {
                called++
            }
        }

        await testComponent(
            C,
            () => {
                expect(called).toBe(0)
            },
            () => {
                expect(called).toBe(1)
            }
        )
    })

    test("class with componentWillUnmount as an arrow function", async () => {
        let called = 0

        class C extends React.Component {
            @disposeOnUnmount
            methodA = jest.fn()
            @disposeOnUnmount
            methodB = jest.fn()
            @disposeOnUnmount
            methodC = null
            @disposeOnUnmount
            methodD = undefined

            render() {
                return null
            }

            componentWillUnmount = () => {
                called++
            }
        }

        await testComponent(
            C,
            () => {
                expect(called).toBe(0)
            },
            () => {
                expect(called).toBe(1)
            }
        )
    })

    test("class without componentWillUnmount using non decorator version", async () => {
        let methodC = jest.fn()
        let methodD = jest.fn()
        class C extends React.Component {
            render() {
                return null
            }

            methodA = disposeOnUnmount(this, jest.fn())
            methodB = disposeOnUnmount(this, jest.fn())

            constructor(props) {
                super(props)
                disposeOnUnmount(this, [methodC, methodD])
            }
        }

        await testComponent(
            C,
            () => {
                expect(methodC).not.toHaveBeenCalled()
                expect(methodD).not.toHaveBeenCalled()
            },
            () => {
                expect(methodC).toHaveBeenCalledTimes(1)
                expect(methodD).toHaveBeenCalledTimes(1)
            }
        )
    })
})

describe("with observer", () => {
    test("class without componentWillUnmount", async () => {
        @observer
        class C extends React.Component {
            @disposeOnUnmount
            methodA = jest.fn()
            @disposeOnUnmount
            methodB = jest.fn()
            @disposeOnUnmount
            methodC = null
            @disposeOnUnmount
            methodD = undefined

            render() {
                return null
            }
        }

        await testComponent(C)
    })

    test("class with componentWillUnmount in the prototype", async () => {
        let called = 0

        @observer
        class C extends React.Component {
            @disposeOnUnmount
            methodA = jest.fn()
            @disposeOnUnmount
            methodB = jest.fn()
            @disposeOnUnmount
            methodC = null
            @disposeOnUnmount
            methodD = undefined

            render() {
                return null
            }

            componentWillUnmount() {
                called++
            }
        }

        await testComponent(
            C,
            () => {
                expect(called).toBe(0)
            },
            () => {
                expect(called).toBe(1)
            }
        )
    })

    test("class with componentWillUnmount as an arrow function", async () => {
        let called = 0

        @observer
        class C extends React.Component {
            @disposeOnUnmount
            methodA = jest.fn()
            @disposeOnUnmount
            methodB = jest.fn()
            @disposeOnUnmount
            methodC = null
            @disposeOnUnmount
            methodD = undefined

            render() {
                return null
            }

            componentWillUnmount = () => {
                called++
            }
        }

        await testComponent(
            C,
            () => {
                expect(called).toBe(0)
            },
            () => {
                expect(called).toBe(1)
            }
        )
    })

    test("class without componentWillUnmount using non decorator version", async () => {
        let methodC = jest.fn()
        let methodD = jest.fn()

        @observer
        class C extends React.Component {
            render() {
                return null
            }

            methodA = disposeOnUnmount(this, jest.fn())
            methodB = disposeOnUnmount(this, jest.fn())

            constructor(props) {
                super(props)
                disposeOnUnmount(this, [methodC, methodD])
            }
        }

        await testComponent(
            C,
            () => {
                expect(methodC).not.toHaveBeenCalled()
                expect(methodD).not.toHaveBeenCalled()
            },
            () => {
                expect(methodC).toHaveBeenCalledTimes(1)
                expect(methodD).toHaveBeenCalledTimes(1)
            }
        )
    })
})

test("custom patching should work", async () => {
    class BaseComponent extends React.Component {
        constructor(props, context) {
            super(props, context)

            _makeAllSafe(this, BaseComponent.prototype, [
                "componentWillMount",
                "componentDidMount",
                "componentWillUpdate",
                "componentWillReceiveProps",
                "render",
                "componentDidUpdate",
                "componentWillUnmount"
            ])
        }

        componentDidMount() {
            this.didMountCalled = true
        }

        componentWillUnmount() {
            this.willUnmountCalled = true
        }
    }

    function _makeAllSafe(obj, prototype, methodNames) {
        for (let i = 0, len = methodNames.length; i < len; i++) {
            _makeSafe(obj, prototype, methodNames[i])
        }
    }

    function _makeSafe(obj, prototype, methodName) {
        let classMethod = obj[methodName]
        let prototypeMethod = prototype[methodName]

        if (classMethod || prototypeMethod) {
            obj[methodName] = function() {
                this.patchRunFor = this.patchRunFor || []
                this.patchRunFor.push(methodName)

                let retVal

                if (prototypeMethod) {
                    retVal = prototypeMethod.apply(this, arguments)
                }
                if (classMethod !== prototypeMethod) {
                    retVal = classMethod.apply(this, arguments)
                }

                return retVal
            }
        }
    }

    @observer
    class C extends BaseComponent {
        @disposeOnUnmount
        methodA = jest.fn()
        @disposeOnUnmount
        methodB = jest.fn()
        @disposeOnUnmount
        methodC = null
        @disposeOnUnmount
        methodD = undefined

        render() {
            return null
        }
    }

    await testComponent(
        C,
        ref => {
            expect(ref.patchRunFor).toEqual(["render", "componentDidMount"])
            expect(ref.didMountCalled).toBeTruthy()
        },
        ref => {
            expect(ref.patchRunFor).toEqual(["render", "componentDidMount", "componentWillUnmount"])
            expect(ref.willUnmountCalled).toBeTruthy()
        }
    )
})

describe("super calls should work", async () => {
    async function doTest(baseObserver, cObserver) {
        const events = []

        const sharedMethod = jest.fn()

        class BaseComponent extends React.Component {
            @disposeOnUnmount
            method0 = sharedMethod

            @disposeOnUnmount
            methodA = jest.fn()

            componentDidMount() {
                events.push("baseDidMount")
            }

            componentWillUnmount() {
                events.push("baseWillUnmount")
            }
        }

        class C extends BaseComponent {
            @disposeOnUnmount
            method0 = sharedMethod

            @disposeOnUnmount
            methodB = jest.fn()

            componentDidMount() {
                super.componentDidMount()
                events.push("CDidMount")
            }

            componentWillUnmount() {
                super.componentWillUnmount()
                events.push("CWillUnmount")
            }

            render() {
                return null
            }
        }

        if (baseObserver) {
            BaseComponent = observer(BaseComponent)
        }
        if (cObserver) {
            C = observer(C)
        }

        await testComponent(
            C,
            ref => {
                expect(events).toEqual(["baseDidMount", "CDidMount"])
                expect(sharedMethod).toHaveBeenCalledTimes(0)
            },
            ref => {
                expect(events).toEqual([
                    "baseDidMount",
                    "CDidMount",
                    "baseWillUnmount",
                    "CWillUnmount"
                ])
                expect(sharedMethod).toHaveBeenCalledTimes(2)
            }
        )
    }

    it("none is observer", async () => {
        await doTest(false, false)
    })
    it("base is observer", async () => {
        await doTest(true, false)
    })
    it("C is observer", async () => {
        await doTest(false, true)
    })
    it("both observers", async () => {
        await doTest(true, true)
    })
})

it("componentDidMount should be different between components", async () => {
    async function doTest(withObserver) {
        const events = []

        class A extends React.Component {
            componentDidMount() {
                this.didMount = "A"
                events.push("mountA")
            }

            componentWillUnmount() {
                this.willUnmount = "A"
                events.push("unmountA")
            }

            render() {
                return null
            }
        }

        class B extends React.Component {
            componentDidMount() {
                this.didMount = "B"
                events.push("mountB")
            }

            componentWillUnmount() {
                this.willUnmount = "B"
                events.push("unmountB")
            }

            render() {
                return null
            }
        }

        if (withObserver) {
            A = observer(A)
            B = observer(B)
        }

        const aRef = React.createRef()
        await asyncReactDOMRender(<A ref={aRef} />, testRoot)
        const caRef = aRef.current

        expect(caRef.didMount).toBe("A")
        expect(caRef.willUnmount).toBeUndefined()
        expect(events).toEqual(["mountA"])

        const bRef = React.createRef()
        await asyncReactDOMRender(<B ref={bRef} />, testRoot)
        const cbRef = bRef.current

        expect(caRef.didMount).toBe("A")
        expect(caRef.willUnmount).toBe("A")

        expect(cbRef.didMount).toBe("B")
        expect(cbRef.willUnmount).toBeUndefined()
        expect(events).toEqual(["mountA", "unmountA", "mountB"])

        await asyncReactDOMRender(null, testRoot)

        expect(caRef.didMount).toBe("A")
        expect(caRef.willUnmount).toBe("A")

        expect(cbRef.didMount).toBe("B")
        expect(cbRef.willUnmount).toBe("B")
        expect(events).toEqual(["mountA", "unmountA", "mountB", "unmountB"])
    }

    await doTest(true)
    await doTest(false)
})
<commit_msg>Some failing tests for disposeOnUnmount<commit_after>import * as React from "react"
import { disposeOnUnmount, observer } from "../src"
import { createTestRoot, asyncReactDOMRender } from "./"

const testRoot = createTestRoot()

async function testComponent(C, afterMount, afterUnmount) {
    const ref = React.createRef()
    await asyncReactDOMRender(<C ref={ref} />, testRoot)

    let cref = ref.current
    expect(cref.methodA).not.toHaveBeenCalled()
    expect(cref.methodB).not.toHaveBeenCalled()
    if (afterMount) {
        afterMount(cref)
    }

    await asyncReactDOMRender(null, testRoot)

    expect(cref.methodA).toHaveBeenCalledTimes(1)
    expect(cref.methodB).toHaveBeenCalledTimes(1)
    if (afterUnmount) {
        afterUnmount(cref)
    }
}

describe("without observer", () => {
    test("class without componentWillUnmount", async () => {
        class C extends React.Component {
            @disposeOnUnmount
            methodA = jest.fn()
            @disposeOnUnmount
            methodB = jest.fn()
            @disposeOnUnmount
            methodC = null
            @disposeOnUnmount
            methodD = undefined

            render() {
                return null
            }
        }

        await testComponent(C)
    })

    test("class with componentWillUnmount in the prototype", async () => {
        let called = 0

        class C extends React.Component {
            @disposeOnUnmount
            methodA = jest.fn()
            @disposeOnUnmount
            methodB = jest.fn()
            @disposeOnUnmount
            methodC = null
            @disposeOnUnmount
            methodD = undefined

            render() {
                return null
            }

            componentWillUnmount() {
                called++
            }
        }

        await testComponent(
            C,
            () => {
                expect(called).toBe(0)
            },
            () => {
                expect(called).toBe(1)
            }
        )
    })

    test("class with componentWillUnmount as an arrow function", async () => {
        let called = 0

        class C extends React.Component {
            @disposeOnUnmount
            methodA = jest.fn()
            @disposeOnUnmount
            methodB = jest.fn()
            @disposeOnUnmount
            methodC = null
            @disposeOnUnmount
            methodD = undefined

            render() {
                return null
            }

            componentWillUnmount = () => {
                called++
            }
        }

        await testComponent(
            C,
            () => {
                expect(called).toBe(0)
            },
            () => {
                expect(called).toBe(1)
            }
        )
    })

    test("class without componentWillUnmount using non decorator version", async () => {
        let methodC = jest.fn()
        let methodD = jest.fn()
        class C extends React.Component {
            render() {
                return null
            }

            methodA = disposeOnUnmount(this, jest.fn())
            methodB = disposeOnUnmount(this, jest.fn())

            constructor(props) {
                super(props)
                disposeOnUnmount(this, [methodC, methodD])
            }
        }

        await testComponent(
            C,
            () => {
                expect(methodC).not.toHaveBeenCalled()
                expect(methodD).not.toHaveBeenCalled()
            },
            () => {
                expect(methodC).toHaveBeenCalledTimes(1)
                expect(methodD).toHaveBeenCalledTimes(1)
            }
        )
    })
})

describe("with observer", () => {
    test("class without componentWillUnmount", async () => {
        @observer
        class C extends React.Component {
            @disposeOnUnmount
            methodA = jest.fn()
            @disposeOnUnmount
            methodB = jest.fn()
            @disposeOnUnmount
            methodC = null
            @disposeOnUnmount
            methodD = undefined

            render() {
                return null
            }
        }

        await testComponent(C)
    })

    test("class with componentWillUnmount in the prototype", async () => {
        let called = 0

        @observer
        class C extends React.Component {
            @disposeOnUnmount
            methodA = jest.fn()
            @disposeOnUnmount
            methodB = jest.fn()
            @disposeOnUnmount
            methodC = null
            @disposeOnUnmount
            methodD = undefined

            render() {
                return null
            }

            componentWillUnmount() {
                called++
            }
        }

        await testComponent(
            C,
            () => {
                expect(called).toBe(0)
            },
            () => {
                expect(called).toBe(1)
            }
        )
    })

    test("class with componentWillUnmount as an arrow function", async () => {
        let called = 0

        @observer
        class C extends React.Component {
            @disposeOnUnmount
            methodA = jest.fn()
            @disposeOnUnmount
            methodB = jest.fn()
            @disposeOnUnmount
            methodC = null
            @disposeOnUnmount
            methodD = undefined

            render() {
                return null
            }

            componentWillUnmount = () => {
                called++
            }
        }

        await testComponent(
            C,
            () => {
                expect(called).toBe(0)
            },
            () => {
                expect(called).toBe(1)
            }
        )
    })

    test("class without componentWillUnmount using non decorator version", async () => {
        let methodC = jest.fn()
        let methodD = jest.fn()

        @observer
        class C extends React.Component {
            render() {
                return null
            }

            methodA = disposeOnUnmount(this, jest.fn())
            methodB = disposeOnUnmount(this, jest.fn())

            constructor(props) {
                super(props)
                disposeOnUnmount(this, [methodC, methodD])
            }
        }

        await testComponent(
            C,
            () => {
                expect(methodC).not.toHaveBeenCalled()
                expect(methodD).not.toHaveBeenCalled()
            },
            () => {
                expect(methodC).toHaveBeenCalledTimes(1)
                expect(methodD).toHaveBeenCalledTimes(1)
            }
        )
    })
})

test("custom patching should work", async () => {
    class BaseComponent extends React.Component {
        constructor(props, context) {
            super(props, context)

            _makeAllSafe(this, BaseComponent.prototype, [
                "componentWillMount",
                "componentDidMount",
                "componentWillUpdate",
                "componentWillReceiveProps",
                "render",
                "componentDidUpdate",
                "componentWillUnmount"
            ])
        }

        componentDidMount() {
            this.didMountCalled = true
        }

        componentWillUnmount() {
            this.willUnmountCalled = true
        }
    }

    function _makeAllSafe(obj, prototype, methodNames) {
        for (let i = 0, len = methodNames.length; i < len; i++) {
            _makeSafe(obj, prototype, methodNames[i])
        }
    }

    function _makeSafe(obj, prototype, methodName) {
        let classMethod = obj[methodName]
        let prototypeMethod = prototype[methodName]

        if (classMethod || prototypeMethod) {
            obj[methodName] = function() {
                this.patchRunFor = this.patchRunFor || []
                this.patchRunFor.push(methodName)

                let retVal

                if (prototypeMethod) {
                    retVal = prototypeMethod.apply(this, arguments)
                }
                if (classMethod !== prototypeMethod) {
                    retVal = classMethod.apply(this, arguments)
                }

                return retVal
            }
        }
    }

    @observer
    class C extends BaseComponent {
        @disposeOnUnmount
        methodA = jest.fn()
        @disposeOnUnmount
        methodB = jest.fn()
        @disposeOnUnmount
        methodC = null
        @disposeOnUnmount
        methodD = undefined

        render() {
            return null
        }
    }

    await testComponent(
        C,
        ref => {
            expect(ref.patchRunFor).toEqual(["render", "componentDidMount"])
            expect(ref.didMountCalled).toBeTruthy()
        },
        ref => {
            expect(ref.patchRunFor).toEqual(["render", "componentDidMount", "componentWillUnmount"])
            expect(ref.willUnmountCalled).toBeTruthy()
        }
    )
})

describe("super calls should work", async () => {
    async function doTest(baseObserver, cObserver) {
        const events = []

        const sharedMethod = jest.fn()

        class BaseComponent extends React.Component {
            @disposeOnUnmount
            method0 = sharedMethod

            @disposeOnUnmount
            methodA = jest.fn()

            componentDidMount() {
                events.push("baseDidMount")
            }

            componentWillUnmount() {
                events.push("baseWillUnmount")
            }
        }

        class C extends BaseComponent {
            @disposeOnUnmount
            method0 = sharedMethod

            @disposeOnUnmount
            methodB = jest.fn()

            componentDidMount() {
                super.componentDidMount()
                events.push("CDidMount")
            }

            componentWillUnmount() {
                super.componentWillUnmount()
                events.push("CWillUnmount")
            }

            render() {
                return null
            }
        }

        if (baseObserver) {
            BaseComponent = observer(BaseComponent)
        }
        if (cObserver) {
            C = observer(C)
        }

        await testComponent(
            C,
            ref => {
                expect(events).toEqual(["baseDidMount", "CDidMount"])
                expect(sharedMethod).toHaveBeenCalledTimes(0)
            },
            ref => {
                expect(events).toEqual([
                    "baseDidMount",
                    "CDidMount",
                    "baseWillUnmount",
                    "CWillUnmount"
                ])
                expect(sharedMethod).toHaveBeenCalledTimes(2)
            }
        )
    }

    it("none is observer", async () => {
        await doTest(false, false)
    })
    it("base is observer", async () => {
        await doTest(true, false)
    })
    it("C is observer", async () => {
        await doTest(false, true)
    })
    it("both observers", async () => {
        await doTest(true, true)
    })
})

it("componentDidMount should be different between components", async () => {
    async function doTest(withObserver) {
        const events = []

        class A extends React.Component {
            componentDidMount() {
                this.didMount = "A"
                events.push("mountA")
            }

            componentWillUnmount() {
                this.willUnmount = "A"
                events.push("unmountA")
            }

            render() {
                return null
            }
        }

        class B extends React.Component {
            componentDidMount() {
                this.didMount = "B"
                events.push("mountB")
            }

            componentWillUnmount() {
                this.willUnmount = "B"
                events.push("unmountB")
            }

            render() {
                return null
            }
        }

        if (withObserver) {
            A = observer(A)
            B = observer(B)
        }

        const aRef = React.createRef()
        await asyncReactDOMRender(<A ref={aRef} />, testRoot)
        const caRef = aRef.current

        expect(caRef.didMount).toBe("A")
        expect(caRef.willUnmount).toBeUndefined()
        expect(events).toEqual(["mountA"])

        const bRef = React.createRef()
        await asyncReactDOMRender(<B ref={bRef} />, testRoot)
        const cbRef = bRef.current

        expect(caRef.didMount).toBe("A")
        expect(caRef.willUnmount).toBe("A")

        expect(cbRef.didMount).toBe("B")
        expect(cbRef.willUnmount).toBeUndefined()
        expect(events).toEqual(["mountA", "unmountA", "mountB"])

        await asyncReactDOMRender(null, testRoot)

        expect(caRef.didMount).toBe("A")
        expect(caRef.willUnmount).toBe("A")

        expect(cbRef.didMount).toBe("B")
        expect(cbRef.willUnmount).toBe("B")
        expect(events).toEqual(["mountA", "unmountA", "mountB", "unmountB"])
    }

    await doTest(true)
    await doTest(false)
})

describe("inheritance with prototype methods", async () => {
    async function doTest(patchBase, patchOther, callSuper) {
        let Bcall = 0
        let Ccall = 0
        let c = 0
        let c2 = 0

        class B extends React.Component {
            componentWillUnmount() {
                Bcall++
            }
        }

        class C extends B {
            componentWillUnmount() {
                if (callSuper) {
                    super.componentWillUnmount()
                }
                Ccall++
            }
            render() {
                return null
            }
        }

        if (patchBase) {
            let target = B.prototype
            target.fn = () => {
                c++
            }
            disposeOnUnmount(target, "fn")
        }

        if (patchOther) {
            let target2 = C.prototype
            target2.fn2 = () => {
                c2++
            }
            disposeOnUnmount(target2, "fn2")
        }

        await asyncReactDOMRender(<C />, testRoot)
        await asyncReactDOMRender(null, testRoot)

        expect(Ccall).toBe(1)
        expect(c).toBe(patchBase ? 1 : 0)
        expect(Bcall).toBe(callSuper ? 1 : 0)
        expect(c2).toBe(patchOther ? 1 : 0)
    }

    for (const base of [false, true]) {
        for (const other of [false, true]) {
            for (const callSuper of [false, true]) {
                test(`base: ${base}, other: ${other}, callSuper: ${callSuper}`, async () => {
                    if (base && !other && !callSuper) {
                        // this one is expected to fail, since we are patching only the base and the other one totally ignores the base method
                        try {
                            await doTest(base, other, callSuper)
                            fail("should have failed")
                        } catch (e) {}
                    } else {
                        await doTest(base, other, callSuper)
                    }
                })
            }
        }
    }
})

describe("inheritance with arrow functions", async () => {
    async function doTest(patchBase, patchOther, callSuper) {
        let Bcall = 0
        let Ccall = 0
        let c = 0
        let c2 = 0

        class B extends React.Component {
            constructor() {
                super()
                if (patchBase) {
                    this.fn = function() {
                        c++
                    }
                    disposeOnUnmount(this, this.fn)
                }
            }
            componentWillUnmount() {
                Bcall++
            }
        }

        class C extends B {
            constructor() {
                super()
                if (patchOther) {
                    this.fn2 = function() {
                        c2++
                    }
                    disposeOnUnmount(this, this.fn2)
                }
            }
            componentWillUnmount = () => {
                if (callSuper) {
                    super.componentWillUnmount()
                }
                Ccall++
            }
            render() {
                return null
            }
        }

        await asyncReactDOMRender(<C />, testRoot)
        await asyncReactDOMRender(null, testRoot)

        expect(c).toBe(patchBase ? 1 : 0)
        expect(c2).toBe(patchOther ? 1 : 0)
        expect(Ccall).toBe(1)
        expect(Bcall).toBe(callSuper ? 1 : 0)
    }

    for (const base of [false, true]) {
        for (const other of [false, true]) {
            for (const callSuper of [false, true]) {
                test(`base: ${base}, other: ${other}, callSuper: ${callSuper}`, async () => {
                    await doTest(base, other, callSuper)
                })
            }
        }
    }
})

test("base cWU should not be called if overriden", async () => {
    let baseCalled = 0
    let dCalled = 0
    let oCalled = 0

    class C extends React.Component {
        componentWillUnmount() {
            baseCalled++
        }

        constructor() {
            super()
            this.componentWillUnmount = () => {
                oCalled++
            }
        }

        render() {
            return null
        }

        @disposeOnUnmount
        fn() {
            dCalled++
        }
    }

    await asyncReactDOMRender(<C />, testRoot)
    await asyncReactDOMRender(null, testRoot)
    expect(dCalled).toBe(1)
    expect(oCalled).toBe(1)
    expect(baseCalled).toBe(0)
})
<|endoftext|>


<commit_before>import React, { PropTypes } from 'react';

import APINotLoaded from 'es6!display/not_loaded';
import SectionHeader from 'es6!display/section_header';
import { ChangesPage } from 'es6!display/page_chrome';
import { Grid, GridRow } from 'es6!display/grid';
import { Tabs, MenuUtils } from 'es6!display/menus';
import { display_duration } from 'es6!display/time';

import InteractiveData from 'es6!pages/helpers/interactive_data';

import * as api from 'es6!server/api';

import * as utils from 'es6!utils/utils';

var BuildTestsPage = React.createClass({

  menuItems: [
    'Failed/Skipped Tests',
    'Slow Tests',
  ],

  getInitialState: function() {
    return {
      selectedItem: null, // set in componentWillMount

      expandedTests: {},
      expandedTestsData: {}
    }
  },

  componentWillMount: function() {
    var selectedItemFromHash = MenuUtils.selectItemFromHash(
      window.location.hash, this.menuItems);

    // when we first came to this page, which tab was shown? Used by the
    // initial data fetching within tabs
    this.initialTab = selectedItemFromHash || 'Failed/Skipped Tests';

    this.setState({ selectedItem: this.initialTab });

    this.setState({
      slowTests : InteractiveData(this,
        'slowTests',
        `/api/0/builds/${this.props.buildID}/tests/?sort=duration`),
    });
  },

  componentDidMount: function() {
    _.each([['slowTests', 'Slow Tests']], tabs => {
      var [stateKey, tabName] = tabs;
      var params = {};
      if (this.initialTab === tabName) {
        var params = InteractiveData.getParamsFromWindowUrl();
      }
      this.state[stateKey].initialize(params);
    });

    api.fetch(this, {
      buildInfo: `/api/0/builds/${this.props.buildID}`,
      failedTests: `/api/0/builds/${this.props.buildID}/tests/failures`
    });
  },

  render: function() {
    if (!api.isLoaded(this.state.buildInfo)) {
      return <APINotLoaded state={this.state.buildInfo} />;
    }
    var buildInfo = this.state.buildInfo.getReturnedData();

    // render menu
    var selectedItem = this.state.selectedItem;

    var menu = <Tabs
      items={this.menuItems}
      selectedItem={selectedItem}
      onClick={MenuUtils.onClick(this, selectedItem)}
    />;

    var content = null;
    switch (selectedItem) {
      case 'Failed/Skipped Tests':
        content = this.renderFailed();
        break;
      case 'Slow Tests':
        content = this.renderSlow();
        break;
      // TODO: slow test files
      default:
        throw 'unreachable';
    }

    var name = buildInfo.project.name;
    return <ChangesPage highlight="Projects">
      <SectionHeader>Tests for {name} Build</SectionHeader>
      {menu}

{content}

    </ChangesPage>;
  },

  renderFailed: function() {
    if (!api.isLoaded(this.state.failedTests)) {
      return <APINotLoaded state={this.state.failedTests} />;
    }
    var failedTests = this.state.failedTests.getReturnedData();

    if (!failedTests) {
      return
No tests failed
;
    }

    var project_id = this.state.buildInfo.getReturnedData().project.id;

    var rows = [];
    _.each(failedTests, test => {
      console.log(test);
      var split_char = test.name.indexOf('/') >= 0 ? '/' : '.';
      var simple_name = _.last(test.name.split(split_char));
      var href = `/v2/project_test/${project_id}/${test.hash}`;

      var onClick = __ => {
        this.setState(
          utils.update_key_in_state_dict('expandedTests',
            test.test_id,
            !this.state.expandedTests[test.test_id])
        );

        if (!this.state.expandedTestsData[test.test_id]) {
          api.fetchMap(this, 'expandedTestsData', {
            [ test.test_id ]: `/api/0/tests/${test.test_id}/`
          });
        }
      };

      var markup = [

          {simple_name} Expand

{test.name}

      ];

      rows.push([
        markup,
        History,
      ]);

      if (this.state.expandedTests[test.test_id]) {
        if (!api.isLoaded(this.state.expandedTestsData[test.test_id])) {
          rows.push(GridRow.oneItem(
            <APINotLoaded
              className="marginTopM"
              calls={this.state.expandedTestsData[test.test_id]}
            />
          ));
        } else {
          var data = this.state.expandedTestsData[test.test_id].getReturnedData();
          rows.push(GridRow.oneItem(

              Captured Output

              {data.message}

          ));
        }
      }
    });

    var tests_by_result = _.groupBy(failedTests, t => t.result);
    var result_markup = _.map(tests_by_result, (tests, result) => {
      var sentence = utils.plural(tests.length, "test(s) " + result);
      var rest_of_words = _.rest(sentence.split(" ")).join(" ");

      return

        {tests.length}
        {" "}
        {rest_of_words}

;
    });

    return

      {result_markup}
      <Grid
        colnum={2}
        className="marginBottomM marginTopM"
        data={rows}
        headers={['Name', 'Links']}
      />

;
  },

  renderSlow: function() {
    var slowTestsInteractive = this.state.slowTests;

    // we want to update the window url whenever the user switches tabs
    slowTestsInteractive.updateWindowUrl();

    if (slowTestsInteractive.hasNotLoadedInitialData()) {
      return <APINotLoaded calls={slowTestsInteractive.getDataToShow()} />;
    }

    var slowTests = slowTestsInteractive.getDataToShow().getReturnedData();

    if (!slowTests) {
      return
No tests?!
;
    }

    var rows = [];
    _.each(slowTests, test => {
      rows.push([
        test.name,
        test.duration
      ]);
    });

    var pagingLinks = slowTestsInteractive.getPagingLinks();
    return

      <Grid
        colnum={2}
        className="marginBottomM marginTopM"
        data={rows}
        headers={['Name', 'Duration (ms)']}
      />

        {pagingLinks}

;
  },
});

export default BuildTestsPage;
<commit_msg>rename tab to Not Passing Tests<commit_after>import React, { PropTypes } from 'react';

import APINotLoaded from 'es6!display/not_loaded';
import SectionHeader from 'es6!display/section_header';
import { ChangesPage } from 'es6!display/page_chrome';
import { Grid, GridRow } from 'es6!display/grid';
import { Tabs, MenuUtils } from 'es6!display/menus';
import { display_duration } from 'es6!display/time';

import InteractiveData from 'es6!pages/helpers/interactive_data';

import * as api from 'es6!server/api';

import * as utils from 'es6!utils/utils';

var BuildTestsPage = React.createClass({

  menuItems: [
    'Not Passing Tests',
    'Slow Tests',
  ],

  getInitialState: function() {
    return {
      selectedItem: null, // set in componentWillMount

      expandedTests: {},
      expandedTestsData: {}
    }
  },

  componentWillMount: function() {
    var selectedItemFromHash = MenuUtils.selectItemFromHash(
      window.location.hash, this.menuItems);

    // when we first came to this page, which tab was shown? Used by the
    // initial data fetching within tabs
    this.initialTab = selectedItemFromHash || 'Not Passing Tests';

    this.setState({ selectedItem: this.initialTab });

    this.setState({
      slowTests : InteractiveData(this,
        'slowTests',
        `/api/0/builds/${this.props.buildID}/tests/?sort=duration`),
    });
  },

  componentDidMount: function() {
    _.each([['slowTests', 'Slow Tests']], tabs => {
      var [stateKey, tabName] = tabs;
      var params = {};
      if (this.initialTab === tabName) {
        var params = InteractiveData.getParamsFromWindowUrl();
      }
      this.state[stateKey].initialize(params);
    });

    api.fetch(this, {
      buildInfo: `/api/0/builds/${this.props.buildID}`,
      failedTests: `/api/0/builds/${this.props.buildID}/tests/failures`
    });
  },

  render: function() {
    if (!api.isLoaded(this.state.buildInfo)) {
      return <APINotLoaded state={this.state.buildInfo} />;
    }
    var buildInfo = this.state.buildInfo.getReturnedData();

    // render menu
    var selectedItem = this.state.selectedItem;

    var menu = <Tabs
      items={this.menuItems}
      selectedItem={selectedItem}
      onClick={MenuUtils.onClick(this, selectedItem)}
    />;

    var content = null;
    switch (selectedItem) {
      case 'Not Passing Tests':
        content = this.renderFailed();
        break;
      case 'Slow Tests':
        content = this.renderSlow();
        break;
      // TODO: slow test files
      default:
        throw 'unreachable';
    }

    var name = buildInfo.project.name;
    return <ChangesPage highlight="Projects">
      <SectionHeader>Tests for {name} Build</SectionHeader>
      {menu}

{content}

    </ChangesPage>;
  },

  renderFailed: function() {
    if (!api.isLoaded(this.state.failedTests)) {
      return <APINotLoaded state={this.state.failedTests} />;
    }
    var failedTests = this.state.failedTests.getReturnedData();

    if (!failedTests) {
      return
No tests failed
;
    }

    var project_id = this.state.buildInfo.getReturnedData().project.id;

    var rows = [];
    _.each(failedTests, test => {
      console.log(test);
      var split_char = test.name.indexOf('/') >= 0 ? '/' : '.';
      var simple_name = _.last(test.name.split(split_char));
      var href = `/v2/project_test/${project_id}/${test.hash}`;

      var onClick = __ => {
        this.setState(
          utils.update_key_in_state_dict('expandedTests',
            test.test_id,
            !this.state.expandedTests[test.test_id])
        );

        if (!this.state.expandedTestsData[test.test_id]) {
          api.fetchMap(this, 'expandedTestsData', {
            [ test.test_id ]: `/api/0/tests/${test.test_id}/`
          });
        }
      };

      var markup = [

          {simple_name} Expand

{test.name}

      ];

      rows.push([
        markup,
        History,
      ]);

      if (this.state.expandedTests[test.test_id]) {
        if (!api.isLoaded(this.state.expandedTestsData[test.test_id])) {
          rows.push(GridRow.oneItem(
            <APINotLoaded
              className="marginTopM"
              calls={this.state.expandedTestsData[test.test_id]}
            />
          ));
        } else {
          var data = this.state.expandedTestsData[test.test_id].getReturnedData();
          rows.push(GridRow.oneItem(

              Captured Output

              {data.message}

          ));
        }
      }
    });

    var tests_by_result = _.groupBy(failedTests, t => t.result);
    var result_markup = _.map(tests_by_result, (tests, result) => {
      var sentence = utils.plural(tests.length, "test(s) " + result);
      var rest_of_words = _.rest(sentence.split(" ")).join(" ");

      return

        {tests.length}
        {" "}
        {rest_of_words}

;
    });

    return

      {result_markup}
      <Grid
        colnum={2}
        className="marginBottomM marginTopM"
        data={rows}
        headers={['Name', 'Links']}
      />

;
  },

  renderSlow: function() {
    var slowTestsInteractive = this.state.slowTests;

    // we want to update the window url whenever the user switches tabs
    slowTestsInteractive.updateWindowUrl();

    if (slowTestsInteractive.hasNotLoadedInitialData()) {
      return <APINotLoaded calls={slowTestsInteractive.getDataToShow()} />;
    }

    var slowTests = slowTestsInteractive.getDataToShow().getReturnedData();

    var rows = [];
    _.each(slowTests, test => {
      rows.push([
        test.name,
        test.duration
      ]);
    });

    var pagingLinks = slowTestsInteractive.getPagingLinks();
    return

      <Grid
        colnum={2}
        className="marginBottomM marginTopM"
        data={rows}
        headers={['Name', 'Duration (ms)']}
      />

        {pagingLinks}

;
  },
});

export default BuildTestsPage;
<|endoftext|>


<commit_before>const config = require('config')
const Hapi = require('hapi')
const { resolve } = require('path')
const { serial, parallel } = require('items-promise')
const { normalizePath } = require('./utils')

const RouteTablePlugin = require('@bakjs/route-table')
const ShortcutsPlugin = require('@bakjs/shortcuts')
const InputPlugin = require('@bakjs/input')
const CompatPlugin = require('hapi-compat')
const LoggingPlugin = require('@bakjs/logging')

module.exports = class Server {
  /**
     *
     * @param bak
     */
  constructor (bak) {
    this.bak = bak
    this.options = this.bak.options
  }

  /**
     *
     * @returns {Promise.<Server>}
     */
  async init () {
    // Create server
    this.hapi = new Hapi.Server(this.options.server)

    // Register core plugins
    const corePlugins = [
      LoggingPlugin,
      ShortcutsPlugin,
      InputPlugin,
      {
        plugin: CompatPlugin,
        options: {
          server: this.hapi
        }
      }
    ]

    if (this.options.routeTable) {
      corePlugins.push(RouteTablePlugin)
    }

    await serial(corePlugins, this.register.bind(this))

    // Register plugins
    await serial(this.options.registrations, this.register.bind(this))

    // Register routes
    await this.registerRoutes(this.options.routes)

    return this
  }

  /**
     * Start server
     * @returns {Promise}
     * @private
     */
  async start () {
    await this.hapi.initialize()
    await this.hapi.start()
    return this
  }

  /**
     *
     * @param routes
     * @returns {Promise}
     * @private
     */
  async _loadRoutes (routes, dirty = false) {
    if (typeof routes === 'string') {
      dirty = true
      routes = require(resolve(this.options.relativeTo, routes))
    }

    if (routes instanceof Function) {
      dirty = true
      routes = new routes() // eslint-disable-line new-cap
      routes.server = this
      routes.hapi = this.hapi
    }

    if (routes.routes instanceof Function) {
      dirty = true
      routes = await routes.routes(this.bak)
    }

    if (!routes) {
      routes = []
    }

    if (!(routes instanceof Array)) {
      routes = [routes]
    }

    if (dirty) {
      routes = await parallel(routes, this._loadRoutes.bind(this))
      routes = Array.prototype.concat.apply([], routes)
    }

    return routes
  }

  /**
     *
     * @param routes
     * @param prefix
     * @returns {Promise<Array>}
     * @private
     */
  async _resolveRoutes (routes, prefix = '') {
    routes = await this._loadRoutes(routes, true)
    prefix = ((prefix && prefix.length) ? prefix : '') + '/'

    return Array.prototype.concat.apply([], await parallel(routes, async route => {
      // Simple nested routes
      if (route instanceof Array) {
        return this._resolveRoutes(route, prefix)
      }

      // Prefixed nested routes
      if (route instanceof Object && route.prefix) {
        const nestedPrefix = prefix + route.prefix
        return this._resolveRoutes(route.routes, nestedPrefix)
      }

      // Same level routes
      if (prefix.length) {
        route.path = normalizePath(prefix + (route.path || ''))
      }
      if (!route.path || !route.path.length) {
        route.path = '/'
      }
      if (!route.method) {
        route.method = 'GET'
      }

      return route
    }))
  }

  /**
     *
     * @param routes
     * @returns {Promise}
     */
  async registerRoutes (routes) {
    // Resolve
    routes = await this._resolveRoutes(routes, this.options.prefix)

    // Register
    return this.hapi.route(routes)
  }

  /**
     *
     * @param registration
     * @param registrationOptions
     * @returns {Promise}
     */
  register (registration, registrationOptions) {
    // Normalize registration into { plugin, [options], ... }
    if (!registration.plugin) {
      registration = {
        plugin: registration
      }
    }

    if (!registration.options) {
      registration.options = {}
    }

    // Resolve plugin
    if (typeof registration.plugin === 'string') {
      let path = registration.plugin
      if (this.options.relativeTo && path[0] === '.') {
        path = resolve(this.options.relativeTo, path)
      }
      registration.plugin = require(path)
    }

    // Resolve and merge configs for plugin
    const configKey = (registration.plugin.configKey || registration.plugin.name || (registration.plugin.pkg && registration.plugin.pkg.name) || '')
      .replace('@bakjs/', '')
      .replace('bak-', '')

    if (configKey.length) {
      // 1. from bak.config.js
      if (this.options[configKey]) {
        Object.assign(registration.options, this.options[configKey])
      }
      // 2. From config/
      if (config.has(configKey)) {
        Object.assign(registration.options, config.get(configKey))
      }
    }

    return this.hapi.register(registration, registrationOptions || {})
  }

  /**
   * Log error to hapi
   * @param {*} error
   * @param {*} request
   */
  logError (error, request = {}) {
    // Ignore Boom or null exceptions
    if (!error || error.isBoom) {
      return
    }

    const tags = [
      'error'
    ]

    const context = {
      error,
      payload: request.payload,
      query: request.query,
      user: request.auth.credentials
    }

    this.hapi.log(tags, context)
  }
}
<commit_msg>fix lint error<commit_after>const config = require('config')
const Hapi = require('hapi')
const { resolve } = require('path')
const { serial, parallel } = require('items-promise')
const { normalizePath } = require('./utils')

const RouteTablePlugin = require('@bakjs/route-table')
const ShortcutsPlugin = require('@bakjs/shortcuts')
const InputPlugin = require('@bakjs/input')
const CompatPlugin = require('hapi-compat')
const LoggingPlugin = require('@bakjs/logging')

module.exports = class Server {
  /**
     *
     * @param bak
     */
  constructor (bak) {
    this.bak = bak
    this.options = this.bak.options
  }

  /**
     *
     * @returns {Promise.<Server>}
     */
  async init () {
    // Create server
    this.hapi = new Hapi.Server(this.options.server)

    // Register core plugins
    const corePlugins = [
      LoggingPlugin,
      ShortcutsPlugin,
      InputPlugin,
      {
        plugin: CompatPlugin,
        options: {
          server: this.hapi
        }
      }
    ]

    if (this.options.routeTable) {
      corePlugins.push(RouteTablePlugin)
    }

    await serial(corePlugins, this.register.bind(this))

    // Register plugins
    await serial(this.options.registrations, this.register.bind(this))

    // Register routes
    await this.registerRoutes(this.options.routes)

    return this
  }

  /**
     * Start server
     * @returns {Promise}
     * @private
     */
  async start () {
    await this.hapi.initialize()
    await this.hapi.start()
    return this
  }

  /**
     *
     * @param routes
     * @returns {Promise}
     * @private
     */
  async _loadRoutes (routes, dirty = false) {
    if (typeof routes === 'string') {
      dirty = true
      routes = require(resolve(this.options.relativeTo, routes))
    }

    if (routes instanceof Function) {
      dirty = true
      routes = new routes() // eslint-disable-line new-cap
      routes.server = this
      routes.hapi = this.hapi
    }

    if (routes.routes instanceof Function) {
      dirty = true
      routes = await routes.routes(this.bak)
    }

    if (!routes) {
      routes = []
    }

    if (!(routes instanceof Array)) {
      routes = [routes]
    }

    if (dirty) {
      routes = await parallel(routes, this._loadRoutes.bind(this))
      routes = Array.prototype.concat.apply([], routes)
    }

    return routes
  }

  /**
     *
     * @param routes
     * @param prefix
     * @returns {Promise<Array>}
     * @private
     */
  async _resolveRoutes (routes, prefix = '') {
    routes = await this._loadRoutes(routes, true)
    prefix = ((prefix && prefix.length) ? prefix : '') + '/'

    return Array.prototype.concat.apply([], await parallel(routes, async route => {
      // Simple nested routes
      if (route instanceof Array) {
        return this._resolveRoutes(route, prefix)
      }

      // Prefixed nested routes
      if (route instanceof Object && route.prefix) {
        const nestedPrefix = prefix + route.prefix
        return this._resolveRoutes(route.routes, nestedPrefix)
      }

      // Same level routes
      if (prefix.length) {
        route.path = normalizePath(prefix + (route.path || ''))
      }
      if (!route.path || !route.path.length) {
        route.path = '/'
      }
      if (!route.method) {
        route.method = 'GET'
      }

      return route
    }))
  }

  /**
     *
     * @param routes
     * @returns {Promise}
     */
  async registerRoutes (routes) {
    // Resolve
    routes = await this._resolveRoutes(routes, this.options.prefix)

    // Register
    return this.hapi.route(routes)
  }

  /**
     *
     * @param registration
     * @param registrationOptions
     * @returns {Promise}
     */
  register (registration, registrationOptions) {
    // Normalize registration into { plugin, [options], ... }
    if (!registration.plugin) {
      registration = {
        plugin: registration
      }
    }

    if (!registration.options) {
      registration.options = {}
    }

    // Resolve plugin
    if (typeof registration.plugin === 'string') {
      let path = registration.plugin
      if (this.options.relativeTo && path[0] === '.') {
        path = resolve(this.options.relativeTo, path)
      }
      registration.plugin = require(path)
    }

    // Resolve and merge configs for plugin
    const configKey = (registration.plugin.configKey || registration.plugin.name || (registration.plugin.pkg && registration.plugin.pkg.name) || '')
      .replace('@bakjs/', '')
      .replace('bak-', '')

    if (configKey.length) {
      // 1. from bak.config.js
      if (this.options[configKey]) {
        Object.assign(registration.options, this.options[configKey])
      }
      // 2. From config/
      if (config.has(configKey)) {
        Object.assign(registration.options, config.get(configKey))
      }
    }

    return this.hapi.register(registration, registrationOptions || {})
  }

  /**
   * Log error to hapi
   * @param {*} error
   * @param {*} request
   */
  logError (error, request = {}) {
    // Ignore Boom or null exceptions
    if (!error || error.isBoom) {
      return
    }

    const tags = [
      'error'
    ]

    const context = {
      error,
      payload: request.payload,
      query: request.query,
      user: request.auth.credentials
    }

    this.hapi.log(tags, context)
  }
}
<|endoftext|>


<commit_before>import React from 'react';
import CentralColumnLayout from '../../containers/CentralColumnLayout/CentralColumnLayout';

import { SprkTable, SprkButton } from '@sparkdesignsystem/spark-core-react';
import ExampleContainer from '../../containers/ExampleContainer/ExampleContainer';

const SprkTableDocs = () => {
  return (
    <CentralColumnLayout>

      <ExampleContainer heading="Column Comparison">
        <SprkTable
          additionalTableClasses="sprk-b-Table--spacing-medium"
          idString="table-1"
          columns={[{name: 'data1', header: 'Column Heading'}, {name: 'data2', header: 'Column Heading'}, {name: 'data3', header: 'Column Heading'}, ]}
          data={[
            {data1: "Data 1", data2: "Data 2", data3: "Data 3"},
            {data1: "Data 1", data2: "Data 2", data3: "Data 3"},
            {data1: "Data 1", data2: "Data 2", data3: "Data 3"},
            {data1: "Data 1", data2: "Data 2", data3: "Data 3"},
          ]}
          >
        </SprkTable>
      </ExampleContainer>

      <ExampleContainer heading="Secondary Column Comparison">
        <SprkTable
          variant="secondary"
          additionalTableClasses="sprk-b-Table--spacing-medium"
          idString="table-2"
          columns={[{name: 'data1', header: 'Column Heading'}, {name: 'data2', header: 'Column Heading'}, {name: 'data3', header: 'Column Heading'}, ]}
          data={[
            {data1: "Data 1", data2: "Data 2", data3: "Data 3"},
            {data1: "Data 1", data2: "Data 2", data3: "Data 3"},
            {data1: "Data 1", data2: "Data 2", data3: "Data 3"},
            {data1: "Data 1", data2: "Data 2", data3: "Data 3"},
          ]}
          >
        </SprkTable>
      </ExampleContainer>

      <ExampleContainer heading="Grouped Columns">
        <SprkTable
          variant="grouped"
          additionalTableClasses="sprk-b-Table--spacing-medium"
          idString="table-3"
          columns={[
            [{name: 'data1', header: 'Data 1', rowspan: 2}, {name: 'data3', header: 'Data 3', colspan: 3}, {name: 'data2', header: 'Data 2', rowspan: 2}],
            [{name: 'data4', header: 'SubHeading 1'}, {name: 'data5', header: 'SubHeading 2'}, {name: 'data6', header: 'Subheading 3'}]
          ]}
          data={[
            {data1: "Data 1", data2: "Data 2",  data3: "Data 3", data4: 'Data 4', data5: 'Data 5'},
            {data1: "Data 1", data2: "Data 2", data3: "Data 3", data4: 'Data 4', data5: 'Data 5'},
            {data1: "Data 1", data2: "Data 2", data3: "Data 3", data4: 'Data 4', data5: 'Data 5'},
            {data1: "Data 1", data2: "Data 2", data3: "Data 3", data4: 'Data 4', data5: 'Data 5'},
          ]}
        >

        </SprkTable>
      </ExampleContainer>

      <ExampleContainer heading="Row Comparison">
        <SprkTable
          variant="rowComparison"
          additionalTableClasses="sprk-b-Table--spacing-medium"
          idString="table-5"
          columns={[{name: 'data1', header: "Column"}, {name: 'data2', header: 'Column'}, {name: "data3", header: 'Column'}]}
          data={[
            {rowHeading: 'Row Heading', data1: "Data", data2: "Data", data3: "Data"},
            {rowHeading: 'Row Heading', data1: "Data", data2: "Data", data3: "Data"},
            {rowHeading: 'Row Heading', data1: "Data", data2: "Data", data3: "Data"},
            {rowHeading: 'Row Heading', data1: "Data", data2: "Data", data3: "Data"},
          ]}
          >
        </SprkTable>
      </ExampleContainer>

      <ExampleContainer heading="Secondary Row Comparison">
        <SprkTable
          variant="secondaryRowComparison"
          additionalTableClasses="sprk-b-Table--spacing-medium"
          idString="table-5"
          columns={[{name: 'data1'}, {name: 'data2'}]}
          data={[
            {rowHeading: 'Row Heading', data1: "Data", data2: "Data", button: <SprkButton variant="secondary">Learn More</SprkButton> },
            {rowHeading: 'Row Heading', data1: "Data", data2: "Data", button: <SprkButton variant="secondary">Learn More</SprkButton> },
            {rowHeading: 'Row Heading', data1: "Data", data2: "Data", button: <SprkButton variant="secondary">Learn More</SprkButton> },
            {rowHeading: 'Row Heading', data1: "Data", data2: "Data", button: <SprkButton variant="secondary">Learn More</SprkButton> },
          ]}
          >
        </SprkTable>
      </ExampleContainer>

    </CentralColumnLayout>
  );
}

export default SprkTableDocs;<commit_msg>fix eslint errors<commit_after>import React from 'react';
import { SprkTable, SprkButton } from '@sparkdesignsystem/spark-core-react';
import CentralColumnLayout from '../../containers/CentralColumnLayout/CentralColumnLayout';
import ExampleContainer from '../../containers/ExampleContainer/ExampleContainer';

const SprkTableDocs = () => (
  <CentralColumnLayout>

    <ExampleContainer heading="Column Comparison">
      <SprkTable
        additionalTableClasses="sprk-b-Table--spacing-medium"
        idString="table-1"
        columns={[{ name: 'data1', header: 'Column Heading' }, { name: 'data2', header: 'Column Heading' }, { name: 'data3', header: 'Column Heading' }]}
        data={[
          { data1: 'Data 1', data2: 'Data 2', data3: 'Data 3' },
          { data1: 'Data 1', data2: 'Data 2', data3: 'Data 3' },
          { data1: 'Data 1', data2: 'Data 2', data3: 'Data 3' },
          { data1: 'Data 1', data2: 'Data 2', data3: 'Data 3' },
        ]}
      />
    </ExampleContainer>

    <ExampleContainer heading="Secondary Column Comparison">
      <SprkTable
        variant="secondary"
        additionalTableClasses="sprk-b-Table--spacing-medium"
        idString="table-2"
        columns={[{ name: 'data1', header: 'Column Heading' }, { name: 'data2', header: 'Column Heading' }, { name: 'data3', header: 'Column Heading' }]}
        data={[
          { data1: 'Data 1', data2: 'Data 2', data3: 'Data 3' },
          { data1: 'Data 1', data2: 'Data 2', data3: 'Data 3' },
          { data1: 'Data 1', data2: 'Data 2', data3: 'Data 3' },
          { data1: 'Data 1', data2: 'Data 2', data3: 'Data 3' },
        ]}
      />
    </ExampleContainer>

    <ExampleContainer heading="Grouped Columns">
      <SprkTable
        variant="grouped"
        additionalTableClasses="sprk-b-Table--spacing-medium"
        idString="table-3"
        columns={[
          [{ name: 'data1', header: 'Data 1', rowspan: 2 }, { name: 'data3', header: 'Data 3', colspan: 3 }, { name: 'data2', header: 'Data 2', rowspan: 2 }],
          [{ name: 'data4', header: 'SubHeading 1' }, { name: 'data5', header: 'SubHeading 2' }, { name: 'data6', header: 'Subheading 3' }],
        ]}
        data={[
          {
            data1: 'Data 1', data2: 'Data 2', data3: 'Data 3', data4: 'Data 4', data5: 'Data 5',
          },
          {
            data1: 'Data 1', data2: 'Data 2', data3: 'Data 3', data4: 'Data 4', data5: 'Data 5',
          },
          {
            data1: 'Data 1', data2: 'Data 2', data3: 'Data 3', data4: 'Data 4', data5: 'Data 5',
          },
          {
            data1: 'Data 1', data2: 'Data 2', data3: 'Data 3', data4: 'Data 4', data5: 'Data 5',
          },
        ]}
      />
    </ExampleContainer>

    <ExampleContainer heading="Row Comparison">
      <SprkTable
        variant="rowComparison"
        additionalTableClasses="sprk-b-Table--spacing-medium"
        idString="table-5"
        columns={[{ name: 'data1', header: 'Column' }, { name: 'data2', header: 'Column' }, { name: 'data3', header: 'Column' }]}
        data={[
          {
            rowHeading: 'Row Heading', data1: 'Data', data2: 'Data', data3: 'Data',
          },
          {
            rowHeading: 'Row Heading', data1: 'Data', data2: 'Data', data3: 'Data',
          },
          {
            rowHeading: 'Row Heading', data1: 'Data', data2: 'Data', data3: 'Data',
          },
          {
            rowHeading: 'Row Heading', data1: 'Data', data2: 'Data', data3: 'Data',
          },
        ]}
      />
    </ExampleContainer>

    <ExampleContainer heading="Secondary Row Comparison">
      <SprkTable
        variant="secondaryRowComparison"
        additionalTableClasses="sprk-b-Table--spacing-medium"
        idString="table-5"
        columns={[{ name: 'data1' }, { name: 'data2' }]}
        data={[
          {
            rowHeading: 'Row Heading', data1: 'Data', data2: 'Data', button: <SprkButton variant="secondary">Learn More</SprkButton>,
          },
          {
            rowHeading: 'Row Heading', data1: 'Data', data2: 'Data', button: <SprkButton variant="secondary">Learn More</SprkButton>,
          },
          {
            rowHeading: 'Row Heading', data1: 'Data', data2: 'Data', button: <SprkButton variant="secondary">Learn More</SprkButton>,
          },
          {
            rowHeading: 'Row Heading', data1: 'Data', data2: 'Data', button: <SprkButton variant="secondary">Learn More</SprkButton>,
          },
        ]}
      />
    </ExampleContainer>

  </CentralColumnLayout>
);

export default SprkTableDocs;
<|endoftext|>


<commit_before>'use strict';

// Setting up route
angular.module('core').config(['$stateProvider', '$urlRouterProvider',
	function($stateProvider, $urlRouterProvider) {
		// Redirect to home view when route not found
		$urlRouterProvider.otherwise('/');

		// Home state routing
		$stateProvider.
		state('home', {
			url: '/',
			templateUrl: 'modules/core/views/home.client.view.html'
		});
	}
]);<commit_msg>Set default route to checks module<commit_after>'use strict';

// Setting up route
angular.module('core').config(['$stateProvider', '$urlRouterProvider',
	function($stateProvider, $urlRouterProvider) {
		// Redirect to home view when route not found
		$urlRouterProvider.otherwise('/');

		// Home state routing
		$stateProvider.
		state('home', {
			url: '/',
			// templateUrl: 'modules/core/views/home.client.view.html'
			templateUrl: 'modules/checks/views/checks.client.view.html'
		});
	}
]);<|endoftext|>


<commit_before>import flash from '~/flash';
import { s__, sprintf } from '~/locale';

// Renders math using KaTeX in any element with the
// `js-render-math` class
//
// ### Example Markup
//
//

//

const MAX_MATH_CHARS = 1000;
const MAX_RENDER_TIME_MS = 2000;

// These messages might be used with inline errors in the future. Keep them around. For now, we will
// display a single error message using flash().

// const CHAR_LIMIT_EXCEEDED_MSG = sprintf(
//   s__(
//     'math|The following math is too long. For performance reasons, math blocks are limited to %{maxChars} characters. Try splitting up this block, or include an image instead.',
//   ),
//   { maxChars: MAX_MATH_CHARS },
// );
// const RENDER_TIME_EXCEEDED_MSG = s__(
//   "math|The math in this entry is taking too long to render. Any math below this point won't be shown. Consider splitting it among multiple entries.",
// );

const RENDER_FLASH_MSG = sprintf(
  s__(
    'math|The math in this entry is taking too long to render and may not be displayed as expected. For performance reasons, math blocks are also limited to %{maxChars} characters. Consider splitting up large formulae, splitting math blocks among multiple entries, or using an image instead.',
  ),
  { maxChars: MAX_MATH_CHARS },
);

// Wait for the browser to reflow the layout. Reflowing SVG takes time.
// This has to wrap the inner function, otherwise IE/Edge throw "invalid calling object".
const waitForReflow = fn => {
  window.requestAnimationFrame(fn);
};

/**
 * Renders math blocks sequentially while protecting against DoS attacks. Math blocks have a maximum character limit of MAX_MATH_CHARS. If rendering math takes longer than MAX_RENDER_TIME_MS, all subsequent math blocks are skipped and an error message is shown.
 */
class SafeMathRenderer {
  /*
  How this works:

  The performance bottleneck in rendering math is in the browser trying to reflow the generated SVG.
  During this time, the JS is blocked and the page becomes unresponsive.
  We want to render math blocks one by one until a certain time is exceeded, after which we stop
  rendering subsequent math blocks, to protect against DoS. However, browsers do reflowing in an
  asynchronous task, so we can't time it synchronously.

  SafeMathRenderer essentially does the following:
  1. Replaces all math blocks with placeholders so that they're not mistakenly rendered twice.
  2. Places each placeholder element in a queue.
  3. Renders the element at the head of the queue and waits for reflow.
  4. After reflow, gets the elapsed time since step 3 and repeats step 3 until the queue is empty.
   */
  queue = [];
  totalMS = 0;

  constructor(elements, katex) {
    this.elements = elements;
    this.katex = katex;

    this.renderElement = this.renderElement.bind(this);
    this.render = this.render.bind(this);
  }

  renderElement() {
    if (!this.queue.length) {
      return;
    }

    const el = this.queue.shift();
    const text = el.textContent;

    el.removeAttribute('style');

    if (this.totalMS >= MAX_RENDER_TIME_MS || text.length > MAX_MATH_CHARS) {
      if (!this.flashShown) {
        flash(RENDER_FLASH_MSG);
        this.flashShown = true;
      }

      // Show unrendered math code
      const codeElement = document.createElement('pre');
      codeElement.className = 'code';
      codeElement.textContent = el.textContent;
      el.parentNode.replaceChild(codeElement, el);

      // Render the next math
      this.renderElement();
    } else {
      this.startTime = Date.now();

      try {
        el.innerHTML = this.katex.renderToString(text, {
          displayMode: el.getAttribute('data-math-style') === 'display',
          throwOnError: true,
          maxSize: 20,
          maxExpand: 20,
        });
      } catch {
        // Don't show a flash for now because it would override an existing flash message
        el.textContent = s__('math|There was an error rendering this math block');
        // el.style.color = '#d00';
        el.className = 'katex-error';
      }

      // Give the browser time to reflow the svg
      waitForReflow(() => {
        const deltaTime = Date.now() - this.startTime;
        this.totalMS += deltaTime;

        this.renderElement();
      });
    }
  }

  render() {
    // Replace math blocks with a placeholder so they aren't rendered twice
    this.elements.forEach(el => {
      const placeholder = document.createElement('span');
      placeholder.style.display = 'none';
      placeholder.setAttribute('data-math-style', el.getAttribute('data-math-style'));
      placeholder.textContent = el.textContent;
      el.parentNode.replaceChild(placeholder, el);
      this.queue.push(placeholder);
    });

    // If we wait for the browser thread to settle down a bit, math rendering becomes 5-10x faster
    // and less prone to timeouts.
    setTimeout(this.renderElement, 400);
  }
}

export default function renderMath($els) {
  if (!$els.length) return;
  Promise.all([
    import(/* webpackChunkName: 'katex' */ 'katex'),
    import(/* webpackChunkName: 'katex' */ 'katex/dist/katex.min.css'),
  ])
    .then(([katex]) => {
      const renderer = new SafeMathRenderer($els.get(), katex);
      renderer.render();
    })
    .catch(() => {});
}
<commit_msg>Add argument to catch<commit_after>import flash from '~/flash';
import { s__, sprintf } from '~/locale';

// Renders math using KaTeX in any element with the
// `js-render-math` class
//
// ### Example Markup
//
//

//

const MAX_MATH_CHARS = 1000;
const MAX_RENDER_TIME_MS = 2000;

// These messages might be used with inline errors in the future. Keep them around. For now, we will
// display a single error message using flash().

// const CHAR_LIMIT_EXCEEDED_MSG = sprintf(
//   s__(
//     'math|The following math is too long. For performance reasons, math blocks are limited to %{maxChars} characters. Try splitting up this block, or include an image instead.',
//   ),
//   { maxChars: MAX_MATH_CHARS },
// );
// const RENDER_TIME_EXCEEDED_MSG = s__(
//   "math|The math in this entry is taking too long to render. Any math below this point won't be shown. Consider splitting it among multiple entries.",
// );

const RENDER_FLASH_MSG = sprintf(
  s__(
    'math|The math in this entry is taking too long to render and may not be displayed as expected. For performance reasons, math blocks are also limited to %{maxChars} characters. Consider splitting up large formulae, splitting math blocks among multiple entries, or using an image instead.',
  ),
  { maxChars: MAX_MATH_CHARS },
);

// Wait for the browser to reflow the layout. Reflowing SVG takes time.
// This has to wrap the inner function, otherwise IE/Edge throw "invalid calling object".
const waitForReflow = fn => {
  window.requestAnimationFrame(fn);
};

/**
 * Renders math blocks sequentially while protecting against DoS attacks. Math blocks have a maximum character limit of MAX_MATH_CHARS. If rendering math takes longer than MAX_RENDER_TIME_MS, all subsequent math blocks are skipped and an error message is shown.
 */
class SafeMathRenderer {
  /*
  How this works:

  The performance bottleneck in rendering math is in the browser trying to reflow the generated SVG.
  During this time, the JS is blocked and the page becomes unresponsive.
  We want to render math blocks one by one until a certain time is exceeded, after which we stop
  rendering subsequent math blocks, to protect against DoS. However, browsers do reflowing in an
  asynchronous task, so we can't time it synchronously.

  SafeMathRenderer essentially does the following:
  1. Replaces all math blocks with placeholders so that they're not mistakenly rendered twice.
  2. Places each placeholder element in a queue.
  3. Renders the element at the head of the queue and waits for reflow.
  4. After reflow, gets the elapsed time since step 3 and repeats step 3 until the queue is empty.
   */
  queue = [];
  totalMS = 0;

  constructor(elements, katex) {
    this.elements = elements;
    this.katex = katex;

    this.renderElement = this.renderElement.bind(this);
    this.render = this.render.bind(this);
  }

  renderElement() {
    if (!this.queue.length) {
      return;
    }

    const el = this.queue.shift();
    const text = el.textContent;

    el.removeAttribute('style');

    if (this.totalMS >= MAX_RENDER_TIME_MS || text.length > MAX_MATH_CHARS) {
      if (!this.flashShown) {
        flash(RENDER_FLASH_MSG);
        this.flashShown = true;
      }

      // Show unrendered math code
      const codeElement = document.createElement('pre');
      codeElement.className = 'code';
      codeElement.textContent = el.textContent;
      el.parentNode.replaceChild(codeElement, el);

      // Render the next math
      this.renderElement();
    } else {
      this.startTime = Date.now();

      try {
        el.innerHTML = this.katex.renderToString(text, {
          displayMode: el.getAttribute('data-math-style') === 'display',
          throwOnError: true,
          maxSize: 20,
          maxExpand: 20,
        });
      } catch (e) {
        // Don't show a flash for now because it would override an existing flash message
        el.textContent = s__('math|There was an error rendering this math block');
        // el.style.color = '#d00';
        el.className = 'katex-error';
      }

      // Give the browser time to reflow the svg
      waitForReflow(() => {
        const deltaTime = Date.now() - this.startTime;
        this.totalMS += deltaTime;

        this.renderElement();
      });
    }
  }

  render() {
    // Replace math blocks with a placeholder so they aren't rendered twice
    this.elements.forEach(el => {
      const placeholder = document.createElement('span');
      placeholder.style.display = 'none';
      placeholder.setAttribute('data-math-style', el.getAttribute('data-math-style'));
      placeholder.textContent = el.textContent;
      el.parentNode.replaceChild(placeholder, el);
      this.queue.push(placeholder);
    });

    // If we wait for the browser thread to settle down a bit, math rendering becomes 5-10x faster
    // and less prone to timeouts.
    setTimeout(this.renderElement, 400);
  }
}

export default function renderMath($els) {
  if (!$els.length) return;
  Promise.all([
    import(/* webpackChunkName: 'katex' */ 'katex'),
    import(/* webpackChunkName: 'katex' */ 'katex/dist/katex.min.css'),
  ])
    .then(([katex]) => {
      const renderer = new SafeMathRenderer($els.get(), katex);
      renderer.render();
    })
    .catch(() => {});
}
<|endoftext|>


<commit_before>define([], function() {
    var DevicePreviewFacade = function(top_node, devices, excludes){

        var that = this;
        var base_url = "/js/vendors/go-device-preview/img/";

        that.default_excludes = [
            ".device",
            "#stryke-db",
            "#qunit",
            "#qunit-fixture",
            "script"
        ];

        that.top_node = $(top_node).length==0?$("body"):$(top_node).first();
        that.device = "";
        that.mode = "landscape";
        that.excludes = [];
        that.excludes = merge(that.default_excludes, excludes);

        function more_param(loc, param_to_update) {
            loc = loc.replace(param_to_update + "&", "");

            if (loc.indexOf("?") == -1) {
                loc += "?" + param_to_update + "";
            } else {
                loc += "&" + param_to_update + "";
            }
            return loc;
        }
        function less_param(loc, param_to_update) {
            var params = param_to_update.split("=");

            if( params[1] == "" ){
                var pattern = ".*[&?]"+params[0]+"=([^&]*)"
                pattern = new RegExp(pattern,"i");
                var matches = loc.match(pattern)
                if ( matches ) {
                    param_to_update = params[0]+"="+matches[1];
                } else {
                    param_to_update = "";
                }
            }

            if( param_to_update != "" ){
                loc = loc.replace(param_to_update + "&", "");
                loc = loc.replace(param_to_update + "", "");

                if (loc.slice(loc.length - 1) == "&")
                    loc = loc.slice(0, loc.length - 1);
                else if (loc.slice(loc.length - 1) == "?")
                    loc = loc.slice(0, loc.length - 1);
            }
            return loc;
        }

        that.wrap_nodes = function(top_node){
            if( $(top_node).children(".device-preview-wrap").length == 0 ){
                var nodes = $("body").children();
                for(var n in that.excludes ){
                    nodes = nodes.not( that.excludes[n] );
                }
                nodes.remove();

                var location = window.location.href;
                location = less_param(location,"device=")
                location = less_param(location,"device_mode=")
                location = less_param(location,"no_dashboard=")
                location = more_param(location,"no_dashboard=true")
                $("<link rel='stylesheet' type='text/css' href='/js/vendors/go-device-preview/device-preview.css' />")
                    .appendTo(top_node);
                $("

")
                    .appendTo(top_node);
            }
        }
        that.unwrap_nodes = function(top_node){
        }
        that.DoFlip = function(){
            $('.device-screen').css("overflow-y", "");
            $('.device-screen').css( "width", "" );
            $('.device-screen').data("w", null);
            $(".device").removeClass(that.mode);
            that.mode = that.mode=="portrait"?"landscape":"portrait";
            that.EnableDevice(that.device)
        }
        that.EnableDeviceMode = function(mode){
            $('.device-screen').css("overflow-y", "");
            $('.device-screen').css( "width", "" );
            $('.device-screen').data("w", null);
            if( mode != that.mode && (mode == "portrait" || mode == "landscape") ){
                $(".device").removeClass(that.mode);
                that.mode = mode;
                that.EnableDevice(that.device)
            }else if (mode==""){
                that.DisableDevice()
            }
        }
        that.EnableDevice = function(device){

            $("html").removeClass("device-enabled");
            $(".device").removeClass(that.device)
            $(".device").removeClass(that.mode);
            that.device = device;
            if( device == "" ) return ;
            var top_node = that.top_node;
            that.wrap_nodes( top_node );

            $("html").removeClass("device-disabled");
            $("html").addClass("device-enabled");
            $(".device").addClass(that.device)
            $(".device").addClass(that.mode);

            $(".device-decoration").show();
        }
        that.DisableDevice = function(){
            $('.device-screen').css("overflow-y", "");
            $('.device-screen').css( "width", "" );
            $('.device-screen').data("w", null);
            $(".device").removeClass(that.device)
            $(".device").removeClass(that.mode);
            $("html").removeClass("device-enabled");
            $("html").addClass("device-disabled");
            if( that.device == "" ) return ;
            that.device = "";
        }

        function merge(arr,arr1){
            var retour = []
            for(var n in arr){
                if( in_array(retour,arr[n]) == false ){
                    retour.push(arr[n])
                }
            }
            for(var n in arr1){
                if( in_array(retour,arr1[n]) == false ){
                    retour.push(arr1[n])
                }
            }
            return retour;
        }
        function in_array(arr,v){
            for(var n in arr){
                if( arr[n] == v ){
                    return true;
                }
            }
            return false;
        }
    }

    return DevicePreviewFacade;
});
<commit_msg>added url_util dependance, fixed device wrapp detection within html tree<commit_after>define(["vendors/utils/url_util"], function(url_util) {
    url_util = new url_util();
    var DevicePreviewFacade = function(top_node, devices, excludes){

        var that = this;
        var base_url = "/js/vendors/go-device-preview/img/";

        that.default_excludes = [
            ".device",
            "#stryke-db",
            "#qunit",
            "#qunit-fixture",
            "script"
        ];

        that.top_node = $(top_node).length==0?$("body"):$(top_node).first();
        that.device = "";
        that.mode = "landscape";
        that.excludes = [];
        that.excludes = merge(that.default_excludes, excludes);

        that.wrap_nodes = function(top_node){
            if( $(top_node).find(".device-preview-wrap").length == 0 ){
                var nodes = $("body").children();
                for(var n in that.excludes ){
                    nodes = nodes.not( that.excludes[n] );
                }
                nodes.remove();

                var location = window.location.href;
                location = url_util.less_param(location,"device=")
                location = url_util.less_param(location,"device_mode=")
                location = url_util.less_param(location,"no_dashboard=")
                location = url_util.more_param(location,"no_dashboard=true")
                $("<link rel='stylesheet' type='text/css' href='/js/vendors/go-device-preview/device-preview.css' />")
                    .appendTo(top_node);
                $("

")
                    .appendTo(top_node);
            }
        }
        that.unwrap_nodes = function(top_node){
        }
        that.DoFlip = function(){
            $('.device-screen').css("overflow-y", "");
            $('.device-screen').css( "width", "" );
            $('.device-screen').data("w", null);
            $(".device").removeClass(that.mode);
            that.mode = that.mode=="portrait"?"landscape":"portrait";
            that.EnableDevice(that.device)
        }
        that.EnableDeviceMode = function(mode){
            $('.device-screen').css("overflow-y", "");
            $('.device-screen').css( "width", "" );
            $('.device-screen').data("w", null);
            if( mode != that.mode && (mode == "portrait" || mode == "landscape") ){
                $(".device").removeClass(that.mode);
                that.mode = mode;
                that.EnableDevice(that.device)
            }else if (mode==""){
                that.DisableDevice()
            }
        }
        that.EnableDevice = function(device){

            $("html").removeClass("device-enabled");
            $(".device").removeClass(that.device)
            $(".device").removeClass(that.mode);
            that.device = device;
            if( device == "" ) return ;
            var top_node = that.top_node;
            that.wrap_nodes( top_node );

            $("html").removeClass("device-disabled");
            $("html").addClass("device-enabled");
            $(".device").addClass(that.device)
            $(".device").addClass(that.mode);

            $(".device-decoration").show();
        }
        that.DisableDevice = function(){
            $('.device-screen').css("overflow-y", "");
            $('.device-screen').css( "width", "" );
            $('.device-screen').data("w", null);
            $(".device").removeClass(that.device)
            $(".device").removeClass(that.mode);
            $("html").removeClass("device-enabled");
            $("html").addClass("device-disabled");
            if( that.device == "" ) return ;
            that.device = "";
        }

        function merge(arr,arr1){
            var retour = []
            for(var n in arr){
                if( in_array(retour,arr[n]) == false ){
                    retour.push(arr[n])
                }
            }
            for(var n in arr1){
                if( in_array(retour,arr1[n]) == false ){
                    retour.push(arr1[n])
                }
            }
            return retour;
        }
        function in_array(arr,v){
            for(var n in arr){
                if( arr[n] == v ){
                    return true;
                }
            }
            return false;
        }
    }

    return DevicePreviewFacade;
});
<|endoftext|>


<commit_before>/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

// this function works exactly the same as UIUtils.formatDuration
function formatDuration(milliseconds) {
  if (milliseconds < 100) {
    return milliseconds + " ms";
  }
  var seconds = milliseconds * 1.0 / 1000;
  if (seconds < 1) {
    return seconds.toFixed(1) + " s";
  }
  if (seconds < 60) {
    return seconds.toFixed(0) + " s";
  }
  var minutes = seconds / 60;
  if (minutes < 10) {
    return minutes.toFixed(1) + " min";
  } else if (minutes < 60) {
    return minutes.toFixed(0) + " min";
  }
  var hours = minutes / 60;
  return hours.toFixed(1) + " h";
}

function makeIdNumeric(id) {
  var strs = id.split("_");
  if (strs.length < 3) {
    return id;
  }
  var appSeqNum = strs[2];
  var resl = strs[0] + "_" + strs[1] + "_";
  var diff = 10 - appSeqNum.length;
  while (diff > 0) {
      resl += "0"; // padding 0 before the app sequence number to make sure it has 10 characters
      diff--;
  }
  resl += appSeqNum;
  return resl;
}

function formatDate(date) {
  return date.split(".")[0].replace("T", " ");
}

function getParameterByName(name, searchString) {
  var regex = new RegExp("[\\?&]" + name + "=([^&#]*)"),
  results = regex.exec(searchString);
  return results === null ? "" : decodeURIComponent(results[1].replace(/\+/g, " "));
}

jQuery.extend( jQuery.fn.dataTableExt.oSort, {
    "title-numeric-pre": function ( a ) {
        var x = a.match(/title="*(-?[0-9\.]+)/)[1];
        return parseFloat( x );
    },

    "title-numeric-asc": function ( a, b ) {
        return ((a  b) ? 1 : 0));
    },

    "title-numeric-desc": function ( a, b ) {
        return ((a  b) ? -1 : 0));
    }
} );

jQuery.extend( jQuery.fn.dataTableExt.oSort, {
    "appid-numeric-pre": function ( a ) {
        var x = a.match(/title="*(-?[0-9a-zA-Z\-\_]+)/)[1];
        return makeIdNumeric(x);
    },

    "appid-numeric-asc": function ( a, b ) {
        return ((a  b) ? 1 : 0));
    },

    "appid-numeric-desc": function ( a, b ) {
        return ((a  b) ? -1 : 0));
    }
} );

$(document).ajaxStop($.unblockUI);
$(document).ajaxStart(function(){
    $.blockUI({ message: '
Loading history summary...
'});
});

$(document).ready(function() {
    $.extend( $.fn.dataTable.defaults, {
      stateSave: true,
      lengthMenu: [[20,40,60,100,-1], [20, 40, 60, 100, "All"]],
      pageLength: 20
    });

    historySummary = $("#history-summary");
    searchString = historySummary["context"]["location"]["search"];
    requestedIncomplete = getParameterByName("showIncomplete", searchString);
    requestedIncomplete = (requestedIncomplete == "true" ? true : false);

    $.getJSON("api/v1/applications", function(response,status,jqXHR) {
      var array = [];
      var hasMultipleAttempts = false;
      for (i in response) {
        var app = response[i];
        if (app["attempts"][0]["completed"] == requestedIncomplete) {
          continue; // if we want to show for Incomplete, we skip the completed apps; otherwise skip incomplete ones.
        }
        var id = app["id"];
        var name = app["name"];
        if (app["attempts"].length > 1) {
            hasMultipleAttempts = true;
        }
        var num = app["attempts"].length;
        for (j in app["attempts"]) {
          var attempt = app["attempts"][j];
          attempt["startTime"] = formatDate(attempt["startTime"]);
          attempt["endTime"] = formatDate(attempt["endTime"]);
          attempt["lastUpdated"] = formatDate(attempt["lastUpdated"]);
          var app_clone = {"id" : id, "name" : name, "num" : num, "attempts" : [attempt]};
          array.push(app_clone);
        }
      }

      var data = {"applications": array}
      $.get("static/historypage-template.html", function(template) {
        historySummary.append(Mustache.render($(template).filter("#history-summary-template").html(),data));
        var selector = "#history-summary-table";
        var conf = {
                    "columns": [
                        {name: 'first', type: "appid-numeric"},
                        {name: 'second'},
                        {name: 'third'},
                        {name: 'fourth'},
                        {name: 'fifth'},
                        {name: 'sixth', type: "title-numeric"},
                        {name: 'seventh'},
                        {name: 'eighth'},
                    ],
                    "autoWidth": false,
                    "order": [[ 4, "desc" ]]
        };

        var rowGroupConf = {
                           "rowsGroup": [
                               'first:name',
                               'second:name'
                           ],
        };

        if (hasMultipleAttempts) {
          jQuery.extend(conf, rowGroupConf);
          var rowGroupCells = document.getElementsByClassName("rowGroupColumn");
          for (i = 0; i < rowGroupCells.length; i++) {
            rowGroupCells[i].style='background-color: #ffffff';
          }
        }

        if (!hasMultipleAttempts) {
          var attemptIDCells = document.getElementsByClassName("attemptIDSpan");
          for (i = 0; i < attemptIDCells.length; i++) {
            attemptIDCells[i].style.display='none';
          }
        }

        var durationCells = document.getElementsByClassName("durationClass");
        for (i = 0; i < durationCells.length; i++) {
          var timeInMilliseconds = parseInt(durationCells[i].title);
          durationCells[i].innerHTML = formatDuration(timeInMilliseconds);
        }

        if ($(selector.concat(" tr")).length < 20) {
          $.extend(conf, {paging: false});
        }

        $(selector).DataTable(conf);
        $('#hisotry-summary [data-toggle="tooltip"]').tooltip();
      });
    });
});
<commit_msg>[SPARK-15641] HistoryServer to not show invalid date for incomplete application<commit_after>/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

// this function works exactly the same as UIUtils.formatDuration
function formatDuration(milliseconds) {
  if (milliseconds < 100) {
    return milliseconds + " ms";
  }
  var seconds = milliseconds * 1.0 / 1000;
  if (seconds < 1) {
    return seconds.toFixed(1) + " s";
  }
  if (seconds < 60) {
    return seconds.toFixed(0) + " s";
  }
  var minutes = seconds / 60;
  if (minutes < 10) {
    return minutes.toFixed(1) + " min";
  } else if (minutes < 60) {
    return minutes.toFixed(0) + " min";
  }
  var hours = minutes / 60;
  return hours.toFixed(1) + " h";
}

function makeIdNumeric(id) {
  var strs = id.split("_");
  if (strs.length < 3) {
    return id;
  }
  var appSeqNum = strs[2];
  var resl = strs[0] + "_" + strs[1] + "_";
  var diff = 10 - appSeqNum.length;
  while (diff > 0) {
      resl += "0"; // padding 0 before the app sequence number to make sure it has 10 characters
      diff--;
  }
  resl += appSeqNum;
  return resl;
}

function formatDate(date) {
  if (date <= 0) return "-";
  else return date.split(".")[0].replace("T", " ");
}

function getParameterByName(name, searchString) {
  var regex = new RegExp("[\\?&]" + name + "=([^&#]*)"),
  results = regex.exec(searchString);
  return results === null ? "" : decodeURIComponent(results[1].replace(/\+/g, " "));
}

jQuery.extend( jQuery.fn.dataTableExt.oSort, {
    "title-numeric-pre": function ( a ) {
        var x = a.match(/title="*(-?[0-9\.]+)/)[1];
        return parseFloat( x );
    },

    "title-numeric-asc": function ( a, b ) {
        return ((a  b) ? 1 : 0));
    },

    "title-numeric-desc": function ( a, b ) {
        return ((a  b) ? -1 : 0));
    }
} );

jQuery.extend( jQuery.fn.dataTableExt.oSort, {
    "appid-numeric-pre": function ( a ) {
        var x = a.match(/title="*(-?[0-9a-zA-Z\-\_]+)/)[1];
        return makeIdNumeric(x);
    },

    "appid-numeric-asc": function ( a, b ) {
        return ((a  b) ? 1 : 0));
    },

    "appid-numeric-desc": function ( a, b ) {
        return ((a  b) ? -1 : 0));
    }
} );

$(document).ajaxStop($.unblockUI);
$(document).ajaxStart(function(){
    $.blockUI({ message: '
Loading history summary...
'});
});

$(document).ready(function() {
    $.extend( $.fn.dataTable.defaults, {
      stateSave: true,
      lengthMenu: [[20,40,60,100,-1], [20, 40, 60, 100, "All"]],
      pageLength: 20
    });

    historySummary = $("#history-summary");
    searchString = historySummary["context"]["location"]["search"];
    requestedIncomplete = getParameterByName("showIncomplete", searchString);
    requestedIncomplete = (requestedIncomplete == "true" ? true : false);

    $.getJSON("api/v1/applications", function(response,status,jqXHR) {
      var array = [];
      var hasMultipleAttempts = false;
      for (i in response) {
        var app = response[i];
        if (app["attempts"][0]["completed"] == requestedIncomplete) {
          continue; // if we want to show for Incomplete, we skip the completed apps; otherwise skip incomplete ones.
        }
        var id = app["id"];
        var name = app["name"];
        if (app["attempts"].length > 1) {
            hasMultipleAttempts = true;
        }
        var num = app["attempts"].length;
        for (j in app["attempts"]) {
          var attempt = app["attempts"][j];
          attempt["startTime"] = formatDate(attempt["startTime"]);
          attempt["endTime"] = formatDate(attempt["endTime"]);
          attempt["lastUpdated"] = formatDate(attempt["lastUpdated"]);
          var app_clone = {"id" : id, "name" : name, "num" : num, "attempts" : [attempt]};
          array.push(app_clone);
        }
      }

      var data = {"applications": array}
      $.get("static/historypage-template.html", function(template) {
        historySummary.append(Mustache.render($(template).filter("#history-summary-template").html(),data));
        var selector = "#history-summary-table";
        var conf = {
                    "columns": [
                        {name: 'first', type: "appid-numeric"},
                        {name: 'second'},
                        {name: 'third'},
                        {name: 'fourth'},
                        {name: 'fifth'},
                        {name: 'sixth', type: "title-numeric"},
                        {name: 'seventh'},
                        {name: 'eighth'},
                    ],
                    "autoWidth": false,
                    "order": [[ 4, "desc" ]]
        };

        var rowGroupConf = {
                           "rowsGroup": [
                               'first:name',
                               'second:name'
                           ],
        };

        if (hasMultipleAttempts) {
          jQuery.extend(conf, rowGroupConf);
          var rowGroupCells = document.getElementsByClassName("rowGroupColumn");
          for (i = 0; i < rowGroupCells.length; i++) {
            rowGroupCells[i].style='background-color: #ffffff';
          }
        }

        if (!hasMultipleAttempts) {
          var attemptIDCells = document.getElementsByClassName("attemptIDSpan");
          for (i = 0; i < attemptIDCells.length; i++) {
            attemptIDCells[i].style.display='none';
          }
        }

        var durationCells = document.getElementsByClassName("durationClass");
        for (i = 0; i < durationCells.length; i++) {
          var timeInMilliseconds = parseInt(durationCells[i].title);
          durationCells[i].innerHTML = formatDuration(timeInMilliseconds);
        }

        if ($(selector.concat(" tr")).length < 20) {
          $.extend(conf, {paging: false});
        }

        $(selector).DataTable(conf);
        $('#hisotry-summary [data-toggle="tooltip"]').tooltip();
      });
    });
});
<|endoftext|>


<commit_before>// Define project paths.
// Note: all of these are relative to the project root.
var projectPaths = {
    scssSources: 'scss',
    wwwRoot: 'www'
};

// Import required dependencies.
var gulp = require('gulp'),
    browserSync = require('browser-sync'),
    browserSyncReload = browserSync.reload,
    sass = require('gulp-sass'),
    filter = require('gulp-filter');

var browserSyncConfig = {
    server: {
        baseDir: './' + projectPaths.wwwRoot
    },
    files: [
        projectPaths.wwwRoot + '/css/*.css',
        projectPaths.wwwRoot + '/*.html'
    ]
};

gulp.task('sass', function() {
   return gulp.src(projectPaths.scssSources + '/*.scss')
        .pipe(sass())
        .pipe(gulp.dest(projectPaths.wwwRoot + '/css'))
        .pipe(filter('**/*.css'))
        .pipe(browserSyncReload({stream: true}));
});

gulp.task('browser-sync', function() {
    browserSync(browserSyncConfig);
});

gulp.task('default', ['sass', 'browser-sync'], function() {
    gulp.watch(projectPaths.scssSources + '/*.scss', ['sass']);
});

<commit_msg>Updates gulp task list with a new task to handle copying bower component JS files over to the 'www' directory.<commit_after>// Define project paths.
// Note: all of these are relative to the project root.
var projectPaths = {
    scssSources: 'scss',
    wwwRoot: 'www'
};

// Import required dependencies.
var gulp = require('gulp'),
    browserSync = require('browser-sync'),
    browserSyncReload = browserSync.reload,
    sass = require('gulp-sass'),
    filter = require('gulp-filter');

var browserSyncConfig = {
    server: {
        baseDir: './' + projectPaths.wwwRoot
    },
    files: [
        projectPaths.wwwRoot + '/css/*.css',
        projectPaths.wwwRoot + '/*.html'
    ]
};

gulp.task('sass', function() {
   return gulp.src(projectPaths.scssSources + '/*.scss')
        .pipe(sass())
        .pipe(gulp.dest(projectPaths.wwwRoot + '/css'))
        .pipe(filter('**/*.css'))
        .pipe(browserSyncReload({stream: true}));
});

gulp.task('browser-sync', function() {
    browserSync(browserSyncConfig);
});

gulp.task('copy-vendor-js', function() {
    gulp.src([
        './bower_components/jquery/dist/jquery.js',
        './bower_components/parse-js-sdk/lib/parse.js'
    ]).pipe(gulp.dest('./www/js'));
});

gulp.task('default', ['sass', 'browser-sync'], function() {
    gulp.watch(projectPaths.scssSources + '/*.scss', ['sass']);
});

<|endoftext|>