MediaWiki:Policy.js
Note: After publishing, you may have to bypass your browser's cache to see the changes.
- Firefox / Safari: Hold Shift while clicking Reload, or press either Ctrl-F5 or Ctrl-R (⌘-R on a Mac)
- Google Chrome: Press Ctrl-Shift-R (⌘-Shift-R on a Mac)
- Internet Explorer / Edge: Hold Ctrl while clicking Refresh, or press Ctrl-F5
- Opera: Press Ctrl-F5.
/* Any JavaScript here will be loaded for all users on every page load. */
QUERY = `[[Responsible role::+]]|?Name|?Description`
selectedThemes = new Set()
selectedRoles = new Set()
selectedTypes = new Set()
selectedYears = new Set()
years = []
progressItems = {}
tlData = []
async function executeAsk(query) {
    
    $('#spinner').show()
    var results;
    try {
        results = await $.ajax({
            url: mw.util.wikiScript( 'api' ),
            data: {
                action: 'ask',
                format: 'json',
                api_version: 3,
                query : query,
            }, 
            xhrFields: {
                withCredentials: false
            },
            dataType: 'json'        
        })
        $('#spinner').hide()
        return results
    } catch(error) {
        $('#spinner').hide()
        console.log(error)
    }
}
function onTypeFilterSelect(e) {
    if(e.checked) {
        selectedTypes.add(e.value)    
    }
    else {
        selectedTypes.delete(e.value)
    }    
    console.log(selectedTypes) 
    updateTimelines()
}
function onYearFilterSelect(e) {
    if(e.checked) {
        selectedYears.add(e.value)    
    }
    else {
        selectedYears.delete(e.value)
    }    
    console.log(selectedYears) 
    updateTimelines()
}
function onRoleFilterSelect(e) {
    if(e.checked) {
        selectedRoles.add(e.value)    
    }
    else {
        selectedRoles.delete(e.value)
    }    
    console.log(selectedRoles) 
    updateTimelines()
}
function onThemeSelect(e) {        
    
    $('#initial_state').hide()
    if(e.checked) {
        selectedThemes.add(e.value)    
    }
    else {
        selectedThemes.delete(e.value)
    }    
    console.log(selectedThemes)
    updateTimelines()
}
function handleAction(data) {
    return new Promise(function(resolve, reject) {resolve(data.query.results)})
}
function processActionData(data) {
    console.log(data)
    tlData = []
    for(const theme of selectedThemes) {
        var tlItem = {
            name: theme
        }
        items = []
        console.log(data)
        data.forEach(function(r) {
            var actionID = Object.keys(r)[0]
            var obj = r[actionID]
            var actionDate = ''
            if(obj.printouts['Action deadline'][0]) {
                actionDate = obj.printouts['Action deadline'][0]
            }
            var sectionDate = ''
            if(obj.printouts['Section deadline'][0]) {
                sectionDate = obj.printouts['Section deadline'][0]
            }
            var actionField =  obj.printouts.Field[0]
            var sectionType = obj.printouts.sectionType[0] || ''
            if(theme == actionField) {
                var newItem = {
                    id: actionID.replace('#', '').replace(' ', '').replace(':', ''), 
                    name: obj.printouts.Name[0] ||'',
                    desc: obj.printouts.Description[0] || '',
                    actionDeadlineYear: actionDate,
                    sectionDeadlineYear: sectionDate,
                    policyEndYear: obj.printouts.policyEndYear[0] || '',
                    role: obj.printouts['Responsible actor'][0] || '',
                    policyName: obj.printouts.policyName[0] || '',
                    objectiveName: obj.printouts.sectionName[0] || '',
                    objectiveDesc: obj.printouts.sectionDesc[0] || '',
                    objectiveOrdinal: obj.printouts.sectionOrdinal[0] || '',
                    sectionType: sectionType,
                    field: actionField
                }
                if(selectedTypes.size > 0) {
                    console.log(selectedTypes)
                    console.log(sectionType)
                    if(selectedTypes.has(sectionType)) {
                        console.log('hepss')
                        items.push(newItem)
                    }
                }
                else {
                    items.push(newItem)
                }
        
                
    
            }
        })
        console.log(items)
        /*
        items.sort(function(a, b) {
            return a.role.localeCompare(b.role)
        })        
        console.log(items)
    */
    
        const groups = items.reduce((groups, item) => {
            year = ''
            if(item.actionDeadlineYear != '') {
                year = item.actionDeadlineYear
            }
            else if(item.sectionDeadlineYear != '') {
                year = item.sectionDeadlineYear
            }
            else {
                year = item.policyEndYear
            }
            const group = (groups[year] || []);
            group.push(item);
            groups[year] = group;
            return groups;
          }, {});
    
        if(false) {
            Object.keys(groups).forEach(function(key) {
                data = groups[key]
                const parents = data.reduce((parents, item) => {
                    const parent = (parents[item.objectiveName] || []);
                    parent.push(item);
                    parents[item.objectiveName] = parent;
                    return parents;
                }, {});
    
                groups[key] = parents
            })
    
        }
        if(selectedRoles.size > 0 ) {
            console.log('Grouping by roles')
            // gruop by role too
            Object.keys(groups).forEach(function(key) {
                _data = groups[key]
                const roles = _data.reduce((roles, item) => {
                    for (const selectedRole of selectedRoles) {
                        if(item.role.indexOf(selectedRole) >= 0) {
                            const c = roles[selectedRole] || []
                            c.push(item)
                            roles[selectedRole] = c                        
                        }
                    }
                    return roles
                }, {});
    
                groups[key] = roles
            })    
    
        }
        console.log(groups)
    
        tlItem['items'] = groups
        tlData.push(tlItem)
        //tlData.push(structuredClone(tlItem))
        //tlData.push(structuredClone(tlItem))
    }
    return tlData
}
function processProgressData(data) {
    console.log(data)
    items = {}
    data.query.results.forEach(function(r) {
        progressID = Object.keys(r)[0]
        obj = r[progressID]
        date = ''
        if(obj.printouts['Date'][0].timestamp) {
            date = new Date(parseInt(obj.printouts['Date'][0].timestamp)*1000)
        }
        
        actionID = obj.printouts.Action[0].fulltext.replace('#', '').replace(' ', '').replace(':', '')
        progressUrl = obj.printouts.Action[0].fullurl
        name = obj.printouts.Name || ''
        desc = obj.printouts.Description || ''
        orgName = obj.printouts.OrgName || ''
        if(!(actionID in items)) {
            items[actionID] = []
        }
        items[actionID].push({
            actionID: actionID,
            url: progressUrl,
            date: date,
            name: name,
            desc: desc,
            org: orgName
        })
    })
    return new Promise(function(resolve, reject) {resolve(items)})
}
function updateTimelines() {
    $('#timeline_content').html('')
    query1 = '[[Section:+]] [[Part of document.Document.Field::Tutkimusjulkaisut]]|?Part of document.Name=sectionName|?Part of document.Type=sectionType|?Part of document.Description=sectionDesc|?Part of document.Ordinal=sectionOrdinal|?Part of document.Document.Name=policyName|?Part of document.Document.Validity end=policyEndYear|?Part of document.Field=Field|?Name|?Description|?Responsible actor|?Deadline year=Action deadline|?Part of document.Name=Section name|?Part of document.Deadline year=Section deadline|limit=500'
    query2 = '[[Section:+]] [[Part of document.Document.Field::Toimintakulttuuri]]|?Part of document.Name=sectionName|?Part of document.Type=sectionType|?Part of document.Description=sectionDesc|?Part of document.Ordinal=sectionOrdinal|?Part of document.Document.Name=policyName|?Part of document.Document.Validity end=policyEndYear|?Part of document.Field=Field|?Name|?Description|?Responsible actor|?Deadline year=Action deadline|?Part of document.Name=Section name|?Part of document.Deadline year=Section deadline|limit=500'
    query3 = '[[Section:+]] [[Part of document.Document.Field::Oppiminen]]|?Part of document.Name=sectionName|?Part of document.Type=sectionType|?Part of document.Description=sectionDesc|?Part of document.Ordinal=sectionOrdinal|?Part of document.Document.Name=policyName|?Part of document.Document.Validity end=policyEndYear|?Part of document.Field=Field|?Name|?Description|?Responsible actor|?Deadline year=Action deadline|?Part of document.Name=Section name|?Part of document.Deadline year=Section deadline|limit=500'
    query4 = '[[Section:+]] [[Part of document.Document.Field::Data]]|?Part of document.Name=sectionName|?Part of document.Type=sectionType|?Part of document.Description=sectionDesc|?Part of document.Ordinal=sectionOrdinal|?Part of document.Document.Name=policyName|?Part of document.Document.Validity end=policyEndYear|?Part of document.Field=Field|?Name|?Description|?Responsible actor|?Deadline year=Action deadline|?Part of document.Name=Section name|?Part of document.Deadline year=Section deadline|limit=500'
    progressQuery = '[[Category:Progress]]|?Name|?Description|?Date|?Action|?Organization.Name=OrgName'
    if(selectedThemes.size == 0) {        
        return
    } 
    // one query per theme - group by year (use action's year first, then parent's and finally policy's validity end.)
    tlData = []
    //actionData = executeAsk(query).then(processActionData)
    progressData = executeAsk(progressQuery).then(processProgressData)
    Promise.all([ executeAsk(query1).then(handleAction), executeAsk(query2).then(handleAction), executeAsk(query3).then(handleAction), executeAsk(query4).then(handleAction), progressData]).then(function(values) {
        console.log(values)
        progressItems = values[4]
        var _tlData = values[0]
        _tlData.push(...values[1], ...values[2], ...values[3])
        _tlData = processActionData(_tlData)
        console.log(tlData)
        // extract years 
        years = []
        _tlData.forEach(function(i) {
            var keys = Object.keys(i.items)
            keys.forEach(function(key) {
                if(Object.keys(i.items[key]).length > 0 ) {
                    if(years.indexOf(key) < 0 ) {
                        years.push(key)
                    }
    
                }
            })
        })
        if(selectedYears.size > 0) {
            years = Array.from(selectedYears)
        }
        years.sort()
        console.log(years)
        tlData = _tlData
        html = createTimeline(tlData)
        $('#timeline_content').html(html) 
        $("[data-toggle=popover]").popover();       
    })
}
function createProgressCard(data) {
    var $card = $('<div>', {class:'card'})
    $cardBody = $('<div>', {class:'card-body'})
    $cardTitle = $('<h5>', {class: 'card-title'}).text(data.name)
    $cardSubTitle = $('<h6>', {class:'card-subtitle mb-2 text-muted'}).text(data.date.toLocaleDateString("fi-FI"))
    $cardSubTitle2 = $('<h6>', {class:'card-subtitle mb-2 text-muted'}).text(data.org)
    $cardText = $('<p>', {class: 'card-text'}).text(data.desc)
    $cardBody.append($cardTitle)
    $cardBody.append($cardSubTitle)
    $cardBody.append($cardSubTitle2)
    $cardBody.append($cardText)
    $card.append($cardBody)
    return $card
}
function addProgressModal(action) {    
    if(action.id in progressItems) {
        console.log(action)
        _progressItems = progressItems[action.id]
        $modalContainer = $('<div>', {class:'modal', tabindex:'-1', role:'dialog', id:action.id})
        $modal = $('<div>', {class:'modal-dialog', role:'document'})
        $modalContent = $('<div>', {class:'modal-content'})
        $modalHeader = $('<div>', {class:'modal-header'})
        $title = $('<h5>', {class:'modal-title'}).text('Toimenpiteeseen liittyvät edistysaskeleet')
        $titleClose = $('<button>', {type:'button', class:'close', 'data-dismiss':'modal'})
        $close = $('<span>').html('×')
        $titleClose.append($close)
        $actionText = $('<p>', {class:'card-text'}).text((action.name != '' ? action.name + '. ' : '') + action.desc)
        $actionRole = $('<h6>', {class:'card-subtitle mb-2 text-muted'}).text(action.role)
        $objectiveName = $('<h5>', {class:'card-title'}).text(action.objectiveName)
        $policyLink = $('<a>', {href:'#', class:'card-link'}).text(action.policyName)
        $modalBody = $('<div>', {class:'modal-body'})
        $modalBody.append($objectiveName)
        $modalBody.append($actionRole)
        $modalBody.append($actionText)
        _progressItems.forEach(function(progressItem) {
            $modalBody.append(createProgressCard(progressItem))
        })
        $modalHeader.append($title)
        $modalHeader.append($titleClose)
        $modalContent.append($modalHeader)
        $modalContent.append($modalBody)
        $modal.append($modalContent)
        $modalContainer.append($modal)
        $('#modals').append($modalContainer)
    }
}
function createTimelineCard(item) {
    //console.log(item)
    var $card = $('<div>', {class:'card shadow-sm'})
    var $cardBody = $('<div>', {class:'card-body'}) 
    var $cardText = $('<p>', {class:'card-text'}).text((item.name != '' ? item.name + '. ' : '') + item.desc)
    var $cardTitle = $('<h5>', {class:'card-title'}).text(item.role)
    var $role = $('<h6>', {class:'card-subtitle mb-2 text-muted'}).text(item.role)
    var $cardFooter = $('<div>', {class:'card-footer',tabindex:'0', 'data-trigger':'focus', 'data-container':'body', 'data-toggle':'popover', 'data-placement':'top', 'data-content':item.objectiveDesc}).text(item.policyName + ' - ' + item.sectionType  + ' ' + item.objectiveOrdinal )
    var $policyLink = $('<a>', {href:'#', class:'card-link'}).text(item.policyName)
    //$tag = $('<span>', {class:'tag', tabindex:'0', 'data-trigger':'focus', 'data-container':'body', 'data-toggle':'popover', 'data-placement':'top', 'data-content':item.objectiveDesc}).html('<h4>' + item.objectiveName + ' - Tavoite ' + item.objectiveOrdinal + '</h4>')
    var $header = $('<div>', {class:'card-header',tabindex:'0', 'data-trigger':'focus', 'data-container':'body', 'data-toggle':'popover', 'data-placement':'top', 'data-content':item.objectiveDesc}).text(item.objectiveName + ' - Tavoite ' + item.objectiveOrdinal )
    /*
<button type="button" class="btn btn-secondary" data-container="body" data-toggle="popover" data-placement="bottom" data-content="Bottom popover">
Popover on bottom
</button>
    */
    //<small class="text-muted">Last updated 3 mins ago</small>
    //$cardBody.append($cardTitle)
    
    //$card.append($header)
    $cardBody.append($role)
    $textContent = $('<div>', {style:'margin-bottom: 1em; overflow: scroll;height: 120px;'})
    $textContent.append($cardText)
    $cardBody.append($textContent)
    //console.log(item.id)
    //console.log(item.id in progressItems)
    if(item.id in progressItems) {
        //$progressButton = $('<button>', {class:'btn btn-primary', type:'button', onclick:'$("#' + item.id + '").modal("show")' }).text('Has progress (' + progressItems[item.id].length + ')')
        //$cardBody.append($progressButton)                        
        var $tag = $('<span>', {class: 'tag', onclick:'$("#' + item.id + '").modal("show")'}).html('<h4>Edistysaskeleita (' + progressItems[item.id].length + ')</h4>')
        $cardBody.append($tag)
    }
    $card.append($cardBody)
    //$cardFooter.append($policyLink)
    
    $card.append($cardFooter)
    //$deck.append($card)
    addProgressModal(item)
    //console.log($card)
    return $card
}
function createTimeline(data) {
    
    content = []
    // header row 
    header = $('<div>', {class: 'container-fluid my-3  sticky-top'})
    headerRow = $('<div>', {class: 'row theme-header-row'})
    headerRow.append( $('<div>', {class: 'col-md-1'}) )
    data.forEach(function(theme) {
        headerRow.append($('<h3>', {class: 'col-md'}).text(theme.name))
    })
    header.append(headerRow)
    content.push(header)
    // year rows
    console.log(progressItems)
    console.log('***')
    console.log(data)
    years.forEach(function(year) {
        $yearContainer = $('<div>', {class: 'container-fluid py-3'})
        $yearRow = $('<div>', {class: 'row'})
        $yearContent = $('<div>', {class: 'col-md-1'})
        $yearValue = $('<div>', {class: 'card card-body sticky-top'}).text(year)
        $yearContent.append($yearValue)
        $yearRow.append($yearContent)
        
        data.forEach(function(theme) {
            $cards = $('<div>', {class: 'col-md', style:'min-height: 150px'})
            $deck = $('<div>', {class: 'card-columns'})
            if (year in theme.items) {
                items = theme.items[year]       
                if(selectedRoles.size > 0) {
                    if(Object.keys(items).length > 0 ) {
                        $roleContainer = $('<div>', {class:'container-fluid'})
                        $roleRow = $('<div>', {class:'row'})
    
                        for(const role of selectedRoles) {
                            if(role in items) {
                                console.log(role)
                                $roleCol = $('<div>', {class:'col-sm'}).html('<h4>'+ role + '</h4>')
                            
                                
                                _items = items[role]
                                console.log(_items)
                                _items.forEach(function(item) {
                                    _card = createTimelineCard(item)
                                    $roleCol.append(_card)
                                })
                                $roleRow.append($roleCol)
    
                            }
    
    
                        }
                        $roleContainer.append($roleRow)
                        $roleContainer.append($('<style>').text('.card { margin-bottom: 15px}'))
                        $cards.append($roleContainer)
    
                    }
                }         
                else {
                    
                    items.forEach(function(item) {
                        //console.log(item)
                        _card = createTimelineCard(item)
                        //console.log(_card)
                        $deck.append(_card)
                    })
                    
                    $cards.append($deck)
                    
                }
                
 
            }
            $yearRow.append($cards)
            
           
        })
        $yearContainer.append($yearRow)
        content.push($yearContainer)
        
    })
    return content
}
$(function () {
    $('#themes input').click(function(e) {
        onThemeSelect(e.target)
    })
    $('#roles input').click(function(e) {
        onRoleFilterSelect(e.target)
    })
    $('#years input').click(function(e) {
        onYearFilterSelect(e.target)
    })
    $('#types input').click(function(e) {
        onTypeFilterSelect(e.target)
    })
   
}());