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

T#2426/expiration history #2442

Merged
merged 22 commits into from
Oct 16, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
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
184 changes: 184 additions & 0 deletions dashboard/src/components/expiration/ExpirationHistory.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
/*
Copyright 2020 SkillTree

Licensed 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

https://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.
*/
<template>
<div>
<sub-page-header title="Skill Expiration History"/>
<b-card body-class="p-0">
<div class="row px-3 pt-3">
<div class="col-md-6">
<b-form-group label="Skill Name Filter" label-class="text-muted">
<b-input v-model="filters.skillName" v-on:keydown.enter="applyFilters" data-cy="skillNameFilter" aria-label="skill name filter"/>
</b-form-group>
</div>
<div class="col-md-6">
<b-form-group label="User Id Filter" label-class="text-muted">
<b-input v-model="filters.userId" v-on:keydown.enter="applyFilters" data-cy="userIdFilter" aria-label="user id filter"/>
</b-form-group>
</div>
</div>
<div class="row pl-3 mb-3">
<div class="col">
<b-button variant="outline-info" @click="applyFilters" data-cy="users-filterBtn"><i class="fa fa-filter" aria-hidden="true" /> Filter</b-button>
<b-button variant="outline-info" @click="reset" class="ml-1" data-cy="users-resetBtn"><i class="fa fa-times" aria-hidden="true" /> Reset</b-button>
</div>
</div>
<b-overlay :show="table.options.busy">
<skills-b-table :options="table.options" :items="table.items"
@page-size-changed="pageSizeChanged"
@page-changed="pageChanged"
@sort-changed="sortTable"
tableStoredStateId="expirationHistoryTable"
data-cy="expirationHistoryTable">
<template #head(userId)="data">
<span class="text-primary">
<i class="fas fa-user-cog skills-color-skills" aria-hidden="true"/> {{ data.label }}
</span>
</template>
<template #head(skillName)="data">
<span class="text-primary"><i class="fas fa-graduation-cap skills-color-skills" aria-hidden="true"></i> {{ data.label }}</span>
</template>
<template #head(expiredOn)="data">
<span class="text-primary"><i class="fas fa-clock text-warning" aria-hidden="true"></i> {{ data.label }}</span>
</template>
<template v-slot:cell(skillName)="data">
<link-to-skill-page :project-id="projectId" :skill-id="data.item.skillId" :link-label="data.label" data-cy="linkToSkill"/>
</template>
<template v-slot:cell(expiredOn)="data">
<date-cell :value="data.value" />
</template>
</skills-b-table>
</b-overlay>
</b-card>
</div>
</template>

<script>
import SubPageHeader from '@/components/utils/pages/SubPageHeader';
import SkillsBTable from '@/components/utils/table/SkillsBTable';
import LinkToSkillPage from '@/components/utils/LinkToSkillPage';
import ExpirationService from '@/components/expiration/ExpirationService';
import DateCell from '../utils/table/DateCell';

