Skip to content

Commit

Permalink
#2490: implemented project expiration and added its cypress tests bac…
Browse files Browse the repository at this point in the history
…k to CI
  • Loading branch information
sudo-may committed Jun 11, 2024
1 parent 27f7804 commit a611f4c
Show file tree
Hide file tree
Showing 8 changed files with 96 additions and 133 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/build-and-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ jobs:
- name: Run Cypress tests
run: |
cd e2e-tests
../.github/scripts/runSubsetOfCypressTests.sh -t 6 -c ${{ matrix.container }} -f 'projects_crud|/skills_table_spec|/skill_reuse_spec|quiz_creation|question_def|metrics-reUsedData_spec|quiz_and_survey|quiz_runs|quiz_skill_assignment|quiz_read|quiz_role|quiz_settings|quiz_skills-in_project_reuse|client-display_quiz_spec|client-display_quiz_theme_spec|client-display_quiz_visual_spec|client-display_run_quiz_spec|run_survey_spec|quiz_skills-catalog_spec|/inception|user_actions|configure-skill-expiration_spec|skill-expiration-table_spec|configure_video_spec|configure_video_features_spec|configure_skill_video_self_report_spec|configure_video_validation_spec|handle_video_without_duration_spec|client_display_video_on_subject_page_spec|client_display_video_on_skill_page_spec|/skills_spec|/users_spec|/subjects_spec|/levels_management_spec|/skills_group_spec|/skills_group_modal_spec|/move-skills/|/badges_spec|/skill-reuse/skill_reuse/|/catalog/|/project_settings_spec|/icon_manager_spec|/not_found_spec|my-usage_spec|/error_pages_spec|/add_skills_to_badge_spec|/tag-skills|/settings_spec|/client-display/|/learning-path/|/copy_project_spec|/project_errors_spec|/selfReport-approvalHistory_spec|/approver|/manage-my-projects_spec|/accessibility|/community/|/metrics/projectMetrics|/metrics/skillMetrics_spec|/metrics/subjectMetrics_spec|/projects_invite_only_spec|/app_features_spec|/breadcrumb_spec|/contact|/cross-project_spec|/discoverable_proj_invite_spec|/login_spec|/markdown_spec/metrics/multipleProjectMetrics_empty_spec|/metrics/multipleProjectMetrics_spec|/my-progress_breadcrumb_spec|/my-progress_spec|/my-progress-badges_spec|/navigation_spec'
../.github/scripts/runSubsetOfCypressTests.sh -t 6 -c ${{ matrix.container }} -f 'projects_crud|/skills_table_spec|/skill_reuse_spec|quiz_creation|question_def|metrics-reUsedData_spec|quiz_and_survey|quiz_runs|quiz_skill_assignment|quiz_read|quiz_role|quiz_settings|quiz_skills-in_project_reuse|client-display_quiz_spec|client-display_quiz_theme_spec|client-display_quiz_visual_spec|client-display_run_quiz_spec|run_survey_spec|quiz_skills-catalog_spec|/inception|user_actions|configure-skill-expiration_spec|skill-expiration-table_spec|configure_video_spec|configure_video_features_spec|configure_skill_video_self_report_spec|configure_video_validation_spec|handle_video_without_duration_spec|client_display_video_on_subject_page_spec|client_display_video_on_skill_page_spec|/skills_spec|/users_spec|/subjects_spec|/levels_management_spec|/skills_group_spec|/skills_group_modal_spec|/move-skills/|/badges_spec|/skill-reuse/skill_reuse/|/catalog/|/project_settings_spec|/icon_manager_spec|/not_found_spec|my-usage_spec|/error_pages_spec|/add_skills_to_badge_spec|/tag-skills|/settings_spec|/client-display/|/learning-path/|/copy_project_spec|/project_errors_spec|/selfReport-approvalHistory_spec|/approver|/manage-my-projects_spec|/accessibility|/community/|/metrics/projectMetrics|/metrics/skillMetrics_spec|/metrics/subjectMetrics_spec|/projects_invite_only_spec|/app_features_spec|/breadcrumb_spec|/contact|/cross-project_spec|/discoverable_proj_invite_spec|/login_spec|/markdown_spec/metrics/multipleProjectMetrics_empty_spec|/metrics/multipleProjectMetrics_spec|/my-progress_breadcrumb_spec|/my-progress_spec|/my-progress-badges_spec|/navigation_spec|/progress_and_ranking_disabled_spec|/project_expiration_spec'
cd ..
env:
Expand Down
32 changes: 2 additions & 30 deletions dashboard-prime/src/components/projects/MyProject.vue
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { useAccessState } from '@/stores/UseAccessState.js'
import { useAppConfig } from '@/common-components/stores/UseAppConfig.js'
import ReminderMessage from '@/components/utils/misc/ReminderMessage.vue'
import { useNumberFormat } from '@/common-components/filter/UseNumberFormat.js'
import ProjectExpirationWarning from '@/components/projects/ProjectExpirationWarning.vue'
const props = defineProps(['project', 'disableSortControl'])
const appConfig = useAppConfig()
Expand Down Expand Up @@ -54,24 +55,11 @@ const minimumPoints = computed(() => {
const isRootUser = computed(() => {
return accessState.isRoot;
});
const expirationDate = computed(() => {
if (!projectInternal.value.expiring) {
return '';
}
const gracePeriodInDays = appConfig.expirationGracePeriod;
const expires = dayjs(projectInternal.value.expirationTriggered).add(gracePeriodInDays, 'day').startOf('day');
return expires.format('YYYY-MM-DD HH:mm');
});
const isReadOnlyProj = computed(() => {
return UserRolesUtil.isReadOnlyProjRole(projectInternal.value.userRole);
});
// methods
const fromExpirationDate = () => {
return dayjs()
.startOf('day')
.to(dayjs(expirationDate));
};
const createCardOptions = () => {
stats.value = [{
label: 'Subjects',
Expand Down Expand Up @@ -155,16 +143,6 @@ const unpin = () => {
emit('pin-removed', projectInternal);
});
};
const keepIt = () => {
cancellingExpiration.value = true;
ProjectService.cancelUnusedProjectDeletion(projectInternal.value.projectId)
.then(() => {
projectInternal.value.expiring = false;
})
.finally(() => {
cancellingExpiration.value = false;
});
};
const moveDown = () => {
emit('sort-changed-requested', {
projectId: projectInternal.value.projectId,
Expand Down Expand Up @@ -265,13 +243,7 @@ defineExpose({
<ProjectCardFooter class="mt-4" :project="projectInternal"/>
</div>

<div v-if="projectInternal.expiring" data-cy="projectExpiration" class="w-100 text-center alert-danger p-2 mt-2">
<span class="mr-2" v-tooltip="'This Project has not been used recently, ' +
'it will be deleted unless you explicitly retain it'">Project has not been used in over <b>{{appConfig.expireUnusedProjectsOlderThan}} days</b> and will be deleted <b>{{ fromExpirationDate() }}</b>.</span>
<Button @click="keepIt" data-cy="keepIt" size="sm" variant="alert" :aria-label="'Keep Project '+ projectInternal.name">
<span class="d-none d-sm-inline">Keep It</span> <SkillsSpinner v-if="cancellingExpiration" small style="font-size:1rem"/><i v-if="!cancellingExpiration" :class="'fas fa-shield-alt'" style="font-size: 1rem;" aria-hidden="true"/>
</Button>
</div>
<project-expiration-warning :project="projectInternal" @extended="projectInternal.expiring = false" />
<ReminderMessage
v-if="warningMsgAboutPoints"
:id="`projectCardWarning_${projectInternal.projectId}`"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
<script setup>
import { useAppConfig } from '@/common-components/stores/UseAppConfig.js'
import { computed, ref } from 'vue'
import dayjs from '@/common-components/DayJsCustomizer.js'
import ProjectService from '@/components/projects/ProjectService.js'
const props = defineProps(['project'])
const emits = defineEmits(['extended'])
const appConfig = useAppConfig()
const cancellingExpiration = ref(false);
const fromExpirationDate = computed(() => {
if (!props.project.expiring) {
return '';
}
const gracePeriodInDays = appConfig.expirationGracePeriod;
const expires = dayjs(props.project.expirationTriggered).add(gracePeriodInDays, 'day').startOf('day');
return dayjs().startOf('day').to(expires);
});
const keepIt = () => {
cancellingExpiration.value = true;
ProjectService.cancelUnusedProjectDeletion(props.project.projectId)
.then(() => {
emits('extended');
})
.finally(() => {
cancellingExpiration.value = false;
});
};
</script>

<template>
<Message
v-if="project.expiring"
data-cy="projectExpiration"
severity="error"
:closable="false"
class="mt-2">
<div class="flex gap-2 flex-column md:flex-row align-items-center">
<div class="flex-1">
Project has not been used in over <Tag severity="danger">{{ appConfig.expireUnusedProjectsOlderThan }} days</Tag> and will be
deleted <Tag severity="danger">{{ fromExpirationDate }}</Tag>.
</div>
<div class="text-right">
<SkillsButton
@click="keepIt"
data-cy="keepIt"
size="sm"
label="Keep It"
icon="fas fa-shield-alt"
:loading="cancellingExpiration"
:aria-label="'Keep Project '+ project.name" />
</div>
</div>
</Message>
</template>

<style scoped>
</style>
36 changes: 2 additions & 34 deletions dashboard-prime/src/components/projects/ProjectPage.vue
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { useProjDetailsState } from '@/stores/UseProjDetailsState.js'
import UserRolesUtil from '@/components/utils/UserRolesUtil'
import Avatar from 'primevue/avatar'
import ProjectShareDialog from '@/components/projects/ProjectShareDialog.vue'
import ProjectExpirationWarning from '@/components/projects/ProjectExpirationWarning.vue'
// const props = defineProps(['project'])
const router = useRouter()
Expand Down Expand Up @@ -149,15 +150,6 @@ const headerOptions = computed(() => {
const minimumPoints = computed(() => {
return appConfig.minimumProjectPoints
})
const expirationDate = computed(() => {
if (!project.value.expiring) {
return ''
}
const gracePeriodInDays = appConfig.expirationGracePeriod.value
const expires = dayjs(project.value.expirationTriggered).add(gracePeriodInDays, 'day').startOf('day')
return expires.format('YYYY-MM-DD HH:mm')
})
const copyAndDisplayShareProjInfo = () => {
const host = window.location.origin
Expand All @@ -167,9 +159,6 @@ const copyAndDisplayShareProjInfo = () => {
})
}
const fromExpirationDate = () => {
return dayjs().startOf('day').to(dayjs(expirationDate))
}
const projectSaved = (updatedProject) => {
const origProjId = project.value.projectId
ProjectService.saveProject(updatedProject).then(() => {
Expand All @@ -189,15 +178,6 @@ const projectSaved = (updatedProject) => {
})
}
const keepIt = () => {
cancellingExpiration.value = true
ProjectService.cancelUnusedProjectDeletion(route.params.projectId).then(() => {
// loadProjects();
}).finally(() => {
cancellingExpiration.value = false
})
}
const setProject = (newProject) => {
projectDetailsState.project = newProject
}
Expand All @@ -208,19 +188,7 @@ const setProject = (newProject) => {
<div ref="mainFocus">
<PageHeader :loading="isLoading" :options="headerOptions">
<template #banner v-if="project && project.expiring && !isReadOnlyProj">
<div data-cy="projectExpiration" class="w-100 text-center alert-danger p-2 mb-3">
<span class="mr-2"
aria-label="This Project has not been used recently, it will be deleted unless you explicitly retain it"
v-tooltip="'This Project has not been used recently, it will be deleted unless you explicitly retain it'">
Project has not been used in over <b>{{ appConfig.expireUnusedProjectsOlderThan }} days</b> and will be deleted <b>{{
fromExpirationDate()
}}</b>.
</span>
</div>
<SkillsButton @click="keepIt" data-cy="keepIt" size="small" variant="alert"
:aria-label="'Keep Project '+ project.value.name" label="Keep It"
:icon="!cancellingExpiration ? 'fas fa-shield-alt' : ''">
</SkillsButton>
<project-expiration-warning :project="project" @extended="project.expiring = false" />
</template>
<template #subTitle v-if="project">
<div v-if="project.userCommunity" class="mb-3" data-cy="userCommunity">
Expand Down
7 changes: 4 additions & 3 deletions dashboard-prime/src/components/settings/ProjectSettings.vue
Original file line number Diff line number Diff line change
Expand Up @@ -215,11 +215,12 @@ const usePointsForLevelsLabel = computed(() => {
});
const projectVisibilityOptions = computed(() => {
const opts = [
{ value: publicNotDiscoverable, text: 'Not in the Project Catalog' },
];
const opts = [];
if (isProgressAndRankingEnabled.value) {
opts.push({ value: discoverableProgressAndRanking, text: 'Add to the Project Catalog' });
opts.push({ value: publicNotDiscoverable, text: 'Not in the Project Catalog' });
} else {
opts.push({ value: publicNotDiscoverable, text: 'Not Enabled' });
}
opts.push({ value: privateInviteOnly, text: 'Private Invite Only' });
return opts;
Expand Down
4 changes: 3 additions & 1 deletion dashboard-prime/src/components/utils/pages/PageHeader.vue
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@ const colors = useColors()
<template>
<div data-cy="pageHeader" class="border-1 border-round-md surface-border font-medium surface-0 mt-2 px-3 py-3">
<skills-spinner v-if="loading" :is-loading="loading" />
<div v-if="!loading" class="flex py-1 px-1 w-full">
<div v-if="!loading">
<slot name="banner"></slot>
<div class="flex py-1 px-1 w-full">
<div class="flex w-full flex-wrap">
<div class="mt-2 text-center lg:text-left w-full lg:w-auto">
<div class="text-2xl flex">
Expand Down Expand Up @@ -75,6 +76,7 @@ const colors = useColors()
</div>
<slot name="footer"/>
</div>
</div>
</div>
</template>

Expand Down
3 changes: 2 additions & 1 deletion dashboard/src/components/projects/MyProject.vue
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,8 @@ limitations under the License.
<project-card-footer class="mt-4" :project="projectInternal"/>
</div>

<div v-if="projectInternal.expiring" data-cy="projectExpiration" class="w-100 text-center alert-danger p-2 mt-2">
<div
v-if="projectInternal.expiring" data-cy="projectExpiration" class="w-100 text-center alert-danger p-2 mt-2">
<span class="mr-2" v-b-tooltip.hover="'This Project has not been used recently, ' +
'it will be deleted unless you explicitly retain it'">Project has not been used in over <b>{{this.$store.getters.config.expireUnusedProjectsOlderThan}} days</b> and will be deleted <b>{{ fromExpirationDate() }}</b>.</span>
<b-button @click="keepIt" data-cy="keepIt" size="sm" variant="alert" :aria-label="'Keep Project '+ projectInternal.name">
Expand Down
83 changes: 20 additions & 63 deletions e2e-tests/cypress/e2e/progress_and_ranking_disabled_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,14 +65,18 @@ describe('Project and Ranking Views are disabled Tests', () => {
.should('not.exist');
});

it.skip('Project level enable prod-mode setting must NOT be shown', () => {
it('Project level enable prod-mode setting must NOT be shown', () => {
cy.createProject(1);

const addToCatalogLabel = 'Add to the Project Catalog'
const addToCatalogLabel = '[aria-label="Add to the Project Catalog"]'
const notInCatalogLabel = '[aria-label="Not in the Project Catalog"]'
const notEnabledLabel = '[aria-label="Not Enabled"]'

cy.visit('/administrator/projects/proj1/settings');
cy.contains('[data-cy=projectVisibilitySelector]', addToCatalogLabel)
.should('exist');
cy.get('[data-cy="projectVisibilitySelector"]').click()
cy.get(`[data-pc-section="panel"] ${addToCatalogLabel}`).should('exist')
cy.get(`[data-pc-section="panel"] ${notInCatalogLabel}`).should('exist')
cy.get(`[data-pc-section="panel"] ${notEnabledLabel}`).should('not.exist')
// cy.get('[ data-cy="productionModeSetting"]').should('exist');
cy.intercept('GET', '/public/config', (req) => {
req.reply({
Expand All @@ -85,23 +89,24 @@ describe('Project and Ranking Views are disabled Tests', () => {

cy.visit('/administrator/projects/proj1/settings');
cy.wait('@getConfig');
cy.contains('[data-cy=projectVisibilitySelector]', addToCatalogLabel)
.should('not.exist');

cy.get('[data-cy="projectVisibilitySelector"]').click()
cy.get(`[data-pc-section="panel"] ${addToCatalogLabel}`).should('not.exist')
cy.get(`[data-pc-section="panel"] ${notInCatalogLabel}`).should('not.exist')
cy.get(`[data-pc-section="panel"] ${notEnabledLabel}`).should('exist')
});

it.skip('do not show Progress and Ranking in the breadcrumb when those views are disabled', function () {
it('do not show Progress and Ranking in the breadcrumb when those views are disabled', function () {
cy.createProject(1);
cy.visit('/progress-and-rankings/projects/proj1/');
cy.get('[data-cy=breadcrumb-item]')
cy.get('[data-cy="breadcrumbItemValue"]')
.its('length')
.should('eq', 2);
cy.get('[data-cy=breadcrumb-item]')
cy.get('[data-cy="breadcrumbItemValue"]')
.eq(0)
.should('contain.text', 'Progress And Rankings');
cy.get('[data-cy=breadcrumb-item]')
cy.get('[data-cy="breadcrumbItemValue"]')
.eq(1)
.should('contain.text', 'Project: proj1');
.should('contain.text', 'proj1');

cy.intercept('GET', '/public/config', (req) => {
req.reply({
Expand All @@ -112,61 +117,13 @@ describe('Project and Ranking Views are disabled Tests', () => {
})
.as('getConfig');
cy.visit('/progress-and-rankings/projects/proj1/');
cy.get('[data-cy=breadcrumb-item]')
cy.get('[data-cy="breadcrumbItemValue"]')
.its('length')
.should('eq', 1);
cy.get('[data-cy=breadcrumb-item]')
cy.get('[data-cy="breadcrumbItemValue"]')
.eq(0)
.should('contain.text', 'Project: proj1');
.should('contain.text', 'proj1');
});

it.skip('Provide clear instructions how to create a new project - root user', function () {
cy.intercept('GET', '/public/config', (req) => {
req.reply({
body: {
rankingAndProgressViewsEnabled: 'false',
},
});
})
.as('getConfig');

cy.logout();
cy.fixture('vars.json')
.then((vars) => {
cy.login(vars.rootUser, vars.defaultPass);
});
cy.visit('/administrator/');
cy.contains('No Projects Yet...');
cy.contains('A Project represents a gamified training profile that consists of skills divided into subjects');
cy.get('[data-cy="firstNewProjectButton"]')
.click();
cy.get('[data-cy="projectName"]')
.type('one');
cy.get('[data-cy="saveProjectButton"]')
.click();
cy.get('[data-cy="projCard_one_manageBtn"]');
});

it.skip('Provide clear instructions how to create a new project - regular user', function () {
cy.intercept('GET', '/public/config', (req) => {
req.reply({
body: {
rankingAndProgressViewsEnabled: 'false',
},
});
})
.as('getConfig');

cy.visit('/administrator/');
cy.contains('No Projects Yet...');
cy.contains('A Project represents a gamified training profile that consists of skills divided into subjects');
cy.get('[data-cy="firstNewProjectButton"]')
.click();
cy.get('[data-cy="projectName"]')
.type('one');
cy.get('[data-cy="saveProjectButton"]')
.click();
cy.get('[data-cy="projCard_one_manageBtn"]');
});
});

0 comments on commit a611f4c

Please sign in to comment.