Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Faster DOM rendering when processing toolbar filters #3845

Merged
merged 5 commits into from
Nov 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 74 additions & 0 deletions tcms/static/js/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -485,3 +485,77 @@ export function updateTestPlanSelectFromProduct (
jsonRPC('TestPlan.filter', query, internalCallback)
}
}

// usage: 'Hello {0}, your order {1} has been shipped.'.format('John', 10001)
function formatString (template, ...args) {
return template.replace(/{([0-9]+)}/g, function (match, index) {
return typeof args[index] === 'undefined' ? match : args[index]
})
}

// returns 2 arrays with selectors that need to be either shown or hidden
// return values will be used as multiple-selector for jQuery
// see https://api.jquery.com/multiple-selector/
export function findSelectorsToShowAndHide (inputData, filterBy, filterValue, selectorStr) {
const hideMe = []
const showMe = []

inputData.forEach(function (element) {
if (element[filterBy] !== undefined && element[filterBy].toString().toLowerCase().indexOf(filterValue) > -1) {
showMe.push(formatString(selectorStr, element.id))
} else {
hideMe.push(formatString(selectorStr, element.id))
}
})

return {
hide: hideMe,
show: showMe
}
}

// similar to the function above, however it operates on API data returned by
// calls to .filter() API methods. Needs all data as input to be able to calculate
// which rows should not be visible
export function findSelectorsToShowAndHideFromAPIData (allData, filteredData, selectorStr) {
const hideMe = []
const showMe = []

// these need to be hidden
const filteredPKs = filteredData.map(element => element.id)
allData.forEach(element => {
if (filteredPKs.indexOf(element.id) === -1) {
hideMe.push(formatString(selectorStr, element.id))
}
})

// these will remain visible
filteredData.forEach(element => {
showMe.push(formatString(selectorStr, element.id))
})

return {
hide: hideMe,
show: showMe
}
}

// update the screen in one swoop trying to perform
// as little display updates as possible
export function showOrHideMultipleRows (rootSelector, rows) {
// initial state is that everything is hidden

if (rows.show.length <= rows.hide.length) {
$(rows.show.join(',')).show()
} else {
/* eslint-disable indent */
$('body')
.find(rootSelector)
.show()
.end()
.find(rows.hide.join(','))
.hide()
.end()
/* eslint-enable */
}
}
14 changes: 9 additions & 5 deletions tcms/testplans/static/testplans/js/get.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ import {
advancedSearchAndAddTestCases,
bindDeleteCommentButton, changeDropdownSelectedItem,
markdown2HTML, renderCommentsForObject, renderCommentHTML,
treeViewBind, quickSearchAndAddTestCase
treeViewBind, quickSearchAndAddTestCase,
findSelectorsToShowAndHide, findSelectorsToShowAndHideFromAPIData,
showOrHideMultipleRows
} from '../../../../static/js/utils'
import { initSimpleMDE } from '../../../../static/js/simplemde_security_override'

Expand Down Expand Up @@ -696,18 +698,20 @@ function filterTestCasesByProperty (planId, testCases, filterBy, filterValue) {
}

$('.js-testcase-row').hide()

if (filterBy === 'component' || filterBy === 'tag') {
const query = { plan: planId }
query[`${filterBy}__name__icontains`] = filterValue

jsonRPC('TestCase.filter', query, function (filtered) {
// hide again if a previous async request showed something else
$('.js-testcase-row').hide()
filtered.forEach(tc => $(`[data-testcase-pk=${tc.id}]`).show())

const rows = findSelectorsToShowAndHideFromAPIData(testCases, filtered, '[data-testcase-pk={0}]')
showOrHideMultipleRows('.js-testcase-row', rows)
})
} else {
testCases.filter(function (tc) {
return (tc[filterBy] !== undefined && tc[filterBy].toString().toLowerCase().indexOf(filterValue) > -1)
}).forEach(tc => $(`[data-testcase-pk=${tc.id}]`).show())
const rows = findSelectorsToShowAndHide(testCases, filterBy, filterValue, '[data-testcase-pk={0}]')
showOrHideMultipleRows('.js-testcase-row', rows)
}
}
27 changes: 17 additions & 10 deletions tcms/testruns/static/testruns/js/get.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ import {
arrayToDict, bindDeleteCommentButton,
changeDropdownSelectedItem, currentTimeWithTimezone,
markdown2HTML, renderCommentsForObject, renderCommentHTML,
quickSearchAndAddTestCase, treeViewBind
quickSearchAndAddTestCase, treeViewBind,
findSelectorsToShowAndHide, findSelectorsToShowAndHideFromAPIData,
showOrHideMultipleRows
} from '../../../../static/js/utils'
import { initSimpleMDE } from '../../../../static/js/simplemde_security_override'

Expand Down Expand Up @@ -248,6 +250,7 @@ function filterTestExecutionsByProperty (runId, executions, filterBy, filterValu
// no input => show all rows
if (filterValue.trim().length === 0) {
$('.test-execution-element').show()
$('.test-executions-count').text(executions.length)
return
}

Expand All @@ -259,24 +262,28 @@ function filterTestExecutionsByProperty (runId, executions, filterBy, filterValu
$('.test-execution-element').hide()

if (filterBy === 'is_automated' || filterBy === 'priority' || filterBy === 'category') {
const query = { executions__run: runId }
const query = { run: runId }
if (filterBy === 'is_automated') {
query[filterBy] = filterValue
query.case__is_automated = filterValue
} else if (filterBy === 'priority') {
query.priority__value__icontains = filterValue
query.case__priority__value__icontains = filterValue
} else if (filterBy === 'category') {
query.category__name__icontains = filterValue
query.case__category__name__icontains = filterValue
}

jsonRPC('TestCase.filter', query, function (filtered) {
// note: querying TEs so that -FromAPIData() can work properly!
jsonRPC('TestExecution.filter', query, function (filtered) {
// hide again if a previous async request showed something else
$('.test-execution-element').hide()
filtered.forEach(tc => $(`.test-execution-case-${tc.id}`).show())

const rows = findSelectorsToShowAndHideFromAPIData(executions, filtered, '.test-execution-{0}')
showOrHideMultipleRows('.test-execution-element', rows)
$('.test-executions-count').text(rows.show.length)
})
} else {
executions.filter(function (te) {
return (te[filterBy] && te[filterBy].toString().toLowerCase().indexOf(filterValue) > -1)
}).forEach(te => $(`.test-execution-${te.id}`).show())
const rows = findSelectorsToShowAndHide(executions, filterBy, filterValue, '.test-execution-{0}')
showOrHideMultipleRows('.test-execution-element', rows)
$('.test-executions-count').text(rows.show.length)
}
}

Expand Down
Loading