Merge remote-tracking branch 'origin/master' into scaffolder-checkpoint-fix

This commit is contained in:
bnechyporenko
2024-03-13 21:21:34 +01:00
19 changed files with 148 additions and 37 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/cli': patch
---
Fixed a bug that could cause the `build-workspace` command to fail when invoked with `--alwaysYarnPack` enabled in environments with limited resources.
+6
View File
@@ -0,0 +1,6 @@
---
'@backstage/plugin-notifications-backend': patch
'@backstage/plugin-notifications': patch
---
the user can newly mark notifications as "Saved" for their better visibility in the future
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-airbrake': patch
---
added an optional ESLint rule - no-top-level-material-ui-4-imports - which has an auto fix function to migrate the imports and using it migrated the imports
@@ -29,7 +29,7 @@ jobs:
cache-prefix: ${{ runner.os }}-v18.x
- name: Create Snyk report
uses: snyk/actions/node@1d672a455ab3339ef0a0021e1ec809165ee12fad # master
uses: snyk/actions/node@8349f9043a8b7f0f3ee8885bf28f0b388d2446e8 # master
continue-on-error: true # Snyk CLI exits with error when vulnerabilities are found
with:
args: >
+2 -2
View File
@@ -31,7 +31,7 @@ jobs:
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
- name: Monitor and Synchronize Snyk Policies
uses: snyk/actions/node@1d672a455ab3339ef0a0021e1ec809165ee12fad # master
uses: snyk/actions/node@8349f9043a8b7f0f3ee8885bf28f0b388d2446e8 # master
with:
command: monitor
args: >
@@ -46,7 +46,7 @@ jobs:
# Above we run the `monitor` command, this runs the `test` command which is
# the one that generates the SARIF report that we can upload to GitHub.
- name: Create Snyk report
uses: snyk/actions/node@1d672a455ab3339ef0a0021e1ec809165ee12fad # master
uses: snyk/actions/node@8349f9043a8b7f0f3ee8885bf28f0b388d2446e8 # master
continue-on-error: true # To make sure that SARIF upload gets called
with:
args: >
@@ -15,6 +15,8 @@ apiVersion: scaffolder.backstage.io/v1beta3
kind: Template
metadata:
name: my_custom_template
spec:
type: service
parameters:
- title: Provide some simple information
properties:
+1
View File
@@ -112,6 +112,7 @@
"node-libs-browser": "^2.2.1",
"npm-packlist": "^5.0.0",
"ora": "^5.3.0",
"p-limit": "^3.1.0",
"p-queue": "^6.6.2",
"postcss": "^8.1.0",
"process": "^0.11.10",
@@ -21,6 +21,7 @@ import {
resolve as resolvePath,
relative as relativePath,
} from 'path';
import pLimit from 'p-limit';
import { tmpdir } from 'os';
import tar, { CreateOptions, FileOptions } from 'tar';
import partition from 'lodash/partition';
@@ -352,9 +353,11 @@ async function moveToDistWorkspace(
}
// Repacking in parallel is much faster and safe for all packages outside of the Backstage repo
// Limit concurrency to 10 to avoid resource exhaustion on larger monorepos.
const limit = pLimit(10);
await Promise.all(
safePackages.map(async (target, index) =>
pack(target, `temp-package-${index}.tgz`),
safePackages.map((target, index) =>
limit(() => pack(target, `temp-package-${index}.tgz`)),
),
);
}
+5 -1
View File
@@ -1 +1,5 @@
module.exports = require('@backstage/cli/config/eslint-factory')(__dirname);
module.exports = require('@backstage/cli/config/eslint-factory')(__dirname, {
rules: {
'@backstage/no-top-level-material-ui-4-imports': 'error',
},
});
@@ -14,7 +14,8 @@
* limitations under the License.
*/
import React from 'react';
import { makeStyles, TextField } from '@material-ui/core';
import TextField from '@material-ui/core/TextField';
import { makeStyles } from '@material-ui/core/styles';
import { Context } from '../ContextProvider';
const useStyles = makeStyles(theme => ({
@@ -22,7 +22,8 @@ import {
Progress,
} from '@backstage/core-components';
import { useApi } from '@backstage/core-plugin-api';
import { Grid, Typography } from '@material-ui/core';
import Grid from '@material-ui/core/Grid';
import Typography from '@material-ui/core/Typography';
import { makeStyles } from '@material-ui/core/styles';
import React from 'react';
import useAsync from 'react-use/lib/useAsync';
@@ -224,6 +224,12 @@ export async function createRouter(
opts.read = false;
// or keep undefined
}
if (req.query.saved === 'true') {
opts.saved = true;
} else if (req.query.saved === 'false') {
opts.saved = false;
// or keep undefined
}
if (req.query.created_after) {
const sinceEpoch = Date.parse(String(req.query.created_after));
if (isNaN(sinceEpoch)) {
+1
View File
@@ -22,6 +22,7 @@ export type GetNotificationsOptions = {
limit?: number;
search?: string;
read?: boolean;
saved?: boolean;
createdAfter?: Date;
sort?: 'created' | 'topic' | 'origin';
sortOrder?: 'asc' | 'desc';
@@ -30,6 +30,7 @@ export type GetNotificationsOptions = {
limit?: number;
search?: string;
read?: boolean;
saved?: boolean;
createdAfter?: Date;
sort?: 'created' | 'topic' | 'origin';
sortOrder?: 'asc' | 'desc';
@@ -61,6 +61,9 @@ export class NotificationsClient implements NotificationsApi {
if (options?.read !== undefined) {
queryString.append('read', options.read ? 'true' : 'false');
}
if (options?.saved !== undefined) {
queryString.append('saved', options.saved ? 'true' : 'false');
}
if (options?.createdAfter !== undefined) {
queryString.append('created_after', options.createdAfter.toISOString());
}
@@ -37,6 +37,8 @@ export type NotificationsFiltersProps = {
onCreatedAfterChanged: (value: string) => void;
sorting: SortBy;
onSortingChanged: (sortBy: SortBy) => void;
saved?: boolean;
onSavedChanged: (checked: boolean | undefined) => void;
};
export const CreatedAfterOptions: {
@@ -113,6 +115,8 @@ export const NotificationsFilters = ({
onUnreadOnlyChanged,
createdAfter,
onCreatedAfterChanged,
saved,
onSavedChanged,
}: NotificationsFiltersProps) => {
const sortByText = getSortByText(sorting);
@@ -122,13 +126,23 @@ export const NotificationsFilters = ({
onCreatedAfterChanged(event.target.value as string);
};
const handleOnUnreadOnlyChanged = (
const handleOnViewChanged = (
event: React.ChangeEvent<{ name?: string; value: unknown }>,
) => {
let value = undefined;
if (event.target.value === 'unread') value = true;
if (event.target.value === 'read') value = false;
onUnreadOnlyChanged(value);
if (event.target.value === 'unread') {
onUnreadOnlyChanged(true);
onSavedChanged(undefined);
} else if (event.target.value === 'read') {
onUnreadOnlyChanged(false);
onSavedChanged(undefined);
} else if (event.target.value === 'saved') {
onUnreadOnlyChanged(undefined);
onSavedChanged(true);
} else {
// All
onUnreadOnlyChanged(undefined);
onSavedChanged(undefined);
}
};
const handleOnSortByChanged = (
@@ -139,9 +153,14 @@ export const NotificationsFilters = ({
onSortingChanged({ ...option.sortBy });
};
let unreadOnlyValue = 'all';
if (unreadOnly) unreadOnlyValue = 'unread';
if (unreadOnly === false) unreadOnlyValue = 'read';
let viewValue = 'all';
if (saved) {
viewValue = 'saved';
} else if (unreadOnly) {
viewValue = 'unread';
} else if (unreadOnly === false) {
viewValue = 'read';
}
return (
<>
@@ -156,10 +175,11 @@ export const NotificationsFilters = ({
<Select
labelId="notifications-filter-view"
label="View"
value={unreadOnlyValue}
onChange={handleOnUnreadOnlyChanged}
value={viewValue}
onChange={handleOnViewChanged}
>
<MenuItem value="unread">New only</MenuItem>
<MenuItem value="saved">Saved</MenuItem>
<MenuItem value="read">Marked as read</MenuItem>
<MenuItem value="all">All</MenuItem>
</Select>
@@ -37,6 +37,7 @@ export const NotificationsPage = () => {
const [refresh, setRefresh] = React.useState(false);
const { lastSignal } = useSignal('notifications');
const [unreadOnly, setUnreadOnly] = React.useState<boolean | undefined>(true);
const [saved, setSaved] = React.useState<boolean | undefined>(undefined);
const [pageNumber, setPageNumber] = React.useState(0);
const [pageSize, setPageSize] = React.useState(5);
const [containsText, setContainsText] = React.useState<string>();
@@ -56,6 +57,9 @@ export const NotificationsPage = () => {
if (unreadOnly !== undefined) {
options.read = !unreadOnly;
}
if (saved !== undefined) {
options.saved = saved;
}
const createdAfterDate = CreatedAfterOptions[createdAfter].getDate();
if (createdAfterDate.valueOf() > 0) {
@@ -64,7 +68,15 @@ export const NotificationsPage = () => {
return api.getNotifications(options);
},
[containsText, unreadOnly, createdAfter, pageNumber, pageSize, sorting],
[
containsText,
unreadOnly,
createdAfter,
pageNumber,
pageSize,
sorting,
saved,
],
);
useEffect(() => {
@@ -100,6 +112,8 @@ export const NotificationsPage = () => {
onCreatedAfterChanged={setCreatedAfter}
onSortingChanged={setSorting}
sorting={sorting}
saved={saved}
onSavedChanged={setSaved}
/>
</Grid>
<Grid item xs={10}>
@@ -17,13 +17,11 @@ import React, { useMemo } from 'react';
import throttle from 'lodash/throttle';
// @ts-ignore
import RelativeTime from 'react-relative-time';
import { Box, IconButton, Tooltip, Typography } from '@material-ui/core';
import { Box, Grid, IconButton, Tooltip, Typography } from '@material-ui/core';
import { Notification } from '@backstage/plugin-notifications-common';
import { notificationsApiRef } from '../../api';
import { useApi } from '@backstage/core-plugin-api';
import MarkAsUnreadIcon from '@material-ui/icons/Markunread';
import MarkAsReadIcon from '@material-ui/icons/CheckCircle';
import {
Link,
Table,
@@ -31,6 +29,11 @@ import {
TableColumn,
} from '@backstage/core-components';
import MarkAsUnreadIcon from '@material-ui/icons/Markunread' /* TODO: use Drafts and MarkAsUnread once we have mui 5 icons */;
import MarkAsReadIcon from '@material-ui/icons/CheckCircle';
import MarkAsUnsavedIcon from '@material-ui/icons/LabelOff' /* TODO: use BookmarkRemove and BookmarkAdd once we have mui 5 icons */;
import MarkAsSavedIcon from '@material-ui/icons/Label';
const ThrottleDelayMs = 1000;
/** @public */
@@ -71,6 +74,18 @@ export const NotificationsTable = ({
[notificationsApi, onUpdate],
);
const onSwitchSavedStatus = React.useCallback(
(notification: Notification) => {
notificationsApi
.updateNotifications({
ids: [notification.id],
saved: !notification.saved,
})
.then(() => onUpdate());
},
[notificationsApi, onUpdate],
);
const throttledContainsTextHandler = useMemo(
() => throttle(setContainsText, ThrottleDelayMs),
[setContainsText],
@@ -136,7 +151,6 @@ export const NotificationsTable = ({
// },
// },
{
// TODO: action for saving notifications
// actions
width: '1rem',
render: (notification: Notification) => {
@@ -147,24 +161,47 @@ export const NotificationsTable = ({
? MarkAsUnreadIcon
: MarkAsReadIcon;
const markAsSavedText = !!notification.saved
? 'Undo save'
: 'Save for later';
const SavedIconComponent = !!notification.saved
? MarkAsUnsavedIcon
: MarkAsSavedIcon;
return (
<Tooltip title={markAsReadText}>
<IconButton
onClick={() => {
onSwitchReadStatus(notification);
}}
>
<IconComponent aria-label={markAsReadText} />
</IconButton>
</Tooltip>
<Grid container wrap="nowrap">
<Grid item>
<Tooltip title={markAsSavedText}>
<IconButton
onClick={() => {
onSwitchSavedStatus(notification);
}}
>
<SavedIconComponent aria-label={markAsSavedText} />
</IconButton>
</Tooltip>
</Grid>
<Grid item>
<Tooltip title={markAsReadText}>
<IconButton
onClick={() => {
onSwitchReadStatus(notification);
}}
>
<IconComponent aria-label={markAsReadText} />
</IconButton>
</Tooltip>
</Grid>
</Grid>
);
},
},
],
[onSwitchReadStatus],
[onSwitchReadStatus, onSwitchSavedStatus],
);
// TODO: render "Saved notifications" as "Pinned"
return (
<Table<Notification>
isLoading={isLoading}
+4 -3
View File
@@ -3691,6 +3691,7 @@ __metadata:
nodemon: ^3.0.1
npm-packlist: ^5.0.0
ora: ^5.3.0
p-limit: ^3.1.0
p-queue: ^6.6.2
postcss: ^8.1.0
process: ^0.11.10
@@ -18735,12 +18736,12 @@ __metadata:
linkType: hard
"@types/dockerode@npm:^3.3.0, @types/dockerode@npm:^3.3.21":
version: 3.3.24
resolution: "@types/dockerode@npm:3.3.24"
version: 3.3.26
resolution: "@types/dockerode@npm:3.3.26"
dependencies:
"@types/docker-modem": "*"
"@types/node": "*"
checksum: 00329ba9225f5b57bfc0ba8c4dddb17100ebe13c5192fe8a14fce59eec456d258e814b6c78df26d7d7bb878fc38455f559f278490c9c8efad8f708335643b40e
checksum: ec1e83ef2d938813c8fa72f7c8744a9bf598369d8a0f340377ddff25c6e7d118379cd297eecbe4af2b7aa64bf5cd1bb66b32d1e3874a062433b967265be94f09
languageName: node
linkType: hard