-
Notifications
You must be signed in to change notification settings - Fork 112
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Notification system #4149
base: main
Are you sure you want to change the base?
Notification system #4149
Conversation
…into notifications-sytem
Thanks for your contribution! Depending on what you are working on, you may want to request a review from a Shopify team:
|
import {CLI_KIT_VERSION} from '../common/version.js' | ||
import {NotificationsKey, cacheRetrieveOrRepopulate, getCache, setCache} from '../../private/node/conf-store.js' | ||
|
||
const URL = 'https://raw.githubusercontent.com/Shopify/cli/notifications/notifications.json' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We have to change this URL before merging to:
const URL = 'https://raw.githubusercontent.com/Shopify/cli/notifications/notifications.json' | |
const URL = 'https://raw.githubusercontent.com/Shopify/cli/main/notifications.json' |
Coverage report
Show new covered files 🐣
Show files with reduced coverage 🔻
Test suite run success1735 tests passing in 797 suites. Report generated by 🧪jest coverage report action from 3f53781 |
Differences in type declarationsWe detected differences in the type declarations generated by Typescript for this branch compared to the baseline ('main' branch). Please, review them to ensure they are backward-compatible. Here are some important things to keep in mind:
New type declarationspackages/cli-kit/dist/public/node/global-context.d.tsexport interface GlobalContext {
currentCommandId: string;
[key: string]: string;
}
/**
* Get the current command ID.
*
* @returns Current command ID.
*/
export declare function getCurrentCommandId(): string;
/**
* Set the current command ID.
*
* @param commandId - Command ID.
*/
export declare function setCurrentCommandId(commandId: string): void;
packages/cli-kit/dist/public/node/notifications-system.d.tsexport interface Notifications {
notifications: Notification[];
}
export interface Notification {
id: string;
message: string;
cta?: {
label: string;
url: string;
};
type: 'info' | 'warning' | 'error';
title?: string;
minVersion?: string;
maxVersion?: string;
minDate?: string;
maxDate?: string;
commands?: string[];
surface?: 'app' | 'theme' | 'hydrogen' | string;
frequency?: 'always' | 'once' | 'once_a_day' | 'once_a_week';
}
/**
* Shows notifications to the user if they meet the criteria specified in the notifications.json file.
*
* @param currentSurfaces - The surfaces present in the current project (usually for app extensions).
* @returns - A promise that resolves when the notifications have been shown.
*/
export declare function showNotificationsIfNeeded(currentSurfaces?: string[]): Promise<void>;
/**
* Get notifications list from cache or fetch it if not present.
*
* @returns A Notifications object.
*/
export declare function getNotifications(): Promise<Notifications>;
/**
* Filters notifications based on the version of the CLI.
*
* @param notifications - The notifications to filter.
* @param commandId - The command ID to filter by.
* @param currentSurfaces - The surfaces present in the current project (usually for app extensions).
* @param today - The current date.
* @param currentVersion - The current version of the CLI.
* @returns - The filtered notifications.
*/
export declare function filterNotifications(notifications: Notification[], commandId: string, currentSurfaces?: string[], today?: Date, currentVersion?: string): Notification[];
/**
* Returns a string with the filters from a notification, one by line.
*
* @param notification - The notification to get the filters from.
* @returns A string with human-readable filters from the notification.
*/
export declare function stringifyFilters(notification: Notification): string;
/**
* Reads the notifications from the local file.
*
* @returns A Notifications object.
*/
export declare function getLocalNotifications(): Promise<Notifications>;
Existing type declarationspackages/cli-kit/dist/private/node/conf-store.d.ts@@ -5,9 +5,13 @@ interface CacheValue<T> {
}
export type IntrospectionUrlKey = ;
export type PackageVersionKey = ;
+export type NotificationsKey = ;
+type NotificationKey = ;
interface Cache {
[introspectionUrlKey: IntrospectionUrlKey]: CacheValue<string>;
[packageVersionKey: PackageVersionKey]: CacheValue<string>;
+ [notifications: NotificationsKey]: CacheValue<string>;
+ [notification: NotificationKey]: CacheValue<string>;
}
export interface ConfSchema {
sessionStore: string;
@@ -40,4 +44,6 @@ type CacheValueForKey<TKey extends keyof Cache> = NonNullable<Cache[TKey]>['valu
* @returns The value from the cache or the result of the function.
*/
export declare function cacheRetrieveOrRepopulate(key: keyof Cache, fn: () => Promise<CacheValueForKey<typeof key>>, timeout?: number, config?: LocalStorage<ConfSchema>): Promise<CacheValueForKey<typeof key>>;
+export declare function getCache(key: keyof Cache, config?: LocalStorage<ConfSchema>): CacheValue<string> | undefined;
+export declare function setCache(key: keyof Cache, value: string, config?: LocalStorage<ConfSchema>): void;
export {};
\ No newline at end of file
|
We detected some changes at either packages/*/src or packages/cli-kit/assets/cli-ruby/** and there are no updates in the .changeset. |
/snapit |
@@ -0,0 +1,36 @@ | |||
export interface GlobalContext { | |||
currentCommandId: string | |||
[key: string]: string |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's this for? It basically makes GlobalContext close to untyped -- I haven't seen a use of it. Would drop it if not used.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added it initially to support anything, but we don't need it anymore if we have specific types for each property stored in the context. We should remove it.
/** | ||
* Fetch notifications from GitHub. | ||
*/ | ||
async function fetchNotifications(): Promise<string> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we should look into wrapping this in a timeout. Like, if it takes longer than Xms to load, we stop waiting and move on. Given that the command is paused whilst this is happening.
notifications: Notification[] | ||
} | ||
|
||
export interface Notification { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The typing here might not match the format used in the notification file -- e.g. if that file evolves but the CLI reading from it is an old one. I think it'd be helpful to defend against that -- whilst we're the authors of the notification file, we're still decoupled from it. I think I would swap this for a Zod schema, and parse the file using that. Then, if parsing fails because it doesn't match what this CLI's schema is expecting we can skip the notifications and move on. Obviously we would also try not to break the file as well.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's a good idea 👌
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We are catching errors here, so if the parsing fails, it already moves on. Any other advantage of using Zod here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That only catches errors -- something that literally ends up in a thrown error -- if the format changes you might have a scenario where no error is thrown but the behaviour is wrong
* @param notifications - The notifications to render. | ||
*/ | ||
async function renderNotifications(notifications: Notification[]) { | ||
notifications.forEach((notification) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is going to be quite noisy if there are multiple notifications matching on any given command run -- each section having it's own box. I'm not sure what the best alternative is. Or, how likely it is to happen. What do you think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't know. We thought about it and decided to show all. What if there are two important notifications? Also, we are in control of the messages, so we should be careful about not bothering too much and try to minimize multiple ones.
However, we can improve it in the future and maybe add a priority field to only show one or two.
* Fetch notifications from GitHub. | ||
*/ | ||
async function fetchNotifications(): Promise<string> { | ||
const response = await fetch(URL) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's an analytics helper to separate out network time from time with the command being active. We should wrap this up in that.
WHY are these changes introduced?
We want to notify CLI users about deprecations, new features or events
WHAT is this pull request doing?
Adds two commands:
Then, before each command we check the remote notifications.json from this repository and show the required ones. The file is cached for 24h.
Demo: https://hackdays.shopify.io/projects/18981
How to test your changes?
p shopify notifications generate
p shopify notifications list
Post-release steps
Merge the internal doc: https://github.com/Shopify/vault-pages/pull/7111
Measuring impact
How do we know this change was effective? Please choose one:
Checklist