export default {
name: 'ExpirationHistory',
components: {
SubPageHeader,
SkillsBTable,
DateCell,
LinkToSkillPage,
},
data() {
return {
projectId: this.$route.params.projectId,
filters: {
skillName: '',
userId: '',
},
table: {
options: {
busy: false,
bordered: true,
outlined: true,
stacked: 'md',
sortBy: 'userId',
sortDesc: true,
tableDescription: 'ExpirationHistory',
fields: [
{
key: 'skillName',
label: 'Skill Name',
sortable: true,
},
{
key: 'userId',
label: 'User',
sortable: true,
},
{
key: 'expiredOn',
label: 'Expired On',
sortable: true,
},
],
pagination: {
server: true,
currentPage: 1,
totalRows: 1,
pageSize: 10,
possiblePageSizes: [10, 25, 50],
},
},
items: [],
},
};
},
mounted() {
this.loadData();
},
methods: {
loadData() {
const params = {
limit: this.table.options.pagination.pageSize,
page: this.table.options.pagination.currentPage,
orderBy: this.table.options.sortBy,
ascending: !this.table.options.sortDesc,
skillName: this.filters.skillName,
userId: this.filters.userId,
};
return ExpirationService.getExpiredSkills(this.$route.params.projectId, params).then((res) => {
this.table.items = res.data;
this.table.options.pagination.totalRows = res.totalCount;
});
},
sortTable(sortContext) {
this.table.options.sortBy = sortContext.sortBy;
this.table.options.sortDesc = sortContext.sortDesc;

// set to the first page
this.table.options.pagination.currentPage = 1;
this.loadData();
},
pageChanged(pageNum) {
this.table.options.pagination.currentPage = pageNum;
this.loadData();
},
pageSizeChanged(newSize) {
this.table.options.pagination.pageSize = newSize;
this.loadData();
},
applyFilters() {
this.table.options.pagination.currentPage = 1;
this.loadData().then(() => {
let filterMessage = 'Skill expiration history table has been filtered by';
if (this.filters.skillName) {
filterMessage += ` ${this.filters.skillName}`;
}
if (this.filters.userId) {
filterMessage += ` ${this.filters.userId}`;
}
this.$nextTick(() => this.$announcer.polite(filterMessage));
});
},
reset() {
this.filters.userId = '';
this.filters.skillName = '';
this.loadData().then(() => {
this.$nextTick(() => this.$announcer.polite('Skill expiration history table filters have been removed'));
});
},
},
};
</script>
4 changes: 4 additions & 0 deletions dashboard/src/components/expiration/ExpirationService.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,8 @@ export default {
return axios.get(url)
.then((response) => response.data);
},
getExpiredSkills(projectId, params) {
const url = `/admin/projects/${projectId}/expirations`;
return axios.get(url, { params }).then((response) => response.data);
},
};
3 changes: 2 additions & 1 deletion dashboard/src/components/projects/ProjectPage.vue
Original file line number Diff line number Diff line change
Expand Up @@ -158,8 +158,9 @@ limitations under the License.
items.push({ name: 'Contact Users', iconClass: 'fas fa-mail-bulk', page: 'EmailUsers' });
items.push({ name: 'Issues', iconClass: 'fas fa-exclamation-triangle', page: 'ProjectErrorsPage' });
items.push({ name: 'Access', iconClass: 'fa-shield-alt skills-color-access', page: 'ProjectAccess' });
items.push({ name: 'Settings', iconClass: 'fa-cogs skills-color-settings', page: 'ProjectSettings' });
items.push({ name: 'Admin Activity', iconClass: 'fa-users-cog text-success', page: 'ProjectActivityHistory' });
items.push({ name: 'Skill Expiration History', iconClass: 'fa-clock skills-color-expiration', page: 'ExpirationHistory' });
items.push({ name: 'Settings', iconClass: 'fa-cogs skills-color-settings', page: 'ProjectSettings' });
}

return items;
Expand Down
11 changes: 11 additions & 0 deletions dashboard/src/router/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ import QuizRun from '@/components/quiz/QuizRunInDashboard';
import QuizAccessPage from '@/components/quiz/access/QuizAccessPage';
import VideoConfigPage from '@/components/video/VideoConfigPage';
import ExpirationConfigPage from '@/components/expiration/ExpirationConfigPage';
import ExpirationHistory from '@/components/expiration/ExpirationHistory';
import UserActionsPage from '@/components/userActions/UserActionsPage';

