Skip to content
Closed
Show file tree
Hide file tree
Changes from all 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
18 changes: 17 additions & 1 deletion config/locales/js-en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -375,7 +375,23 @@
direct_hit_available: "Work package with exact ID found. Press Enter to open it."
items_available: "%{count} items available"
recently_viewed: "Recently viewed"
search_placeholder_expanded: "Search work packages by subject, project, type, status or ID"
search_placeholder_expanded: "Search work packages, meetings and wiki pages"
wiki_pages: "Wiki pages"
quick_filters:

Check failure on line 380 in config/locales/js-en.yml

View workflow job for this annotation

GitHub Actions / Yamllint

[yamllint] reported by reviewdog 🐶 [error] wrong ordering of key "quick_filters" in mapping (key-ordering) Raw Output: config/locales/js-en.yml:380:7: [error] wrong ordering of key "quick_filters" in mapping (key-ordering)

Check failure on line 380 in config/locales/js-en.yml

View workflow job for this annotation

GitHub Actions / Yamllint

[yamllint] reported by reviewdog 🐶 [error] wrong ordering of key "quick_filters" in mapping (key-ordering) Raw Output: config/locales/js-en.yml:380:7: [error] wrong ordering of key "quick_filters" in mapping (key-ordering)
accountable: "Accountable"
any_time: "Any time"
attended: "Attended"
closed: "Closed"
involvement: "Involvement"
invited: "Invited"

Check failure on line 386 in config/locales/js-en.yml

View workflow job for this annotation

GitHub Actions / Yamllint

[yamllint] reported by reviewdog 🐶 [error] wrong ordering of key "invited" in mapping (key-ordering) Raw Output: config/locales/js-en.yml:386:9: [error] wrong ordering of key "invited" in mapping (key-ordering)

Check failure on line 386 in config/locales/js-en.yml

View workflow job for this annotation

GitHub Actions / Yamllint

[yamllint] reported by reviewdog 🐶 [error] wrong ordering of key "invited" in mapping (key-ordering) Raw Output: config/locales/js-en.yml:386:9: [error] wrong ordering of key "invited" in mapping (key-ordering)
meeting_date: "Meeting date"
open: "Open"
past: "Past"
past_7_days: "Past 7 days"
past_30_days: "Past 30 days"

Check failure on line 391 in config/locales/js-en.yml

View workflow job for this annotation

GitHub Actions / Yamllint

[yamllint] reported by reviewdog 🐶 [error] wrong ordering of key "past_30_days" in mapping (key-ordering) Raw Output: config/locales/js-en.yml:391:9: [error] wrong ordering of key "past_30_days" in mapping (key-ordering)

Check failure on line 391 in config/locales/js-en.yml

View workflow job for this annotation

GitHub Actions / Yamllint

[yamllint] reported by reviewdog 🐶 [error] wrong ordering of key "past_30_days" in mapping (key-ordering) Raw Output: config/locales/js-en.yml:391:9: [error] wrong ordering of key "past_30_days" in mapping (key-ordering)
past_year: "Past year"
upcoming: "Upcoming"
yesterday: "Yesterday"
title:
all_projects: "all projects"
project_and_subprojects: "and all subprojects"
Expand Down
27 changes: 27 additions & 0 deletions docs/api/apiv3/components/schemas/wiki_page_collection_model.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Schema: WikiPageCollectionModel
---
allOf:
- $ref: './paginated_collection_model.yml'
- type: object
required:
- _embedded
- _links
properties:
_embedded:
type: object
required:
- elements
properties:
elements:
type: array
items:
$ref: './wiki_page_model.yml'
_links:
type: object
required:
- self
properties:
self:
allOf:
- $ref: './link.yml'
- description: The link to this wiki page collection resource.
5 changes: 5 additions & 0 deletions docs/api/apiv3/components/schemas/wiki_page_model.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@ properties:
_links:
type: object
properties:
showWikiPage:
allOf:
- "$ref": "./link.yml"
- description: Open this wiki page in the web application
readOnly: true
addAttachment:
allOf:
- "$ref": "./link.yml"
Expand Down
4 changes: 4 additions & 0 deletions docs/api/apiv3/openapi-spec.yml
Original file line number Diff line number Diff line change
Expand Up @@ -569,6 +569,8 @@ paths:
"$ref": "./paths/views.yml"
"/api/v3/views/{id}":
"$ref": "./paths/view.yml"
"/api/v3/wiki_pages":
"$ref": "./paths/wiki_pages.yml"
"/api/v3/wiki_pages/{id}":
"$ref": "./paths/wiki_page.yml"
"/api/v3/wiki_pages/{id}/attachments":
Expand Down Expand Up @@ -1160,6 +1162,8 @@ components:
"$ref": "./components/schemas/week_day_write_model.yml"
Wiki_PageModel:
"$ref": "./components/schemas/wiki_page_model.yml"
WikiPageCollectionModel:
"$ref": "./components/schemas/wiki_page_collection_model.yml"
WorkPackageModel:
"$ref": "./components/schemas/work_package_model.yml"
WorkPackageFormModel:
Expand Down
24 changes: 24 additions & 0 deletions docs/api/apiv3/paths/meetings.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,30 @@ get:
- Meetings
description: |-
Retrieve a paginated collection of meetings visible to the authenticated user.
parameters:
- name: offset
in: query
required: false
description: Page number inside the requested collection.
schema:
type: integer
default: 1
- name: pageSize
in: query
required: false
description: Number of elements to display per page.
schema:
type: integer
- name: filters
in: query
required: false
description: |-
JSON specifying filter conditions. The `search` filter with the `**` operator searches meeting titles,
agenda item titles and notes, and outcome notes. Results always remain restricted to meetings visible to
the authenticated user.
example: '[{ "search": { "operator": "**", "values": ["quarterly planning"] } }]'
schema:
type: string
responses:
'200':
description: OK
Expand Down
41 changes: 41 additions & 0 deletions docs/api/apiv3/paths/wiki_pages.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# /api/v3/wiki_pages
---
get:
summary: List visible wiki pages
operationId: list_wiki_pages
tags:
- Wiki Pages
description: |-
Retrieve a paginated collection of wiki pages in projects where the authenticated user may view the wiki.
parameters:
- name: offset
in: query
required: false
description: Page number inside the requested collection.
schema:
type: integer
default: 1
- name: pageSize
in: query
required: false
description: Number of elements to display per page.
schema:
type: integer
- name: filters
in: query
required: false
description: |-
JSON specifying filter conditions. The `search` filter with the `**` operator searches wiki page titles
and text. Results always remain restricted to pages visible to the authenticated user.
example: '[{ "search": { "operator": "**", "values": ["release plan"] } }]'
schema:
type: string
responses:
'200':
description: OK
content:
application/hal+json:
schema:
$ref: "../components/schemas/wiki_page_collection_model.yml"
'400':
$ref: "../components/responses/invalid_request_body.yml"
3 changes: 1 addition & 2 deletions docs/api/apiv3/tags/wiki_pages.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@
description: |-
Represents an individual page in a project's wiki.

