diff --git a/backend/pom.xml b/backend/pom.xml index 93c9ce1131..5278ec5c22 100644 --- a/backend/pom.xml +++ b/backend/pom.xml @@ -319,4 +319,4 @@ - + \ No newline at end of file diff --git a/frontend/cypress/e2e/tab.cy.ts b/frontend/cypress/e2e/tab.cy.ts index 343f1b979d..7186fb4416 100644 --- a/frontend/cypress/e2e/tab.cy.ts +++ b/frontend/cypress/e2e/tab.cy.ts @@ -218,7 +218,7 @@ describe('Tab workflow tests', () => { cy.focused().parentsUntil('#objective-column').last().contains('Edited by Cypress'); }); - it('Duplicate objective with tab', () => { + it.only('Duplicate objective with tab', () => { openThreeDotMenu(); cy.realPress('ArrowDown'); cy.realPress('ArrowDown'); @@ -231,7 +231,7 @@ describe('Tab workflow tests', () => { editInputFields('Duplicated by Cypress'); cy.tabForward(); cy.tabForward(); - cy.focused().contains('GJ'); + cy.realPress('ArrowDown'); cy.realPress('ArrowDown'); cy.tabForward(); cy.focused().contains('Speichern'); diff --git a/frontend/src/app/components/application-banner/application-banner.component.html b/frontend/src/app/components/application-banner/application-banner.component.html index 39f0ba7b8a..84aa105ebd 100644 --- a/frontend/src/app/components/application-banner/application-banner.component.html +++ b/frontend/src/app/components/application-banner/application-banner.component.html @@ -16,20 +16,27 @@
- - + + - {{ - (quarterLabel$ | async) || quarterLabel$.getValue() - }} - Filter +
+ {{ + (quarterLabel$ | async) || quarterLabel$.getValue() + }} + Filter +
+

Filter

- -
- - - +
+ + +
diff --git a/frontend/src/app/components/objective-filter/objective-filter.component.html b/frontend/src/app/components/objective-filter/objective-filter.component.html index b1392ebd18..a487e26b38 100644 --- a/frontend/src/app/components/objective-filter/objective-filter.component.html +++ b/frontend/src/app/components/objective-filter/objective-filter.component.html @@ -8,6 +8,7 @@ > 0 && t[t.length - 1])) && (op[0] === 6 || op[0] === 2)) { + _ = 0; + continue; + } + if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { + _.label = op[1]; + break; + } + if (op[0] === 6 && _.label < t[1]) { + _.label = t[1]; + t = op; + break; + } + if (t && _.label < t[2]) { + _.label = t[2]; + _.ops.push(op); + break; + } + if (t[2]) _.ops.pop(); + _.trys.pop(); + continue; + } + op = body.call(thisArg, _); + } catch (e) { + op = [6, e]; + y = 0; + } finally { + f = t = 0; + } + if (op[0] & 5) throw op[1]; + return { value: op[0] ? op[1] : void 0, done: true }; + } + }; +Object.defineProperty(exports, "__esModule", { value: true }); +var testing_1 = require("@angular/core/testing"); +var objective_component_1 = require("./objective.component"); +var menu_1 = require("@angular/material/menu"); +var card_1 = require("@angular/material/card"); +var testbed_1 = require("@angular/cdk/testing/testbed"); +var animations_1 = require("@angular/platform-browser/animations"); +var platform_browser_1 = require("@angular/platform-browser"); +var State_1 = require("../../shared/types/enums/State"); +var testing_2 = require("@angular/router/testing"); +var overview_service_1 = require("../../services/overview.service"); +var testData_1 = require("../../shared/testData"); +var testing_3 = require("@angular/material/menu/testing"); +var keyresult_component_1 = require("../keyresult/keyresult.component"); +var dialog_1 = require("@angular/material/dialog"); +var testing_4 = require("@angular/common/http/testing"); +var icon_1 = require("@angular/material/icon"); +var tooltip_1 = require("@angular/material/tooltip"); +var scoring_component_1 = require("../../shared/custom/scoring/scoring.component"); +var confidence_component_1 = require("../confidence/confidence.component"); +var forms_1 = require("@angular/forms"); +var de = require("../../../assets/i18n/de.json"); +var ngx_translate_testing_1 = require("ngx-translate-testing"); +var rxjs_1 = require("rxjs"); +var objective_service_1 = require("../../services/objective.service"); +var overviewServiceMock = { + getObjectiveWithKeyresults: jest.fn(), +}; +var objectiveServiceMock = { + getFullObjective: function (objectiveMin) { + var ongoingObjective = testData_1.objective; + ongoingObjective.state = State_1.State.ONGOING; + return (0, rxjs_1.of)(ongoingObjective); + }, +}; +describe("ObjectiveColumnComponent", function () { + var component; + var fixture; + var loader; + beforeEach(function () { + overviewServiceMock.getObjectiveWithKeyresults.mockReset(); + testing_1.TestBed.configureTestingModule({ + declarations: [ + objective_component_1.ObjectiveComponent, + keyresult_component_1.KeyresultComponent, + scoring_component_1.ScoringComponent, + confidence_component_1.ConfidenceComponent, + keyresult_component_1.KeyresultComponent, + ], + imports: [ + menu_1.MatMenuModule, + card_1.MatCardModule, + animations_1.NoopAnimationsModule, + testing_2.RouterTestingModule, + dialog_1.MatDialogModule, + testing_4.HttpClientTestingModule, + icon_1.MatIconModule, + tooltip_1.MatTooltipModule, + forms_1.ReactiveFormsModule, + ngx_translate_testing_1.TranslateTestingModule.withTranslations({ + de: de, + }), + ], + providers: [ + { provide: overview_service_1.OverviewService, useValue: overviewServiceMock }, + { provide: objective_service_1.ObjectiveService, useValue: objectiveServiceMock }, + ], + }).compileComponents(); + fixture = testing_1.TestBed.createComponent(objective_component_1.ObjectiveComponent); + component = fixture.componentInstance; + loader = testbed_1.TestbedHarnessEnvironment.loader(fixture); + component.objective = testData_1.objectiveMin; + }); + it("should create", function () { + expect(component).toBeTruthy(); + }); + test("Mat-menu should open and close", function () { + return __awaiter(void 0, void 0, void 0, function () { + var menu, _a, _b, _c; + return __generator(this, function (_d) { + switch (_d.label) { + case 0: + component.isWritable = true; + fixture.detectChanges(); + return [ + 4 /*yield*/, + loader.getHarness(testing_3.MatMenuHarness.with({ selector: '[data-testid="three-dot-menu"]' })), + ]; + case 1: + menu = _d.sent(); + _a = expect; + return [4 /*yield*/, menu.isOpen()]; + case 2: + _a.apply(void 0, [_d.sent()]).toBeFalsy(); + return [4 /*yield*/, menu.open()]; + case 3: + _d.sent(); + _b = expect; + return [4 /*yield*/, menu.isOpen()]; + case 4: + _b.apply(void 0, [_d.sent()]).toBeTruthy(); + return [4 /*yield*/, menu.close()]; + case 5: + _d.sent(); + _c = expect; + return [4 /*yield*/, menu.isOpen()]; + case 6: + _c.apply(void 0, [_d.sent()]).toBeFalsy(); + return [2 /*return*/]; + } + }); + }); + }); + test.each([ + [State_1.State.DRAFT, "assets/icons/draft-icon.svg"], + [State_1.State.ONGOING, "assets/icons/ongoing-icon.svg"], + [State_1.State.SUCCESSFUL, "assets/icons/successful-icon.svg"], + [State_1.State.NOTSUCCESSFUL, "assets/icons/not-successful-icon.svg"], + ])("Status-indicator should change based on the state given by the service", function (state, path) { + component.objective = __assign(__assign({}, testData_1.objectiveMin), { state: state }); + fixture.detectChanges(); + var image = fixture.debugElement.query(platform_browser_1.By.css('[data-testid="objective-state"]')); + var statusIndicatorSrc = image.attributes["src"]; + expect(statusIndicatorSrc).toBe(path); + }); + test("Mat-menu should not be present if writeable is false", function () { + return __awaiter(void 0, void 0, void 0, function () { + var menu; + return __generator(this, function (_a) { + component.isWritable = false; + fixture.detectChanges(); + menu = fixture.debugElement.query(platform_browser_1.By.css('[data-testid="objective-menu"]')); + expect(menu).toBeFalsy(); + return [2 /*return*/]; + }); + }); + }); + test("Create keyresult button should not be present if writeable is false", function () { + return __awaiter(void 0, void 0, void 0, function () { + var button; + return __generator(this, function (_a) { + component.isWritable = false; + button = fixture.debugElement.query(platform_browser_1.By.css('[data-testId="add-keyResult"]')); + expect(button).toBeFalsy(); + return [2 /*return*/]; + }); + }); + }); + it("Correct method should be called when back to draft is clicked", function () { + jest.spyOn(component, "objectiveBackToDraft"); + component.objective$.next(testData_1.objectiveMin); + fixture.detectChanges(); + var menuEntry = + component.getOngoingMenuActions()[ + component + .getOngoingMenuActions() + .map(function (menuAction) { + return menuAction.action; + }) + .indexOf("todraft") + ]; + component.handleDialogResult(menuEntry, { endState: "", comment: null, objective: testData_1.objective }); + fixture.detectChanges(); + expect(component.objectiveBackToDraft).toHaveBeenCalled(); + }); + test("Should set isBacklogQuarter right", function () { + return __awaiter(void 0, void 0, void 0, function () { + return __generator(this, function (_a) { + expect(component.isBacklogQuarter).toBeFalsy(); + testData_1.objectiveMin.quarter.label = "Backlog"; + component.objective = testData_1.objectiveMin; + fixture.detectChanges(); + component.ngOnInit(); + expect(component.isBacklogQuarter).toBeTruthy(); + return [2 /*return*/]; + }); + }); + }); + test("Should return correct menu entries when backlog", function () { + return __awaiter(void 0, void 0, void 0, function () { + var menuActions; + return __generator(this, function (_a) { + testData_1.objectiveMin.quarter.label = "Backlog"; + component.objective = testData_1.objectiveMin; + fixture.detectChanges(); + component.ngOnInit(); + menuActions = component.getDraftMenuActions(); + expect(menuActions.length).toEqual(3); + expect(menuActions[0].displayName).toEqual("Objective bearbeiten"); + expect(menuActions[1].displayName).toEqual("Objective duplizieren"); + expect(menuActions[2].displayName).toEqual("Objective veröffentlichen"); + return [2 /*return*/]; + }); + }); + }); +}); diff --git a/frontend/src/app/components/objective/objective.component.ts b/frontend/src/app/components/objective/objective.component.ts index d67984a57b..74cdefd935 100644 --- a/frontend/src/app/components/objective/objective.component.ts +++ b/frontend/src/app/components/objective/objective.component.ts @@ -1,4 +1,4 @@ -import { Component, Input, OnInit, ViewChild } from '@angular/core'; +import { Component, Input, ViewChild } from '@angular/core'; import { ObjectiveMin } from '../../shared/types/model/ObjectiveMin'; import { Router } from '@angular/router'; import { map, ReplaySubject, take } from 'rxjs'; diff --git a/frontend/src/app/components/quarter-filter/quarter-filter.component.html b/frontend/src/app/components/quarter-filter/quarter-filter.component.html index 6967a68057..023d0b9307 100644 --- a/frontend/src/app/components/quarter-filter/quarter-filter.component.html +++ b/frontend/src/app/components/quarter-filter/quarter-filter.component.html @@ -6,8 +6,8 @@ {{ quarter.fullLabel() }} diff --git a/frontend/src/app/components/team-filter/team-filter.component.html b/frontend/src/app/components/team-filter/team-filter.component.html index be579936cf..a69417cf81 100644 --- a/frontend/src/app/components/team-filter/team-filter.component.html +++ b/frontend/src/app/components/team-filter/team-filter.component.html @@ -1,19 +1,34 @@ - - - + diff --git a/frontend/src/app/components/team-filter/team-filter.component.ts b/frontend/src/app/components/team-filter/team-filter.component.ts index 5568cc9fff..f3b1e1b524 100644 --- a/frontend/src/app/components/team-filter/team-filter.component.ts +++ b/frontend/src/app/components/team-filter/team-filter.component.ts @@ -1,4 +1,4 @@ -import { ChangeDetectionStrategy, Component, OnDestroy, OnInit } from '@angular/core'; +import { ChangeDetectionStrategy, Component, Input, input, OnDestroy, OnInit } from '@angular/core'; import { BehaviorSubject, filter, Subject, Subscription, takeUntil } from 'rxjs'; import { Team } from '../../shared/types/model/Team'; import { TeamService } from '../../services/team.service'; @@ -20,6 +20,9 @@ export class TeamFilterComponent implements OnInit, OnDestroy { private unsubscribe$ = new Subject(); private subscription?: Subscription; + @Input('showMoreTeams') + showMoreTeams = true; + constructor( private teamService: TeamService, private route: ActivatedRoute, @@ -97,4 +100,12 @@ export class TeamFilterComponent implements OnInit, OnDestroy { getAllTeamIds() { return this.teams$.getValue().map((team) => team.id); } + + getTeamName(id: number): string { + let teamName = this.teams$.getValue().find((team) => team.id === id)?.name; + if (teamName && teamName.length > 10) { + teamName = teamName.substring(0, 10) + '...'; + } + return teamName ?? 'no team name'; + } } diff --git a/frontend/src/assets/i18n/de.json b/frontend/src/assets/i18n/de.json index be9c8d61a4..5e2086cf7e 100644 --- a/frontend/src/assets/i18n/de.json +++ b/frontend/src/assets/i18n/de.json @@ -65,6 +65,42 @@ } } }, + "CONFIRMATION": { + "DRAFT_CREATE": { + "TITLE": "Check-in im Draft-Status", + "TEXT": "Dein Objective befindet sich noch im DRAFT Status. Möchtest du das Check-in trotzdem erfassen?" + }, + "RELEASE": { + "TITLE": "Objective veröffentlichen", + "TEXT": "Soll dieses Objective veröffentlicht werden?" + }, + "TO_DRAFT": { + "TITLE": "Objective als Draft speichern", + "TEXT": "Soll dieses Objective als Draft gespeichert werden?" + }, + "DELETE": { + "ACTION":{ + "TITLE": "Löschen bestätigen", + "TEXT": "Möchtest du die Action wirklich löschen?" + }, + "TEAM":{ + "TITLE": "Team löschen", + "TEXT": "Möchtest du das Team '{{team}}' wirklich löschen? Zugehörige Objectives werden dadurch in allen Quartalen ebenfalls gelöscht!" + }, + "OBJECTIVE":{ + "TITLE": "Objective löschen", + "TEXT": "Möchtest du dieses Objective wirklich löschen? Zugehörige Key Results werden dadurch ebenfalls gelöscht!" + }, + "KEYRESULT":{ + "TITLE": "Key Result löschen", + "TEXT": "Möchtest du dieses Key Result wirklich löschen? Zugehörige Check-ins werden dadurch ebenfalls gelöscht!" + }, + "USER_FROM_TEAM":{ + "TITLE": "Mitglied entfernen", + "TEXT": "Möchtest du {{user}} wirklich aus dem Team '{{team}}' entfernen?" + } + } + }, "ERROR": { "UNAUTHORIZED": "Du bist nicht autorisiert, um das Objekt mit der Id {1} zu öffnen.", "NOT_FOUND": "Das Objekt '{0}' mit der Id {1} konnte nicht gefunden werden.", diff --git a/frontend/src/style/styles.scss b/frontend/src/style/styles.scss index 6ebff4ac77..8c07492ca5 100644 --- a/frontend/src/style/styles.scss +++ b/frontend/src/style/styles.scss @@ -214,6 +214,7 @@ input { .objective-three-dot-menu { border-radius: 0.5rem !important; + .mat-mdc-menu-content { padding: 0 !important; } @@ -334,7 +335,6 @@ table.okr-table { border-top-right-radius: 0 !important; } } - .mat-mdc-autocomplete-panel { &.autocomplete-bigger { max-height: 480px; diff --git a/pom.xml b/pom.xml index 7b1bbc5968..a4cfcf6bcf 100644 --- a/pom.xml +++ b/pom.xml @@ -127,4 +127,4 @@ - + \ No newline at end of file