Vue.use(Router);
Expand Down Expand Up @@ -607,6 +608,16 @@ const router = new Router({
message: 'User Activity History',
},
},
}, {
name: 'ExpirationHistory',
path: 'expirationHistory',
component: ExpirationHistory,
meta: {
requiresAuth: true,
announcer: {
message: 'Skill Expiration History',
},
},
}, {
name: 'EmailUsers',
path: 'contact-users',
Expand Down
20 changes: 20 additions & 0 deletions e2e-tests/cypress/e2e/accessibility/accessibility_specs.js
Original file line number Diff line number Diff line change
Expand Up @@ -350,6 +350,26 @@ describe('Accessibility Tests', () => {
cy.customA11y();
});

it('skill expirations', () => {
cy.createProject(1);
cy.createSubject(1, 1);
cy.createSkill(1, 1, 1);

cy.configureExpiration(1, 0, 1, 'DAILY');
let yesterday = moment.utc().subtract(1, 'day')
let twoDaysAgo = moment.utc().subtract(2, 'day')
cy.doReportSkill({ project: 1, skill: 1, subjNum: 1, userId: Cypress.env('proxyUser'), date: yesterday.format('YYYY-MM-DD HH:mm') });
cy.doReportSkill({ project: 1, skill: 1, subjNum: 1, userId: Cypress.env('proxyUser'), date: twoDaysAgo.format('YYYY-MM-DD HH:mm') });
cy.expireSkills();

cy.visit('/administrator/projects/proj1/expirationHistory');
cy.injectAxe();

cy.get('[data-cy="expirationHistoryTable"]')
cy.customLighthouse();
cy.customA11y();
});

it('badges', () => {
cy.visit('/administrator/');
cy.injectAxe();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,17 +20,6 @@ const dateFormatter = value => moment.utc(value)

describe('Client Display Expiration Tests', () => {

Cypress.Commands.add('configureExpiration', (skillNum = '1', numDays = 30, every=1, expirationType='YEARLY') => {
const isRecurring = expirationType === 'YEARLY' || expirationType === 'MONTHLY'
const m = isRecurring ? moment.utc().add(numDays, 'day') : null;
cy.request('POST', `/admin/projects/proj1/skills/skill${skillNum}/expiration`, {
expirationType: expirationType,
every: every,
monthlyDay: m ? m.date() : null,
nextExpirationDate: m ? m.format('x') : null
});
});

beforeEach(() => {
Cypress.env('disabledUILoginProp', true);
cy.createProject(1);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -390,7 +390,7 @@ describe('Learning Path Management Validation Tests', () => {
cy.get('[data-cy="skillsSelectionItem-proj1-badge2"]').click();
cy.get('[data-cy="addLearningPathItemBtn"]').click();

cy.clickOnNode(360, 250);
cy.clickOnNode(340, 250);
cy.get('button').contains('Remove').click();
cy.get('[data-cy="fullDepsSkillsGraph"]').contains('No Learning Path Yet')
})
Expand Down
8 changes: 4 additions & 4 deletions e2e-tests/cypress/e2e/metrics/skillMetrics_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,10 @@ describe('Metrics Tests - Skills', () => {
});

cy.intercept('GET', '/public/config', (req) => {
req.reply({
body: {
projectMetricsTagCharts: '[{"key":"tagA","type":"table","title":"Tag A","tagLabel":"Tag A"}]'
},
req.reply((res) => {
const conf = res.body;
conf.projectMetricsTagCharts = '[{"key":"tagA","type":"table","title":"Tag A","tagLabel":"Tag A"}]';
res.send(conf);
});
})
.as('getConfig');
Expand Down
10 changes: 5 additions & 5 deletions e2e-tests/cypress/e2e/metrics/subjectMetrics_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ var moment = require('moment-timezone');

describe('Metrics Tests - Subject', () => {

const waitForSnap = 4000;
const waitForSnap = 6000;

before(() => {
Cypress.Commands.add('addUserTag', (userId, tagKey, tags) => {
Expand All @@ -32,10 +32,10 @@ describe('Metrics Tests - Subject', () => {
});

cy.intercept('GET', '/public/config', (req) => {
req.reply({
body: {
projectMetricsTagCharts: '[{"key":"tagA","type":"table","title":"Tag A","tagLabel":"Tag A"}]'
},
req.reply((res) => {
const conf = res.body;
conf.projectMetricsTagCharts = '[{"key":"tagA","type":"table","title":"Tag A","tagLabel":"Tag A"}]';
res.send(conf);
});
})
.as('getConfig');
Expand Down
Loading
Loading