Skip to content
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
72 changes: 72 additions & 0 deletions __tests__/task-requests/task-request.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -285,3 +285,75 @@ describe('createCustomElement', () => {
});
});
});

describe('urlParams', () => {
beforeEach(async () => {
await page.goto(`${SITE_URL}/task-requests`);
await page.waitForNetworkIdle();
});

it('Should update page url for default filter and sort by', async () => {
const url = page.url();
expect(url).toBe(
`${SITE_URL}/task-requests?sort=created-asc&status=pending`,
);
});

it('Should update page url when filter is changed', async () => {
await page.click('#filter-button');
await page.click('input[value="APPROVED"]');
await page.click('input[value="DENIED"]');
await page.click('input[value="assignment"]');
await page.click('input[value="creation"]');
await page.click('#apply-filter-button');
await page.waitForNetworkIdle();
const url = page.url();

expect(url).toBe(
`${SITE_URL}/task-requests?sort=created-asc&status=approved&status=pending&status=denied&request-type=assignment&request-type=creation`,
);
});

it('Should update page url when sort by is changed', async () => {
await page.click('.sort-button');
await page.click('#REQUESTORS_COUNT_ASC');
await page.waitForNetworkIdle();
const url = page.url();

expect(url).toBe(
`${SITE_URL}/task-requests?sort=requestors-asc&status=pending`,
);
});

it('Should have UI elements in sync with url', async () => {
await page.goto(
`${SITE_URL}/task-requests?sort=created-desc&status=approved&status=pending&status=denied&request-type=assignment&request-type=creation`,
);
await page.click('#filter-button');
await page.waitForSelector('.filter-modal');

const approvedFilter = await page.$('input[value="APPROVED"]');
const pendingFilter = await page.$('input[value="PENDING"]');
const deniedFilter = await page.$('input[value="DENIED"]');

const isApprovedChecked = await (
await approvedFilter.getProperty('checked')
).jsonValue();
const isPendingChecked = await (
await pendingFilter.getProperty('checked')
).jsonValue();
const isDeniedChecked = await (
await deniedFilter.getProperty('checked')
).jsonValue();

expect(isApprovedChecked).toBe(true);
expect(isPendingChecked).toBe(true);
expect(isDeniedChecked).toBe(true);

const newestFirst = await page.$('#CREATED_TIME_DESC');
const newestFirstClass = await (
await newestFirst.getProperty('className')
).jsonValue();
expect(newestFirstClass).toContain('selected');
});
});
7 changes: 7 additions & 0 deletions task-requests/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,10 @@ const ErrorMessages = {
NOT_FOUND: 'Task Requests not found',
SERVER_ERROR: 'Unexpected error occurred',
};

