Преглед на файлове

initial experiment for the blacklist feature

Eric Charles преди 5 години
родител
ревизия
d47c1f446e

+ 1 - 0
packages/extensionmanager/src/index.ts

@@ -3,4 +3,5 @@
 
 export * from './model';
 export * from './query';
+export * from './listings';
 export * from './widget';

+ 82 - 0
packages/extensionmanager/src/listings.ts

@@ -0,0 +1,82 @@
+// Copyright (c) Jupyter Development Team.
+// Distributed under the terms of the Modified BSD License.
+
+/**
+ * Listing search result structure (subset).
+ *
+ */
+export interface IListResult {
+  /**
+   * A collection of search results.
+   */
+  blacklist: string[];
+
+  /**
+   * Timestamp of the search result creation.
+   */
+  time: string;
+}
+
+/**
+ * An object for searching an List registry.
+ *
+ */
+export class Lister {
+  /**
+   * Create a Lister object.
+   *
+   * @param blackListUri The URI of the list registry to use.
+   * @param whiteListUri The URI of the CDN to use for fetching full package data.
+   */
+  constructor(
+    blackListUri = 'http://localhost:8080/lists/blacklist.json',
+    whiteListUri = 'http://localhost:8080/lists/whitelist.json'
+  ) {
+    this.blackListUri = blackListUri;
+    this.whiteListUri = whiteListUri;
+  }
+
+  /**
+   * Search for a jupyterlab extension.
+   *
+   * @param page The page of results to fetch.
+   * @param pageination The pagination size to use. See registry API documentation for acceptable values.
+   */
+  getBlackList(): Promise<IListResult> {
+    const uri = new URL('', this.blackListUri);
+    return fetch(uri.toString()).then((response: Response) => {
+      if (response.ok) {
+        return response.json();
+      }
+      return [];
+    });
+  }
+
+  /**
+   * The URI of the black listing registry to use.
+   */
+  blackListUri: string;
+
+  /**
+   * The URI of the white listing registry to use.
+   */
+  whiteListUri: string;
+}
+
+/**
+ *
+ */
+export function isWhiteListed(name: string): boolean {
+  /**
+   * A list of whitelisted NPM orgs.
+   */
+  const whitelist = ['jupyterlab', 'jupyter-widgets'];
+  const parts = name.split('/');
+  const first = parts[0];
+  return (
+    parts.length > 1 && // Has a first part
+    !!first && // with a finite length
+    first[0] === '@' && // corresponding to an org name
+    whitelist.indexOf(first.slice(1)) !== -1 // in the org whitelist.
+  );
+}

+ 67 - 14
packages/extensionmanager/src/model.ts

@@ -25,6 +25,8 @@ import { reportInstallError } from './dialog';
 
 import { Searcher, ISearchResult, isJupyterOrg } from './query';
 
+import { Lister, IListResult } from './listings';
+
 /**
  * Information about an extension.
  */
@@ -68,6 +70,15 @@ export interface IEntry {
    * The installed version of the extension.
    */
   installed_version: string;
+
+  isBlacklisted: boolean;
+}
+
+export interface IListEntry {
+  /**
+   * The name of the extension.
+   */
+  name: string;
 }
 
 /**
@@ -399,8 +410,10 @@ export class ListModel extends VDomModel {
    * @param res Promise to an npm query result.
    */
   protected async translateSearchResult(
-    res: Promise<ISearchResult>
+    res: Promise<ISearchResult>,
+    blacklistMap: Map<string, IListEntry>
   ): Promise<{ [key: string]: IEntry }> {
+    console.log('::::', typeof blacklistMap);
     let entries: { [key: string]: IEntry } = {};
     for (let obj of (await res).objects) {
       let pkg = obj.package;
@@ -420,19 +433,31 @@ export class ListModel extends VDomModel {
         enabled: false,
         status: null,
         latest_version: pkg.version,
-        installed_version: ''
+        installed_version: '',
+        isBlacklisted: blacklistMap.has(pkg.name)
       };
     }
     return entries;
   }
 
+  protected async translateBlacklistResult(
+    res: Promise<IListResult>
+  ): Promise<Map<string, IListEntry>> {
+    let entries: Map<string, IListEntry> = new Map();
+    for (let obj of (await res).blacklist) {
+      entries.set(obj, { name: obj });
+    }
+    return entries;
+  }
+
   /**
    * Translate installed extensions information from the server into entries.
    *
    * @param res Promise to the server reply data.
    */
   protected async translateInstalled(
-    res: Promise<IInstalledEntry[]>
+    res: Promise<IInstalledEntry[]>,
+    blacklistMap: Map<string, IListEntry>
   ): Promise<{ [key: string]: IEntry }> {
     const promises = [];
     const entries: { [key: string]: IEntry } = {};
@@ -447,7 +472,8 @@ export class ListModel extends VDomModel {
             enabled: pkg.enabled,
             status: pkg.status,
             latest_version: pkg.latest_version,
-            installed_version: pkg.installed_version
+            installed_version: pkg.installed_version,
+            isBlacklisted: blacklistMap.has(pkg.name)
           };
         })
       );