*This resource is currently a stub*

## Actions

| Link | Description | Condition |
Expand All @@ -17,6 +15,7 @@ description: |-
| self | This wiki page | WikiPage | not null | READ |
| attachments | The files attached to this wiki page | Collection | | READ |
| project | The project the wiki page belongs to | Project | not null | READ |
| showWikiPage | Open the wiki page in the web application | Link | not null | READ |

## Local Properties

Expand Down
3 changes: 3 additions & 0 deletions frontend/src/app/core/apiv3/api-v3.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,9 @@ export class ApiV3Service {
// /api/v3/meetings
public readonly meetings = this.apiV3CollectionEndpoint('meetings');

// /api/v3/wiki_pages
public readonly wiki_pages = this.apiV3CollectionEndpoint('wiki_pages');

// /api/v3/memberships
public readonly memberships = this.apiV3CustomEndpoint(ApiV3MembershipsPaths);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,8 +114,13 @@ export class ApiV3WorkPackagesPaths extends ApiV3Collection<WorkPackageResource,
* @param idOnly
* @param additionalParams Additional set of params to the API
*/
public filterByTypeaheadOrId(term:string, idOnly = false, additionalParams:Record<string, string> = {}):ApiV3WorkPackageCachedSubresource {
const filters:ApiV3FilterBuilder = new ApiV3FilterBuilder();
public filterByTypeaheadOrId(
term:string,
idOnly = false,
additionalParams:Record<string, string> = {},
additionalFilters:ApiV3FilterBuilder = new ApiV3FilterBuilder(),
):ApiV3WorkPackageCachedSubresource {
const filters = additionalFilters.clone();

if (idOnly) {
filters.add('id', '=', [term]);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@
[searchFn]="customSearchFn"
[virtualScroll]="false"
(focus)="onFocus()"
(blur)="onFocusOut()"
(blur)="onFocusOut($event)"
(search)="search($event)"
(close)="onClose()"
(change)="followItem($event)"
Expand All @@ -62,50 +62,135 @@
@if (!searchTerm && (hasRecentItems$ | async)) {
<div>{{text.recently_viewed}}</div>
}
@if (searchTerm) {
<div class="global-search--toolbar">
<div class="global-search--tabs" role="tablist">
@for (resultType of resultTypes; track resultType) {
<button
type="button"
class="global-search--tab"
role="tab"
[class.-selected]="activeResultType === resultType"
[attr.aria-selected]="activeResultType === resultType"
(mousedown)="$event.preventDefault()"
(click)="$event.stopPropagation(); selectResultType(resultType)"
>{{ text[resultType] }}</button>
}
</div>
<select
class="global-search--project-scope-select"
[attr.aria-label]="text.project"
[value]="selectedProjectScope"
(mousedown)="onProjectScopeMouseDown($event)"
(click)="$event.stopPropagation()"
(blur)="onProjectScopeBlur()"
(change)="selectProjectScope($event)"
>
@for (projectScope of projectScopes; track projectScope) {
<option [value]="projectScope">{{ text[projectScope] }}</option>
}
</select>
</div>
<div class="global-search--quick-filters">
<div class="global-search--quick-filter-group">
<span class="global-search--quick-filter-label">{{ text.last_updated }}</span>
@for (filter of lastUpdatedFilters; track filter) {
<button
type="button"
class="global-search--quick-filter"
[class.-selected]="selectedLastUpdatedFilter === filter"
[attr.aria-pressed]="selectedLastUpdatedFilter === filter"
(mousedown)="$event.preventDefault()"
(click)="$event.stopPropagation(); selectLastUpdatedFilter(filter)"
>{{ text[filter] }}</button>
}
</div>
@if (activeResultType === 'work_packages') {
<div class="global-search--quick-filter-group">
<span class="global-search--quick-filter-label">{{ text.status }}</span>
@for (filter of workPackageStatusFilters; track filter) {
<button
type="button"
class="global-search--quick-filter"
[class.-selected]="selectedWorkPackageStatusFilter === filter"
[attr.aria-pressed]="selectedWorkPackageStatusFilter === filter"
(mousedown)="$event.preventDefault()"
(click)="$event.stopPropagation(); selectWorkPackageStatusFilter(filter)"
>{{ text[filter] }}</button>
}
</div>
<div class="global-search--quick-filter-group">
<span class="global-search--quick-filter-label">{{ text.involvement }}</span>
@for (filter of workPackageInvolvementFilters; track filter) {
<button
type="button"
class="global-search--quick-filter"
[class.-selected]="selectedWorkPackageInvolvementFilter === filter"
[attr.aria-pressed]="selectedWorkPackageInvolvementFilter === filter"
(mousedown)="$event.preventDefault()"
(click)="$event.stopPropagation(); selectWorkPackageInvolvementFilter(filter)"
>{{ text[filter] }}</button>
}
</div>
}
@if (activeResultType === 'meetings') {
<div class="global-search--quick-filter-group">
<span class="global-search--quick-filter-label">{{ text.meeting_date }}</span>
@for (filter of meetingTimeFilters; track filter) {
<button
type="button"
class="global-search--quick-filter"
[class.-selected]="selectedMeetingTimeFilter === filter"
[attr.aria-pressed]="selectedMeetingTimeFilter === filter"
(mousedown)="$event.preventDefault()"
(click)="$event.stopPropagation(); selectMeetingTimeFilter(filter)"
>{{ text[filter] }}</button>
}
</div>
<div class="global-search--quick-filter-group">
<span class="global-search--quick-filter-label">{{ text.involvement }}</span>
@for (filter of meetingInvolvementFilters; track filter) {
<button
type="button"
class="global-search--quick-filter"
[class.-selected]="selectedMeetingInvolvementFilter === filter"
[attr.aria-pressed]="selectedMeetingInvolvementFilter === filter"
(mousedown)="$event.preventDefault()"
(click)="$event.stopPropagation(); selectMeetingInvolvementFilter(filter)"
>{{ text[filter] }}</button>
}
</div>
}
</div>
}
</ng-template>
<ng-template op-autocompleter-option-tmp let-item let-index="index" let-search="searchTerm">
@if (!item.id) {
<div>
<a tabindex="0"
href="#"
class="global-search--option"
(click)="$event.preventDefault(); followItem(item)"
(keydown.enter)="$event.preventDefault(); followItem(item)"
(keydown.space)="$event.preventDefault(); followItem(item)">
<div class="global-search--search-term">{{ currentValue }}</div>
<div class="global-search--project-scope" title="{{ item.projectScope }}">{{ item.text }} ↵</div>
</a>
</div>
} @else {
<a
class="global-search--option"
[href]="wpPath(item.displayId)"
(click)="redirectToWp(item.displayId, $event)"
style="line-height: 1"
>
<div class="global-search--option-meta">
<div class="global-search--wp-line">
<a
class="global-search--option global-search--result-card"
[href]="resultPath(item)"
(click)="redirectToResult(item, $event)"
>
<span class="global-search--result-icon" aria-hidden="true">
<svg octicon [icon]="resultIcon(item)" size="small"></svg>
</span>
<div class="global-search--option-meta">
<div class="global-search--result-kind-line">
<span class="global-search--result-kind">{{ resultTypeLabel(item) }}</span>
@if (isWorkPackage(item)) {
<span aria-hidden="true">·</span>
<span [ngClass]="highlighting('type', item.type.id)">
{{ item.type.name }}
</span>
<span>{{ item.formattedId }}</span>
<span [ngClass]="statusHighlighting(item.status.id)">
{{ item.status.name }}
</span>
<span>-</span>
<span
class="global-search--wp-project"
[attr.title]="item.project.name"
>
{{ item.project.name }}
</span>
</div>
<span class="global-search--wp-subject">
{{ item.subject }}
</span>
}
</div>
</a>
}
<span class="global-search--result-title">{{ resultTitle(item) }}</span>
<span class="global-search--result-project">{{ item.project.name }}</span>
</div>
</a>
</ng-template>
</op-autocompleter>
</div>
Loading
Loading