fix: check invalid provider options before project actions (#390)
* fix: optomise pdf loading * fix: check for invalid connection provider options for project actions * docs: add test case to test runbook * style: add missing semi colons
This commit is contained in:
Родитель
2abb0cb062
Коммит
212647d432
|
@ -1,5 +1,33 @@
|
|||
# Test Runbook
|
||||
|
||||
## Fix: check invalid connection provider options before project actions
|
||||
|
||||
> ### Feature description ###
|
||||
- check connection provider options are valid before creating a project
|
||||
- check connection provider options are valid before opening a recent project
|
||||
|
||||
> ### Use Case ###
|
||||
|
||||
**As** a user
|
||||
**I want** a notification when I try to open or create a project with invalid provider options
|
||||
**So** I know how to fix invalid provider options issue
|
||||
|
||||
> ### Acceptance criteria ###
|
||||
|
||||
#### Scenario One ####
|
||||
|
||||
**Given** I've created a connection with invalid provider options (e.g. invalid SAS token for Azure provider).
|
||||
**When** I try to create a new project with that connection.
|
||||
**Then** a notification will be displayed telling me my connection is invalid.
|
||||
|
||||
#### Scenario Two ####
|
||||
|
||||
**Given** I've created a connection with invalid provider options (e.g. invalid SAS token for Azure provider).
|
||||
**When** I try to open a recent project that now has an invalid connection provider options (e.g. the Azure container was deleted)
|
||||
**Then** a notification will be displayed telling me my connection is invalid.
|
||||
|
||||
___
|
||||
|
||||
## Feat: support distributable releasing
|
||||
|
||||
> ### Feature description ###
|
||||
|
|
|
@ -252,6 +252,7 @@ export const english: IAppStrings = {
|
|||
instructions: "Please select a connection to edit",
|
||||
new: "New Connection",
|
||||
save: "Save Connection",
|
||||
genericInvalid: "\"${project.sourceConnection.name}\" is an invalid connection. Please check it in the Connections page",
|
||||
messages: {
|
||||
saveSuccess: "Successfully saved ${connection.name}",
|
||||
deleteSuccess: "Successfully deleted ${connection.name}",
|
||||
|
@ -282,6 +283,7 @@ export const english: IAppStrings = {
|
|||
title: "Create Container",
|
||||
description: "Creates the blob container if it does not already exist",
|
||||
},
|
||||
invalidSASMessage: "\"${project.sourceConnection.name}\" has no storage account. Please check it's SAS token in the Connections page",
|
||||
},
|
||||
bing: {
|
||||
title: "Bing Image Search",
|
||||
|
@ -298,9 +300,11 @@ export const english: IAppStrings = {
|
|||
},
|
||||
local: {
|
||||
title: "Local file system",
|
||||
folderPath: "Browse",
|
||||
folderPath: "Folder",
|
||||
browse: "Browse",
|
||||
selectFolder: "Select folder",
|
||||
chooseFolder: "Choose folder",
|
||||
invalidFolderMessage: "\"${project.sourceConnection.name}\" has an invalid folder. Please check it's selected folder in the Connections page",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
|
@ -250,6 +250,7 @@ export const spanish: IAppStrings = {
|
|||
title: "Conexiones",
|
||||
new: "Nueva conexión",
|
||||
save: "Guardar Conexión",
|
||||
genericInvalid: "\"${project.sourceConnection.name}\" es una conexión no válida. Por favor verifíquelo en la página Conexiones",
|
||||
details: "Detalles de Conexión",
|
||||
settings: "Configuración de Conexión",
|
||||
instructions: "Por favor seleccione una conexión para editar",
|
||||
|
@ -284,6 +285,7 @@ export const spanish: IAppStrings = {
|
|||
title: "Crear contenedor",
|
||||
description: "Crea el contenedor de blobs si aún no existe",
|
||||
},
|
||||
invalidSASMessage: "\"${project.sourceConnection.name}\" no tiene cuenta de almacenamiento. Verifique su token SAS en la página de Conexiones",
|
||||
},
|
||||
bing: {
|
||||
title: "Búsqueda de Imágenes Bing",
|
||||
|
@ -301,8 +303,10 @@ export const spanish: IAppStrings = {
|
|||
local: {
|
||||
title: "Sistema de Archivos Local",
|
||||
folderPath: "Ruta de la carpeta",
|
||||
browse: "vistazo",
|
||||
selectFolder: "Seleccionar la carpeta",
|
||||
chooseFolder: "Elijir la carpeta",
|
||||
invalidFolderMessage: "\"${project.sourceConnection.name}\" tiene una carpeta no válida Por favor verifique su carpeta seleccionada en la página de Conexiones",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
|
@ -311,6 +311,7 @@ export default class MockFactory {
|
|||
readBinary: jest.fn(),
|
||||
deleteFile: jest.fn(),
|
||||
writeText: jest.fn(),
|
||||
isValidProjectConnection: jest.fn(),
|
||||
writeBinary: jest.fn(),
|
||||
listFiles: jest.fn(() => Promise.resolve(MockFactory.createFileList())),
|
||||
listContainers: jest.fn(),
|
||||
|
|
|
@ -251,6 +251,7 @@ export interface IAppStrings {
|
|||
instructions: string;
|
||||
new: string;
|
||||
save: string;
|
||||
genericInvalid: string;
|
||||
messages: {
|
||||
saveSuccess: string;
|
||||
deleteSuccess: string;
|
||||
|
@ -277,7 +278,8 @@ export interface IAppStrings {
|
|||
createContainer: {
|
||||
title: string,
|
||||
description: string,
|
||||
}
|
||||
},
|
||||
invalidSASMessage: string;
|
||||
},
|
||||
bing: {
|
||||
title: string;
|
||||
|
@ -295,8 +297,10 @@ export interface IAppStrings {
|
|||
local: {
|
||||
title: string;
|
||||
folderPath: string;
|
||||
browse: string;
|
||||
selectFolder: string;
|
||||
chooseFolder: string;
|
||||
invalidFolderMessage: string;
|
||||
},
|
||||
}
|
||||
};
|
||||
|
|
|
@ -49,6 +49,20 @@ export default class LocalFileSystem implements IStorageProvider {
|
|||
});
|
||||
}
|
||||
|
||||
public isValidProjectConnection(folderPath) {
|
||||
return new Promise<boolean>((resolve, reject) => {
|
||||
try {
|
||||
if (fs.existsSync(folderPath)) {
|
||||
resolve(true);
|
||||
} else {
|
||||
resolve(false);
|
||||
}
|
||||
} catch (err) {
|
||||
reject(err);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public getFileType(filePath: string): Promise<Buffer> {
|
||||
return FileType.fromFile(filePath);
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@ import { IAsset, AssetType, StorageType, AssetState, AppError } from "../../mode
|
|||
import { AssetService } from "../../services/assetService";
|
||||
import {
|
||||
TokenCredential, AnonymousCredential, ContainerURL,
|
||||
StorageURL, Credential, Aborter, BlockBlobURL,
|
||||
StorageURL, Credential, Aborter, BlockBlobURL, ServiceURL
|
||||
} from "@azure/storage-blob";
|
||||
import { constants } from "../../common/constants";
|
||||
import { ErrorCode } from "../../models/applicationState";
|
||||
|
@ -61,6 +61,15 @@ export class AzureBlobStorage implements IStorageProvider {
|
|||
}
|
||||
}
|
||||
|
||||
public async isValidProjectConnection() {
|
||||
try {
|
||||
await new ServiceURL(this.options.sas, StorageURL.newPipeline(this.getCredential())).getAccountInfo(Aborter.none);
|
||||
return (true);
|
||||
} catch {
|
||||
return (false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads Buffer from specified blob
|
||||
* @param blobName - Name of blob in container
|
||||
|
|
|
@ -49,6 +49,10 @@ export class LocalFileSystemProxy implements IStorageProvider, IAssetProvider {
|
|||
return IpcRendererProxy.send(`${PROXY_NAME}:readText`, [filePath]);
|
||||
}
|
||||
|
||||
public isValidProjectConnection(): Promise<boolean> {
|
||||
return IpcRendererProxy.send(`${PROXY_NAME}:isValidProjectConnection`, [this.options.folderPath]);
|
||||
}
|
||||
|
||||
public getFileType(fileName: string): Promise<any> {
|
||||
const filePath = [this.options.folderPath, fileName].join("/");
|
||||
return IpcRendererProxy.send(`${PROXY_NAME}:getFileType`, [filePath]);
|
||||
|
|
|
@ -33,6 +33,9 @@ class TestStorageProvider implements IStorageProvider {
|
|||
public readBinary(filePath: string): Promise<Buffer> {
|
||||
throw new Error("Method not implemented.");
|
||||
}
|
||||
public isValidProjectConnection(filePath: string): Promise<boolean> {
|
||||
throw new Error("Method not implemented.");
|
||||
}
|
||||
public deleteFile(filePath: string): Promise<void> {
|
||||
throw new Error("Method not implemented.");
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@ import getHostProcess, { HostProcessType } from "../../common/hostProcess";
|
|||
* @member deleteFile - Delete file from path
|
||||
* @member writeText - Write text to file at path
|
||||
* @member writeBinary - Write buffer to file at path
|
||||
* @member isValidProjectConnection
|
||||
* @member listFiles - List files in container within storage provider
|
||||
* @member listContainers - List containers in storage provider
|
||||
* @member createContainer - Create container within storage provider
|
||||
|
@ -31,6 +32,8 @@ export interface IStorageProvider extends IAssetProvider {
|
|||
writeText(filePath: string, contents: string): Promise<void>;
|
||||
writeBinary(filePath: string, contents: Buffer): Promise<void>;
|
||||
|
||||
isValidProjectConnection(filepath?): Promise<boolean>;
|
||||
|
||||
listFiles(folderPath?: string, ext?: string): Promise<string[]>;
|
||||
listContainers(folderPath?: string): Promise<string[]>;
|
||||
|
||||
|
|
|
@ -58,7 +58,7 @@ export default class LocalFolderPicker extends React.Component<ILocalFolderPicke
|
|||
<PrimaryButton
|
||||
className="keep-button-80px"
|
||||
theme={getPrimaryGreenTheme()}
|
||||
text={strings.connections.providers.local.folderPath}
|
||||
text={strings.connections.providers.local.browse}
|
||||
autoFocus={true}
|
||||
onClick={this.selectLocalFolder}
|
||||
/>
|
||||
|
|
|
@ -26,6 +26,7 @@ import { StorageProviderFactory } from "../../../../providers/storage/storagePro
|
|||
import { decryptProject } from "../../../../common/utils";
|
||||
import { toast } from "react-toastify";
|
||||
import { isElectron } from "../../../../common/hostProcess";
|
||||
import ProjectService from "../../../../services/projectService";
|
||||
|
||||
export interface IHomePageProps extends RouteComponentProps, React.Props<HomePage> {
|
||||
recentProjects: IProject[];
|
||||
|
@ -174,6 +175,10 @@ export default class HomePage extends React.Component<IHomePageProps, IHomePageS
|
|||
try {
|
||||
let projectStr: string;
|
||||
try {
|
||||
const projectService = new ProjectService();
|
||||
if (!(await projectService.isValidProjectConnection(project))) {
|
||||
return;
|
||||
}
|
||||
projectStr = await storageProvider.readText(
|
||||
`${decryptedProject.name}${constants.projectFileExtension}`);
|
||||
} catch (err) {
|
||||
|
|
|
@ -177,6 +177,11 @@ export default class ProjectSettingsPage extends React.Component<IProjectSetting
|
|||
private onFormSubmit = async (project: IProject) => {
|
||||
const isNew = !(!!project.id);
|
||||
|
||||
const projectService = new ProjectService();
|
||||
if (!(await projectService.isValidProjectConnection(project))) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (await this.isValidProjectName(project, isNew)) {
|
||||
toast.error(interpolate(strings.projectSettings.messages.projectExisted, { project }));
|
||||
return;
|
||||
|
|
|
@ -159,6 +159,21 @@ export default class ProjectService implements IProjectService {
|
|||
return false;
|
||||
}
|
||||
|
||||
public async isValidProjectConnection(project: IProject): Promise<boolean> {
|
||||
const storageProvider = StorageProviderFactory.createFromConnection(project.sourceConnection);
|
||||
const isValid = await storageProvider.isValidProjectConnection();
|
||||
if (!isValid) {
|
||||
if (project.sourceConnection.providerType === "localFileSystemProxy") {
|
||||
await toast.error(interpolate(strings.connections.providers.local.invalidFolderMessage, {project}));
|
||||
} else if (project.sourceConnection.providerType === "azureBlobStorage") {
|
||||
await toast.error(interpolate(strings.connections.providers.azureBlob.invalidSASMessage, {project}));
|
||||
} else {
|
||||
await toast.error(interpolate(strings.connections.genericInvalid, { project }));
|
||||
}
|
||||
}
|
||||
return isValid;
|
||||
};
|
||||
|
||||
public async updateProjectTagsFromFiles(project: IProject, asset?: string): Promise<IProject> {
|
||||
const updatedProject = Object.assign({}, project);
|
||||
updatedProject.tags = [];
|
||||
|
|
Загрузка…
Ссылка в новой задаче