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

SP24 Multiple Plans Release #901

Merged
merged 7 commits into from
Mar 16, 2024
Merged
Show file tree
Hide file tree
Changes from 4 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
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,25 @@ Then access http://localhost:8080/

## Contributors

## SP24
- **Hannah Zhou** - Developer
- **Nidhi Mylavarapu** - Developer
- **Simon Ilincev** - Developer
- **Zak Kent** - Dev Advisor
- **Joanna Chen** - Designer
- **Elizabeth Tang** - TPM
- **Andrew Xu** - TPM
- **Kaylin Chan** - PM
- **Ilyssa Yan** - APM

### FA23
- **Nidhi Mylavarapu** - Developer
- **Simon Ilincev** - Developer
- **Jerry Wang** - Developer
- **Miranda Yu** - Developer
- **Elizabeth Tang** - Developer
- **Pablo Raigoza** - Developer
- **Andrew Xu** - Developer
- **Michelle Dai** - Designer
- **Joanna Chen** - Designer
- **Jonathan Mak** - PMM
Expand Down
17 changes: 17 additions & 0 deletions cypress/integration/test.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,12 @@ before('Visit site logged in', () => {
cy.wait(5000); // ensure the page has time to load
});

// Test to confirm that the new user walkthrough works as expected
// Click through the initial explanation, then the 4 following steps, and finally the finishing page
it('Click through new feature tour', () => {
cy.get('.introjs-nextbutton').click();
});

// Delete existing semesters to ensure existing data does not mess with tests
it('Delete all existing semesters, if any exist', () => {
const semesterMenus = '[data-cyId=semesterMenu]';
Expand Down Expand Up @@ -237,3 +243,14 @@ it('Minimize a semester', () => {
cy.get('[data-cyId=semester-course]').should('be.visible');
cy.get('[data-cyId=semester-addCourse]').should('be.visible');
});

it('Multiple plans dropdown open/close', () => {
// dropdown initially closed
cy.get('[data-cyId=multiplePlans-dropdown-content]').should('not.exist');
cy.get('[data-cyId=multiplePlans-dropdown-open]').click();
// dropdown opens
cy.get('[data-cyId=multiplePlans-dropdown-content]').should('be.visible');
cy.get('[data-cyId=multiplePlans-dropdown-close]').click();
// dropdown closed again
cy.get('[data-cyId=multiplePlans-dropdown-content]').should('not.exist');
});
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -100,4 +100,4 @@
"not dead"
],
"license": "AGPL-3.0"
}
}
28 changes: 28 additions & 0 deletions scripts/migration/new-feature-migration.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/* eslint-disable no-console */

import { usernameCollection, onboardingDataCollection } from '../firebase-config';

/**
* Perform migration of semester to plans with a list of semesters
*/
async function runOnUser(userEmail: string) {
await onboardingDataCollection.doc(userEmail).update({ sawNewFeature: false });
}

async function main() {
const userEmail = process.argv[2];
if (userEmail != null) {
await runOnUser(userEmail);
return;
}
const collection = await usernameCollection.get();
for (const { id } of collection.docs) {
console.group(`Running on ${id}...`);
// Intentionally await in a loop to have no interleaved console logs.
// eslint-disable-next-line no-await-in-loop
await runOnUser(id);
console.groupEnd();
}
}

main();
32 changes: 32 additions & 0 deletions scripts/migration/plans-migration.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/* eslint-disable no-console */

import { usernameCollection, semestersCollection } from '../firebase-config';

/**
* Perform migration of semester to plans with a list of semesters
*/
async function runOnUser(userEmail: string) {
const semestersDoc = await semestersCollection.doc(userEmail).get();
const semesters = semestersDoc.data()?.semesters ?? [];
await semestersCollection.doc(userEmail).update({
plans: [{ name: 'Plan 1', semesters }],
});
}

async function main() {
const userEmail = process.argv[2];
if (userEmail != null) {
await runOnUser(userEmail);
return;
}
const collection = await usernameCollection.get();
for (const { id } of collection.docs) {
console.group(`Running on ${id}...`);
// Intentionally await in a loop to have no interleaved console logs.
// eslint-disable-next-line no-await-in-loop
await runOnUser(id);
console.groupEnd();
}
}

main();
6 changes: 3 additions & 3 deletions scripts/track-users.ts
Original file line number Diff line number Diff line change
Expand Up @@ -135,11 +135,11 @@ async function trackUsers() {
return;
}

const semesters = getFirstPlan(doc.data());
const plan = getFirstPlan(doc.data());