@@ -496,7 +522,9 @@ export class ListModel extends VDomModel {
    *
    * @returns {Promise<{ [key: string]: IEntry; }>} The search result as a map of entries.
    */
-  protected async performSearch(): Promise<{ [key: string]: IEntry }> {
+  protected async performSearch(
+    blacklistingMap: Map<string, IListEntry>
+  ): Promise<{ [key: string]: IEntry }> {
     if (this.query === null) {
       this._searchResult = [];
       this._totalEntries = 0;
@@ -510,7 +538,7 @@ export class ListModel extends VDomModel {
       this.page,
       this.pagination
     );
-    let searchMapPromise = this.translateSearchResult(search);
+    let searchMapPromise = this.translateSearchResult(search, blacklistingMap);
 
     let searchMap: { [key: string]: IEntry };
     try {
@@ -521,13 +549,24 @@ export class ListModel extends VDomModel {
       this.searchError = reason.toString();
     }
 
+    return searchMap;
+  }
+
+  protected async performGetBlacklist(): Promise<Map<string, IListEntry>> {
+    // Start the search without waiting for it:
+    let blacklist = this.lister.getBlackList();
+    let blacklistMapPromise = this.translateBlacklistResult(blacklist);
+
+    let blacklistMap: Map<string, IListEntry>;
     try {
-      this._totalEntries = (await search).total;
-    } catch (error) {
-      this._totalEntries = 0;
+      blacklistMap = await blacklistMapPromise;
+      this.blacklistError = null;
+    } catch (reason) {
+      blacklistMap = new Map();
+      this.blacklistError = reason.toString();
     }
 
-    return searchMap;
+    return blacklistMap;
   }
 
   /**
@@ -538,12 +577,14 @@ export class ListModel extends VDomModel {
    * @returns {Promise<{ [key: string]: IEntry; }>} A map of installed extensions.
    */
   protected async queryInstalled(
-    refreshInstalled: boolean
+    refreshInstalled: boolean,
+    blacklistMap: Map<string, IListEntry>
   ): Promise<{ [key: string]: IEntry }> {
     let installedMap;
     try {
       installedMap = await this.translateInstalled(
-        this.fetchInstalled(refreshInstalled)
+        this.fetchInstalled(refreshInstalled),
+        blacklistMap
       );
       this.installedError = null;
     } catch (reason) {
@@ -562,8 +603,13 @@ export class ListModel extends VDomModel {
    */
   protected async update(refreshInstalled = false) {
     // Start both queries before awaiting:
-    const searchMapPromise = this.performSearch();
-    const installedMapPromise = this.queryInstalled(refreshInstalled);
+    const blacklistingPromise = this.performGetBlacklist();
+    const blacklistingMap = await blacklistingPromise;
+    const searchMapPromise = this.performSearch(blacklistingMap);
+    const installedMapPromise = this.queryInstalled(
+      refreshInstalled,
+      blacklistingMap
+    );
 
     // Await results:
     const searchMap = await searchMapPromise;
@@ -664,6 +710,11 @@ export class ListModel extends VDomModel {
    */
   searchError: string | null = null;
 
+  /**
+   * Contains an error message if an error occurred when searching for lists.
+   */
+  blacklistError: string | null = null;
+
   /**
    * Contains an error message if an error occurred when querying the server extension.
    */
@@ -694,6 +745,8 @@ export class ListModel extends VDomModel {
    */
   protected searcher = new Searcher();
 
+  protected lister = new Lister();
+
   /**
    * The service manager to use for building.
    */

+ 13 - 0
packages/extensionmanager/src/widget.tsx

@@ -9,6 +9,8 @@ import {
   caretRightIcon,
   Collapse,
   InputGroup,
+  //  notTrustedIcon as blacklistIcon,
+  bugIcon as blacklistIcon,
   jupyterIcon,
   refreshIcon
 } from '@jupyterlab/ui-components';
@@ -157,6 +159,10 @@ function ListEntry(props: ListEntry.IProperties): React.ReactElement<any> {
     flagClasses.push(`jp-extensionmanager-entry-mod-whitelisted`);
     title = `${entry.name} (Developed by Project Jupyter)`;
   }
+  if (entry.isBlacklisted) {
+    flagClasses.push(`jp-extensionmanager-entry-mod-blacklisted`);
+    title = `${entry.name} is blacklisted`;
+  }
   return (
     <li
       className={`jp-extensionmanager-entry ${flagClasses.join(' ')}`}
@@ -174,6 +180,13 @@ function ListEntry(props: ListEntry.IProperties): React.ReactElement<any> {
           height="auto"
           width="1em"
         />
+        {entry.isBlacklisted === true && (
+          <blacklistIcon.react
+            className="jp-extensionmanager-entry-blacklisted"
+            top="1px"
+            kind="menuItem"
+          />
+        )}
       </div>
       <div className="jp-extensionmanager-entry-content">
         <div className="jp-extensionmanager-entry-description">

+ 5 - 0
packages/extensionmanager/style/base.css

@@ -164,6 +164,11 @@
   display: inline;
 }
 
+.jp-extensionmanager-entry.jp-extensionmanager-entry-mod-blacklisted
+  .jp-extensionmanager-entry-blacklisted {
+  display: inline;
+}
+
 /* Precedence order update/error/warning matters! */
 .jp-extensionmanager-entry.jp-extensionmanager-entry-update {
   border-left: solid 8px var(--jp-brand-color2);