Skip to content

Commit

Permalink
fix(INTERNAL-1432): restrict access to personal goals
Browse files Browse the repository at this point in the history
  • Loading branch information
LamaEats committed Dec 12, 2024
1 parent 3f2a9f2 commit 6125123
Show file tree
Hide file tree
Showing 14 changed files with 383 additions and 51 deletions.
77 changes: 77 additions & 0 deletions cypress/e2e/projects.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
projectsCombobox,
filtersPanelTitle,
dashboardLoadMore,
goalTableList,
} from '../../src/utils/domObjects';
import { exactUrl } from '../helpers';
import { routes } from '../../src/hooks/router';
Expand All @@ -38,6 +39,10 @@ const testProjectKeyRu = keyPredictor(testProjectTitleRu);
const customKeyRu = 'КАСТОМНЫЙ КЛЮЧ РУ';
const customKeyRuPredict = keyPredictor(customKeyRu, { allowVowels: true });

before(() => {
cy.loadLangFile();
});

describe('Projects', () => {
beforeEach(() => {
cy.signInViaEmail();
Expand Down Expand Up @@ -255,3 +260,75 @@ describe('Projects', () => {
});
});
});

describe('Personal project', () => {
const testUser = {
name: 'Test user 1',
email: '[email protected]',
password: 'password',
provider: 'provider1',
};

const testUser2 = {
name: 'Test user 2',
email: '[email protected]',
password: 'password',
provider: 'provider2',
};

before(() => {
cy.task('db:create:user', testUser).then((u) => {
Cypress.env('testUser', u);
});
cy.task('db:create:user', testUser2).then((u) => {
Cypress.env('testUser2', u);
});
});

after(() => {
cy.task('db:remove:user', Cypress.env('testUser'));
cy.task('db:remove:user', Cypress.env('testUser2'));
});

beforeEach(() => {
cy.interceptWhatsNew();
cy.intercept('/api/trpc/*v2.project.getUserDashboardProjects*?*').as('userDashboard');
cy.intercept('/api/trpc/*v2.project.getProjectGoalsById*?*').as('goalsByProject');
cy.signInViaEmail(testUser);
cy.wait('@whatsnew.check');
// @ts-ignore
cy.createPersonalGoal({
title: 'Personal test goal',
description: 'Personal test goal description',
mode: 'personal',
});
});

it('should be able on personal dashboard', () => {
cy.wait(['@userDashboard', '@goalsByProject']);

cy.get(projectListItem.query)
.should('exist')
.get(projectListItemTitle.query)
.should('include.text', testUser.name);

cy.get(projectListItem.query)
.get(goalTableList.query)
.should('have.length.gt', 0)
.and('contain.text', 'Personal test goal');
cy.logout();

cy.signInViaEmail(testUser2);
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
cy.visit(routes.project(Cypress.env('createdGoal')!.projectId as string), {
failOnStatusCode: false,
});
cy.get('body').should('contain.text', '404');

// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
cy.visit(routes.goal(Cypress.env('createdGoal')!._shortId as string), {
failOnStatusCode: false,
});
cy.get('body').should('contain.text', '404');
});
});
5 changes: 3 additions & 2 deletions cypress/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,14 +57,15 @@ declare global {
hideEmptyProjectOnGoalLists(): Chainable<void>;
createProject(fields: ProjectCreate): Chainable<ProjectCreateReturnType>;
createGoal(projectTitle: string, fields: GoalCommon): Chainable<GoalCreateReturnType>;
createPersonalGoal(fields: GoalCommon): Chainable<GoalCreateReturnType>;
updateGoal(shortId: string, filelds: GoalUpdate): Chainable<GoalUpdateReturnType>;
deleteGoal(shortId: string): Chainable<void>;
createComment(fields: GoalCommentCreateSchema): Chainable<CommentCreateReturnType>;
updateComment(fields: CommentEditSchema): Chainable<CommentCreateReturnType>;
deleteComment(id: string): Chainable<void>;
task(
event: 'db:create:project',
data: { title: string; key: string; description?: string; ownerEmail: string },
data: { title: string; key: string; description?: string; ownerEmail: string; personal?: boolean },
): Chainable<string>;
task(event: 'db:remove:project', data: { id: string }): Chainable<null>;
task(
Expand All @@ -75,7 +76,7 @@ declare global {
task(event: 'db:remove:user', data?: { ids: string[] }): Chainable<null>;
task(
event: 'db:create:goal',
data: { title: string; projectId: string; ownerEmail: string },
data: { title: string; projectId: string; ownerEmail: string; private?: boolean },
): Chainable<Goal>;
task(event: 'db:create:tag', data: { title: string; userEmail: string }): Chainable<TagData>;
task(event: 'db:remove:tag', data: { id: string }): Chainable<null>;
Expand Down
45 changes: 43 additions & 2 deletions cypress/support/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,19 @@ import {
sortPanelDropdownTrigger,
sortPanel,
sortPanelEmptyProjectsCheckbox,
goalPersonalityToggle,
createPersonalGoalItem,
estimateCombobox,
estimateQuarterTrigger,
} from '../../src/utils/domObjects';
import { keyPredictor } from '../../src/utils/keyPredictor';
import { SignInFields } from '..';
import { GoalCommentCreateSchema, GoalCommon, GoalUpdate } from '../../src/schema/goal';
import { CommentEditSchema } from '../../src/schema/comment';
import { ProjectCreate } from '../../src/schema/project';

import { getTranslation } from './lang';

Cypress.Commands.addAll({
logout: () => {
cy.visit(routes.userSettings());
Expand Down Expand Up @@ -133,6 +139,41 @@ Cypress.Commands.addAll({
});
},

createPersonalGoal: (fields: GoalCommon) => {
const translations = getTranslation({
Dropdown: ['Not chosen'],
EstimateDropdown: ['Choose quarter'],
});
cy.intercept('/api/trpc/*goal.create?*').as('createGoalRequest');
cy.get(createSelectButton.query).click();
cy.get(createPersonalGoalItem.query).click();
cy.get(goalForm.query).should('exist').and('be.visible');
cy.get(goalTitleInput.query).type(fields.title);
cy.get(goalDescriptionInput.query).type(fields.description);
cy.get(goalPersonalityToggle.query).should('exist');
cy.get(projectsCombobox.query).should('not.exist');

cy.get(estimateCombobox.query).should('contain.text', translations.Dropdown['Not chosen']()).click();
cy.get(estimateQuarterTrigger.query)
.find(`:button:contains(${translations.EstimateDropdown['Choose quarter']()})`)
.click();

cy.get(estimateQuarterTrigger.query).children().find(':button:contains(@current)').click();
cy.get(estimateCombobox.query).should('not.contain.text', translations.Dropdown['Not chosen']()).click();

cy.get(goalActionCreateOnly.query).should('exist').and('be.visible').and('be.enabled');
cy.get(goalActionCreateOnly.query).click();

cy.wait('@createGoalRequest')
.its('response')
.then((res) => {
const createdGoal = res.body[0].result.data;

Cypress.env('createdGoal', createdGoal);
cy.wrap(createdGoal).as('createdGoal');
});
},

updateGoal: (shortId: string, fields: GoalUpdate) => {
cy.visit(routes.goal(shortId));
cy.intercept('/api/trpc/goal.update?*').as('updateGoal');
Expand Down Expand Up @@ -168,7 +209,7 @@ Cypress.Commands.addAll({

const createdGoal = Cypress.env('createdGoal');

if (createdGoal._shortId === shortId) {
if (createdGoal?._shortId === shortId) {
Cypress.env('createdGoal', null);
}
});
Expand Down Expand Up @@ -237,7 +278,7 @@ Cypress.Commands.addAll({

const createdComment = Cypress.env('createdComment');

if (createdComment.id === id) {
if (createdComment?.id === id) {
Cypress.env('createdComment', null);
}
});
Expand Down
120 changes: 120 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions src/components/GoalCreateForm/GoalCreateForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ const GoalCreateForm: React.FC<GoalCreateFormProps> = ({
utils.v2.project.userProjects.invalidate();
utils.v2.project.getProjectChildrenTree.invalidate();
utils.v2.project.getProjectGoalsById.invalidate();
utils.v2.project.getUserDashboardProjects.invalidate();

if (form.parent && form.mode === 'default') {
utils.project.getDeepInfo.invalidate({ id: form.parent.id });
Expand Down
21 changes: 18 additions & 3 deletions src/components/GoalForm/GoalForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ import {
combobox,
estimateCombobox,
goalDescriptionInput,
goalPersonalityToggle,
goalPersonalityToggleProjectValue,
goalPersonalityTogglePersonalValue,
goalTagList,
goalTagListItem,
goalTagListItemClean,
Expand Down Expand Up @@ -224,9 +227,21 @@ export const GoalForm: React.FC<GoalFormProps> = ({
control={control}
render={({ field }) => (
<div className={s.SwitchGoalType}>
<Switch {...field} onChange={(_, value) => setValue('mode', value)}>
<SwitchControl text={tr('Project goal')} value={goalTypeMap.default} />
<SwitchControl text={tr('Personal goal')} value={goalTypeMap.personal} />
<Switch
{...field}
onChange={(_, value) => setValue('mode', value)}
{...goalPersonalityToggle.attr}
>
<SwitchControl
text={tr('Project goal')}
value={goalTypeMap.default}
{...goalPersonalityToggleProjectValue.attr}
/>
<SwitchControl
text={tr('Personal goal')}
value={goalTypeMap.personal}
{...goalPersonalityTogglePersonalValue.attr}
/>
</Switch>
<HelpButton slug="goals" />
</div>
Expand Down
7 changes: 4 additions & 3 deletions src/components/GoalTableList/GoalTableList.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Badge, Table, Tag, Text, UserGroup, ListViewItem, Tooltip } from '@taskany/bricks/harmony';
import { MouseEventHandler, useCallback, useEffect, useMemo } from 'react';
import React, { MouseEventHandler, useCallback, useEffect, useMemo } from 'react';
import { nullable, formateEstimate } from '@taskany/bricks';
import { IconGitBranchOutline, IconMessageTextOutline } from '@taskany/icons';

Expand All @@ -18,11 +18,11 @@ import { InlineUserBadge } from '../InlineUserBadge/InlineUserBadge';
import { State } from '../State';
import { GoalCriteriaPreview } from '../GoalCriteria/GoalCriteria';
import { useGoalPreview } from '../GoalPreview/GoalPreviewProvider';
import { participants } from '../../utils/domObjects';
import { participants, goalTableListItem } from '../../utils/domObjects';

import s from './GoalTableList.module.css';

interface GoalTableListProps<T> {
interface GoalTableListProps<T> extends React.ComponentProps<typeof Table> {
goals: T[];
onGoalPreviewShow?: (goal: T) => MouseEventHandler<HTMLAnchorElement>;
onGoalClick?: MouseEventHandler<HTMLAnchorElement>;
Expand Down Expand Up @@ -220,6 +220,7 @@ export const GoalTableList = <T extends GoalTableListItem>({
<TableListItem
selected={goal.shortId === shortId || goal.id === preview?.id}
hovered={active}
{...goalTableListItem.attr}
{...props}
>
{row.list.map(({ content, width, className }, index) => (
Expand Down
Loading

0 comments on commit 6125123

Please sign in to comment.