let oldSemesterCount = 0;
let newSemesterCount = 0;
semesters.forEach((semester: { year: number; season: string }) => {
plan.semesters.forEach((semester: { year: number; season: string }) => {
if (isOld(semester)) {
oldSemesterCount += 1;
} else {
Expand All @@ -152,7 +152,7 @@ async function trackUsers() {
activeUsersCount += 1;
}

numSemestersPerUser.push(semesters.length);
numSemestersPerUser.push(plan.semesters.length);
oldSemesters.push(oldSemesterCount);
newSemesters.push(newSemesterCount);
});
Expand Down
4 changes: 4 additions & 0 deletions src/assets/images/editplan.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 2 additions & 1 deletion src/components/Course/CourseCaution.vue
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,8 @@ const getCourseCautions = (
isPlaceholderCourse(course) &&
((!store.state.orderByNewest && semesterIndex !== course.startingSemester) ||
(store.state.orderByNewest &&
store.state.semesters.length - semesterIndex + 1 !== course.startingSemester));
store.getters.getCurrentPlanSemesters.length - semesterIndex + 1 !==
course.startingSemester));
return {
noMatchedRequirement,
typicallyOfferedWarning,
Expand Down
2 changes: 1 addition & 1 deletion src/components/Modals/EditSemester.vue
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ export default defineComponent({
},
computed: {
semesters(): readonly FirestoreSemester[] {
return store.state.semesters;
return store.getters.getCurrentPlanSemesters;
},
},
methods: {
Expand Down
71 changes: 71 additions & 0 deletions src/components/Modals/MultiplePlans/AddPlanModal.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
<template>
<teleport-modal
title="Copy Plan?"
content-class="content-plan"
left-button-text="Create Blank"
right-button-text="Make a Copy"
@modal-closed="closeCurrentModal"
@left-button-clicked="blankPlan"
@right-button-clicked="copyPlan"
:isPlanModal="true"
>
Would you like to copy an existing plan or create a blank one?
</teleport-modal>
</template>

<script lang="ts">
import { defineComponent } from 'vue';
import store from '@/store';
import TeleportModal from '@/components/Modals/TeleportModal.vue';

export default defineComponent({
components: { TeleportModal },
emits: {
'close-plan-modal': () => true,
'open-copy-modal': () => true,
'add-plan': (name: string) => typeof name === 'string',
},
computed: {
placeholder_name() {
const oldplans = store.state.plans.map(plan => plan.name);
let newplannum = 1;
// eslint-disable-next-line no-loop-func
while (oldplans.find(p => p === `New Plan ${newplannum}`)) {
newplannum += 1;
}
return `New Plan ${newplannum}`;
},
},
data() {
return { isDisabled: false, isCopyPlanOpen: false };
},
methods: {
closeCurrentModal() {
this.$emit('close-plan-modal');
},
blankPlan() {
this.$emit('close-plan-modal');
this.$emit('add-plan', this.placeholder_name);
},
copyPlan() {
this.$emit('open-copy-modal');
this.$emit('close-plan-modal');
},
},
});
</script>

<style lang="scss">
.content-plan {
width: 20rem;
}

.modal {
&--block {
display: block;
}
&--flex {
display: flex;
}
}
</style>
124 changes: 124 additions & 0 deletions src/components/Modals/MultiplePlans/CopyPlanModal.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
<template>
<teleport-modal
title="Copy a Plan"
content-class="content-plan"
left-button-text="Back"
right-button-text="Copy Plan"
@modal-closed="closeCurrentModal"
@left-button-clicked="backAddPlan"
@right-button-clicked="namePlan"
>
<div class="selectPlan">
<label class="selectPlan-label">Copy Plan</label>
<div class="selectPlan-dropdown">
<div class="multiplePlansModal-dropdown">
<div
class="multiplePlansModal-dropdown-placeholder wrapper"
@click="closeDropdownIfOpen()"
>
<div class="multiplePlansModal-dropdown-placeholder plan">{{ selectedPlan }}</div>
<img
class="multiplePlansModal-dropdown-placeholder up-arrow"
v-if="shown"
alt="close dropdown"
/>
<img
class="multiplePlansModal-dropdown-placeholder down-arrow"
v-else
alt="open dropdown"
/>
</div>
<div class="multiplePlansModal-dropdown-content" v-if="shown" :class="shown">
<div
v-for="otherPlan in otherPlans"
class="multiplePlansModal-dropdown-content item"
:key="otherPlan"
@click="planClicked(otherPlan)"
>
{{ otherPlan }}
</div>
</div>
</div>
</div>
</div>
</teleport-modal>
</template>

<script lang="ts">
import { defineComponent } from 'vue';
import TeleportModal from '@/components/Modals/TeleportModal.vue';
import store from '@/store';

export default defineComponent({
props: {
plan: { type: String, default: 'No additional plans yet' },
},
components: { TeleportModal },
emits: {
'close-copy-modal': () => true,
'open-plan-modal': () => true,
'open-name-modal': () => true,
'copy-plan': (selectedPlan: string) => typeof selectedPlan === 'string',
},
data() {
return { isDisabled: false, shown: false, selectedPlan: store.state.currentPlan.name };
},
methods: {
closeCurrentModal() {
this.$emit('close-copy-modal');
},
closeDropdownIfOpen() {
this.shown = !this.shown;
},
planClicked(plan: string) {
if (this.plans.length !== 1) this.selectedPlan = plan;
},
backAddPlan() {
this.$emit('open-plan-modal');
this.$emit('close-copy-modal');
},
namePlan() {
this.$emit('open-name-modal');
this.$emit('copy-plan', this.selectedPlan);
this.$emit('close-copy-modal');
},
},
computed: {
otherPlans() {
const filtered = this.plans.filter(plan => plan !== this.selectedPlan);
return filtered.length === 0 ? ['No additional plans yet'] : filtered;
},
plans() {
return store.state.plans.map(plan => plan.name);
},
},
});
</script>

<style lang="scss">
@import '@/components/Modals/MultiplePlans/PlanModalDropdown.scss';
.selectPlan {
height: 6rem;
&-label {
position: relative;
top: 0.5rem;
}
&-dropdown {
top: 0.5rem;
position: relative;
}
}

.content-plan {
width: 20rem;
}

.modal {
&--block {
display: block;
}
&--flex {
display: flex;
}
}
</style>
Loading
Loading