const Sort = {
REQUESTORS_COUNT_ASC: 'requestors-asc',
REQUESTORS_COUNT_DESC: 'requestors-desc',
CREATED_TIME_DESC: 'created-desc',
CREATED_TIME_ASC: 'created-asc',
};
108 changes: 85 additions & 23 deletions task-requests/script.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ const filterStates = {

const updateFilterStates = (key, value) => {
filterStates[key] = value;
const constructedQueryString = formURLQueryString(filterStates, isDev);
manipulateURLQueryParams(constructedQueryString);
};

async function getTaskRequests(query = {}, nextLink) {
Expand Down Expand Up @@ -119,6 +121,45 @@ backDrop.addEventListener('click', () => {
backDrop.style.display = 'none';
});

function updateUIBasedOnQueryParams(parsedQueryObj) {
const statusFilters = document.querySelectorAll(
`input[name="status-filter"]`,
);
const requestTypeFilters = document.querySelectorAll(
`input[name="request-type-filter"]`,
);
const sortOptions = document.querySelectorAll('.sort-container');

if (parsedQueryObj.sort) {
sortOptions.forEach((option) => {
if (parsedQueryObj.sort.includes(Sort[option.id])) {
selectButton(option);
updateFilterStates('order', option.id);
sortModal.classList.toggle('hidden');
}
});
}

if (parsedQueryObj.status) {
statusFilters.forEach((filter) => {
if (parsedQueryObj.status.includes(filter.value.toLowerCase())) {
filter.checked = true;
}
});
}

if (parsedQueryObj['request-type']) {
requestTypeFilters.forEach((filter) => {
if (parsedQueryObj['request-type'].includes(filter.value)) {
filter.checked = true;
}
});
}

applyFilterButton.click();
filterModal.classList.toggle('hidden');
}

function toggleStatusCheckbox(statusValue) {
const element = document.querySelector(
`#status-filter input[value=${statusValue}]`,
Expand All @@ -143,6 +184,18 @@ filterButton.addEventListener('click', (event) => {
backDrop.style.display = 'flex';
});

function manipulateURLQueryParams(constructedQueryString) {
const currentURLInstance = new URL(window.location.href);
currentURLInstance.search = '';
const currentURL = currentURLInstance.href;
const newURLWithQueryParams = `${currentURL}${constructedQueryString}`;
window.history.pushState(
{ path: newURLWithQueryParams },
'',
newURLWithQueryParams,
);
}

applyFilterButton.addEventListener('click', async () => {
filterModal.classList.toggle('hidden');
const checkedValuesStatus = getCheckedValues('status-filter');
Expand All @@ -158,9 +211,11 @@ applyFilterButton.addEventListener('click', async () => {
});
clearButton.addEventListener('click', async function () {
clearCheckboxes('status-filter');
clearCheckboxes('request-type-filter');
filterModal.classList.toggle('hidden');
changeFilter();
updateFilterStates('status', '');
updateFilterStates('requestType', '');
await renderTaskRequestCards(filterStates);
});

Expand All @@ -183,6 +238,26 @@ function addSortByIcon(name, id, groupName, order) {
group.appendChild(containerAsc);
}

function toggleSortModal() {
sortModal.classList.toggle('hidden');
backDrop.style.display = 'none';
}

function selectButton(button) {
if (selectedSortButton === button) {
selectedSortButton.classList.remove('selected');
selectedSortButton = null;
toggleSortModal();
} else {
if (selectedSortButton) {
selectedSortButton.classList.remove('selected');
}
selectedSortButton = button;
selectedSortButton.classList.add('selected');
toggleSortModal();
}
}

function sortModalButtons() {
const assigneeAsc = document.getElementById(ASSIGNEE_COUNT);
const assigneeDesc = document.getElementById(ASSIGNEE_DESC);
Expand All @@ -196,26 +271,6 @@ function sortModalButtons() {
createTimeDesc,
];

function toggleSortModal() {
sortModal.classList.toggle('hidden');
backDrop.style.display = 'none';
}

function selectButton(button) {
if (selectedSortButton === button) {
selectedSortButton.classList.remove('selected');
selectedSortButton = null;
toggleSortModal();
} else {
if (selectedSortButton) {
selectedSortButton.classList.remove('selected');
}
selectedSortButton = button;
selectedSortButton.classList.add('selected');
toggleSortModal();
}
}

sortModalButtons.forEach((button) => {
if (button) {
button.addEventListener('click', async () => {
Expand All @@ -226,6 +281,7 @@ function sortModalButtons() {
});
}
});

selectButton(createTimeAsc);
toggleSortModal();
}
Expand Down Expand Up @@ -410,9 +466,15 @@ async function renderTaskRequestCards(queries = {}, newLink = '') {
}

async function render() {
toggleStatusCheckbox(Status.PENDING.toUpperCase());

await renderTaskRequestCards(filterStates);
if (window.location.search === '') {
toggleStatusCheckbox(Status.PENDING.toUpperCase());
const constructedQueryString = formURLQueryString(filterStates);
manipulateURLQueryParams(constructedQueryString);
await renderTaskRequestCards(filterStates);
} else {
const parsedQuery = parseQueryParams(params);
updateUIBasedOnQueryParams(parsedQuery);
}
addIntersectionObserver();
}

Expand Down
51 changes: 51 additions & 0 deletions task-requests/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -67,3 +67,54 @@ const addSpinner = (container) => {

return removeSpinner;
};

/**
* Parses the query parameters from the URLSearchParams object and organizes them into an object.
*
* @param {URLSearchParams} searchParams - The URLSearchParams object that needs to be parsed.
* @returns {Object.<string, string[]>} An object containing query parameter keys as properties
* and arrays of corresponding values.
* */
function parseQueryParams(searchParams) {
const queryObject = {};

searchParams.forEach((value, key) => {
if (!queryObject[key]) {
queryObject[key] = [];
}
queryObject[key].push(value);
});
return queryObject;
}

function formURLQueryString(queryStates, isDev) {
const urlParams = new URLSearchParams();

if (queryStates.order) {
let sortQueryString = Order[queryStates.order];
const key = Object.keys(sortQueryString)[0];
const value = sortQueryString[key];
sortQueryString = key + '-' + value;
urlParams.append('sort', sortQueryString);
}
if (queryStates.status) {
if (Array.isArray(queryStates.status)) {
queryStates.status.forEach((_, index) => {
urlParams.append('status', queryStates.status[index]);
});
} else {
urlParams.append('status', queryStates.status);
}
}
if (queryStates.requestType) {
queryStates.requestType.forEach((_, index) =>
urlParams.append('request-type', queryStates.requestType[index]),
);
}

if (isDev) {
urlParams.append('dev', 'true');
}

return '?' + urlParams.toString().trim();
}