Skip to content

Commit

Permalink
Implement import/export of environments
Browse files Browse the repository at this point in the history
  • Loading branch information
imolorhe committed Feb 17, 2024
1 parent b08e6cd commit 937a11e
Show file tree
Hide file tree
Showing 23 changed files with 363 additions and 23 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -67,10 +67,18 @@
<div class="environment-manager__list-actions">
<button
class="app-button full-width"
(click)="addSubEnvironmentChange.next()"
(click)="addSubEnvironmentChange.emit()"
>
<app-icon name="plus-circle"></app-icon>
{{ 'ADD_SUB_ENVIRONMENT_BUTTON' | translate }}
</button>
<button
class="app-button full-width"
(click)="importEnvironmentChange.emit()"
>
<app-icon name="log-in"></app-icon>
{{ 'IMPORT_BUTTON' | translate }}
</button>
</div>
</div>
<div class="environment-manager__editor-wrapper">
Expand All @@ -91,6 +99,13 @@
/>
</div>
<div class="environment-manager__editor-meta-actions">
<button
class="app-button"
(click)="exportEnvironmentChange.emit(selectedEnvironment)"
>
<app-icon name="file-down"></app-icon>
{{ 'EXPORT_TEXT' | translate }}
</button>
<button
class="app-button icon-button active-destructive"
(click)="onDeleteSubEnvironment()"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ export class EnvironmentManagerComponent implements OnInit, OnChanges {
@Output() addSubEnvironmentChange = new EventEmitter();
@Output() deleteSubEnvironmentChange = new EventEmitter();
@Output() repositionSubEnvironmentsChange = new EventEmitter();
@Output() importEnvironmentChange = new EventEmitter();
@Output() exportEnvironmentChange = new EventEmitter<EnvironmentState>();

@ViewChild('subEnvironmentTitle') subEnvironmentTitleEl?: ElementRef;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { Store } from '@ngrx/store';
import { SharedModule } from '../../modules/shared/shared.module';
import { MockModule } from 'ng-mocks';
import { MockModule, MockService } from 'ng-mocks';
import { mockStoreFactory } from '../../../../../testing';

import { FancyInputMarkerComponent } from './fancy-input-marker.component';
import { EnvironmentService } from '../../services';

describe('FancyInputMarkerComponent', () => {
let component: FancyInputMarkerComponent;
Expand All @@ -15,6 +16,10 @@ describe('FancyInputMarkerComponent', () => {
declarations: [FancyInputMarkerComponent],
imports: [MockModule(SharedModule)],
providers: [
{
provide: EnvironmentService,
useValue: MockService(EnvironmentService),
},
{
provide: Store,
useValue: mockStoreFactory(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import { Store } from '@ngrx/store';
import { mockStoreFactory } from '../../../../../testing';

import { XInputComponent } from './x-input.component';
import { EnvironmentService } from '../../services';
import { MockService } from 'ng-mocks';

describe('XInputComponent', () => {
let component: XInputComponent;
Expand All @@ -13,6 +15,10 @@ describe('XInputComponent', () => {
await TestBed.configureTestingModule({
declarations: [XInputComponent],
providers: [
{
provide: EnvironmentService,
useValue: MockService(EnvironmentService),
},
{
provide: Store,
useValue: mockStore,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -385,6 +385,8 @@
(repositionSubEnvironmentsChange)="
repositionSubEnvironments($event)
"
(importEnvironmentChange)="importEnvironmentData()"
(exportEnvironmentChange)="exportEnvironment($event)"
></app-environment-manager>
<app-plugin-manager
[showPluginManager]="(windowsMeta$ | async)?.showPluginManager"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,14 @@ describe('AltairComponent', () => {
provide: services.NotifyService,
useValue: mock(),
},
{
provide: services.FilesService,
useValue: mock(),
},
{
provide: services.EnvironmentService,
useValue: mock(),
},
{
provide: TranslateService,
useValue: mock({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ import {
QueryCollectionService,
ThemeRegistryService,
SharingService,
FilesService,
EnvironmentService,
NotifyService,
} from '../../services';

import isElectron from 'altair-graphql-core/build/utils/is_electron';
Expand All @@ -63,7 +66,7 @@ import { RootState } from 'altair-graphql-core/build/types/state/state.interface
import { AltairConfig } from 'altair-graphql-core/build/config';
import { WindowState } from 'altair-graphql-core/build/types/state/window.interfaces';
import { AltairPanel } from 'altair-graphql-core/build/plugin/panel';
import { externalLink, mapToKeyValueList } from '../../utils';
import { externalLink, mapToKeyValueList, openFiles } from '../../utils';
import { AccountState } from 'altair-graphql-core/build/types/state/account.interfaces';
import { catchUselessObservableError } from '../../utils/errors';
import { PerWindowState } from 'altair-graphql-core/build/types/state/per-window.interfaces';
Expand Down Expand Up @@ -128,6 +131,9 @@ export class AltairComponent {
private collectionService: QueryCollectionService,
private themeRegistry: ThemeRegistryService,
private sharingService: SharingService,
private filesService: FilesService,
private environmentService: EnvironmentService,
private notifyService: NotifyService,
private altairConfig: AltairConfig
) {
this.isWebApp = altairConfig.isWebApp;
Expand Down Expand Up @@ -654,6 +660,22 @@ export class AltairComponent {
})
);
}
async importEnvironmentData() {
const data = await openFiles({ accept: '.agx' });

return data.map((content) => {
this.environmentService.importEnvironmentData(JSON.parse(content));
});
}
async exportEnvironment(environment: EnvironmentState) {
if (
await this.notifyService.confirm(
'Reminder: Your environment data may contain sensitive information. Are you sure you want to export it?'
)
) {
this.environmentService.exportEnvironmentData(environment);
}
}

toggleCollections() {
this.showCollections = !this.showCollections;
Expand Down Expand Up @@ -854,20 +876,13 @@ export class AltairComponent {
}

async fileDropped(files: FileList) {
if (files && files.length) {
try {
// Handle window import
await this.windowService.handleImportedFile(files);
} catch (error) {
debug.log(error);
try {
// Handle collection import
await this.collectionService.handleImportedFile(files);
} catch (collectionError) {
debug.log(collectionError);
}
}
const file = files[0];
if (!file) {
debug.log('No file specified.');
return;
}

await this.filesService.handleImportedFile(file);
}

hideDonationAlert() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ export type TODO = any;

export type UnknownError<V = unknown> = V | Error | string;

export type WithRequired<T, K extends keyof T> = T & { [P in K]-?: T[P] };

export interface TrackByIdItem {
id: string;
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {
Eye,
EyeOff,
File,
FileDown,
FilePlus,
FlaskConical,
Folder,
Expand Down Expand Up @@ -90,6 +91,7 @@ export const icons = {
Eye,
EyeOff,
File,
FileDown,
FilePlus,
FlaskConical,
Folder,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import { Store } from '@ngrx/store';
import { Subscription } from 'rxjs';
import { mock } from '../../../../../testing';
import { RootState } from 'altair-graphql-core/build/types/state/state.interfaces';
import { NotifyService } from '../notify/notify.service';
import { MockService } from 'ng-mocks';

let mockStore: Store<RootState>;

Expand Down Expand Up @@ -45,6 +47,10 @@ describe('EnvironmentService', () => {
TestBed.configureTestingModule({
providers: [
EnvironmentService,
{
provide: NotifyService,
useValue: MockService(NotifyService),
},
{
provide: Store,
useFactory: () => mockStore,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,17 @@ import * as fromRoot from '../../store';
import * as fromEnvironments from '../../store/environments/environments.reducer';
import * as fromHeaders from '../../store/headers/headers.reducer';
import { IDictionary } from '../../interfaces/shared';
import { EnvironmentsState } from 'altair-graphql-core/build/types/state/environments.interfaces';
import * as environmentsActions from '../../store/environments/environments.action';
import {
EnvironmentState,
EnvironmentsState,
ExportEnvironmentState,
} from 'altair-graphql-core/build/types/state/environments.interfaces';
import { RootState } from 'altair-graphql-core/build/types/state/state.interfaces';
import { HeaderState } from 'altair-graphql-core/build/types/state/header.interfaces';
import { merge } from 'lodash-es';
import { downloadJson } from '../../utils';
import { NotifyService } from '../notify/notify.service';

// Unfortunately, Safari doesn't support lookbehind in regex: https://caniuse.com/js-regexp-lookbehind
// So have to go with an alternative approach using lookahead instead
Expand All @@ -30,7 +37,10 @@ interface HydrateEnvironmentOptions {
export class EnvironmentService {
environmentsState?: EnvironmentsState;

constructor(private store: Store<RootState>) {
constructor(
private notifyService: NotifyService,
private store: Store<RootState>
) {
this.store.subscribe({
next: (data) => {
this.environmentsState = data.environments;
Expand Down Expand Up @@ -163,4 +173,44 @@ export class EnvironmentService {

return hydratedHeaders;
}

importEnvironmentData(data: ExportEnvironmentState) {
if (!data.version || !data.type || data.type !== 'environment') {
throw new Error('File is not a valid Altair environment file.');
}

const id = uuid();
this.store.dispatch(
new environmentsActions.AddSubEnvironmentAction({
...data,
id,
})
);
this.notifyService.success(
`${data.title ?? 'New'} environment imported successfully`
);
}

exportEnvironmentData(environment: EnvironmentState) {
return downloadJson(
this.getExportEnvironmentData(environment),
`${environment.title}_exported`,
{
fileType: 'agx',
}
);
}

getExportEnvironmentData(
environment: EnvironmentState
): ExportEnvironmentState {
const exportCollectionData: ExportEnvironmentState = {
version: 1,
type: 'environment',
id: environment.id,
title: environment.title,
variables: JSON.parse(environment.variablesJson),
};
return exportCollectionData;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { TestBed } from '@angular/core/testing';

import { FilesService } from './files.service';
import { MockService } from 'ng-mocks';
import { WindowService } from '../window.service';
import { NotifyService } from '../notify/notify.service';
import { EnvironmentService } from '../environment/environment.service';
import { GqlService } from '../gql/gql.service';
import { QueryCollectionService } from '../query-collection/query-collection.service';

describe('FilesService', () => {
let service: FilesService;

beforeEach(() => {
TestBed.configureTestingModule({
providers: [
{
provide: WindowService,
useValue: MockService(WindowService),
},
{
provide: GqlService,
useValue: MockService(GqlService),
},
{
provide: NotifyService,
useValue: MockService(NotifyService),
},
{
provide: EnvironmentService,
useValue: MockService(EnvironmentService),
},
{
provide: QueryCollectionService,
useValue: MockService(QueryCollectionService),
},
],
});
service = TestBed.inject(FilesService);
});

it('should be created', () => {
expect(service).toBeTruthy();
});
});
Loading

0 comments on commit 937a11e

Please sign in to comment.