Merge remote-tracking branch 'origin/master' into scaffolder-checkpoint-fix
This commit is contained in:
@@ -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.
|
||||
@@ -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
|
||||
@@ -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: >
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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`)),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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)) {
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
+30
-10
@@ -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}
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user