.should('contain', 'someKey: someValue')
.and('not.contain', 'anotherKey: anotherValue');
// Check that the file listing show both read & write operations
- cy.get('[data-cy=collection-files-panel]').within(() => {
+ cy.waitForDom().get('[data-cy=collection-files-panel]').within(() => {
cy.get('[data-cy=collection-files-right-panel]', { timeout: 5000 })
.should('contain', fileName);
if (isWritable) {
cy.get('[data-cy=form-submit-btn]').click();
- cy.get('.layout-pane-primary', { timeout: 12000 }).contains('Projects').click();
+ cy.waitForDom().get('.layout-pane-primary', { timeout: 12000 }).contains('Projects').click();
cy.get('main').contains(`Files extracted from: ${this.collection.name}`).should('exist');
});
// From https://github.com/cypress-io/cypress/issues/7306#issuecomment-1076451070=
// This command requires the async package (https://www.npmjs.com/package/async)
Cypress.Commands.add('waitForDom', () => {
- cy.window().then(win => {
+ cy.window().then({
+ // Don't timeout before waitForDom finishes
+ timeout: 10000
+ }, win => {
let timeElapsed = 0;
cy.log("Waiting for DOM mutations to complete");
if (currentTime - lastTime > idleTimeoutMs) {
//logout
sh.reset();
- sh.sessionClosed();
+ sh.sessionClosed("Session timed out after " + timeout + " seconds.");
document.body.onmousemove = undefined;
document.body.onkeydown = undefined;
} else {
sh.keysPressed(token + "\n");
sh.vt100('(sent authentication token)\n');
token = null;
- updateIdleTimer();
- document.body.onmousemove = updateIdleTimer;
- document.body.onkeydown = updateIdleTimer;
- setTimeout(checkIdleTimer, 1000);
+ if (timeout > 0) {
+ updateIdleTimer();
+ document.body.onmousemove = updateIdleTimer;
+ document.body.onkeydown = updateIdleTimer;
+ setTimeout(checkIdleTimer, 1000);
+ }
} else {
setTimeout(trySendToken, 200);
}
};
extend(ShellInABox, VT100);
-ShellInABox.prototype.sessionClosed = function() {
+ShellInABox.prototype.sessionClosed = function(msg) {
try {
this.connected = false;
if (this.session) {
if (this.cursorX > 0) {
this.vt100('\r\n');
}
- this.vt100('Session closed.');
+ this.vt100(msg || 'Session closed.');
this.currentRequest.abort();
}
// Revealing the "reconnect" button is commented out until we hook
<Chip
label={this.renderChipValue(item)}
key={index}
- onDelete={() => onDelete ? onDelete(item, index) : undefined} />
+ onDelete={onDelete && !this.props.disabled ? (() => onDelete(item, index)) : undefined} />
);
}
GENERAL = 'nil',
OUTPUT = 'output',
LOG = 'log',
+ INTERMEDIATE = 'intermediate',
}
ownerUuid: 'zzzzz-tpzed-someusersowneruuid',
prefs: {}, isAdmin: false, isActive: true
},
- expect: 'Some User <<someuser@example.com>>'
+ expect: 'Some User <someuser@example.com>'
},
{
caseName: 'Missing first name',
: "";
};
-export const getUserDisplayName = (user: User, withEmail = false) => {
+export const getUserDisplayName = (user: User, withEmail = false, withUuid = false) => {
const displayName = getUserFullname(user) || user.email || user.username || user.uuid;
+ let parts: string[] = [displayName];
if (withEmail && user.email && displayName !== user.email) {
- return `${displayName} <<${user.email}>>`;
+ parts.push(`<${user.email}>`);
}
- return displayName;
+ if (withUuid) {
+ parts.push(`(${user.uuid})`);
+ }
+ return parts.join(' ');
};
export interface UserResource extends Resource, User {
deselectNode(ObjectTypeFilter.PROCESS),
deselectNode(CollectionTypeFilter.GENERAL_COLLECTION),
deselectNode(CollectionTypeFilter.LOG_COLLECTION),
+ deselectNode(CollectionTypeFilter.INTERMEDIATE_COLLECTION),
)();
const serializedFilters = serializeResourceTypeFilters(filters);
.toEqual(`["uuid","is_a",["${ResourceKind.PROJECT}","${ResourceKind.COLLECTION}"]],["collections.properties.type","in",["output"]]`);
});
+ it("should serialize intermediate collections and projects", () => {
+ const filters = pipe(
+ () => getInitialResourceTypeFilters(),
+ deselectNode(ObjectTypeFilter.PROCESS),
+ deselectNode(CollectionTypeFilter.GENERAL_COLLECTION),
+ deselectNode(CollectionTypeFilter.LOG_COLLECTION),
+ deselectNode(CollectionTypeFilter.OUTPUT_COLLECTION),
+ )();
+
+ const serializedFilters = serializeResourceTypeFilters(filters);
+ expect(serializedFilters)
+ .toEqual(`["uuid","is_a",["${ResourceKind.PROJECT}","${ResourceKind.COLLECTION}"]],["collections.properties.type","in",["intermediate"]]`);
+ });
+
it("should serialize general and log collections", () => {
const filters = pipe(
() => getInitialResourceTypeFilters(),
GENERAL_COLLECTION = 'General',
OUTPUT_COLLECTION = 'Output',
LOG_COLLECTION = 'Log',
+ INTERMEDIATE_COLLECTION = 'Intermediate',
}
export enum ProcessTypeFilter {
initFilter(ObjectTypeFilter.COLLECTION),
initFilter(CollectionTypeFilter.GENERAL_COLLECTION, ObjectTypeFilter.COLLECTION),
initFilter(CollectionTypeFilter.OUTPUT_COLLECTION, ObjectTypeFilter.COLLECTION),
+ initFilter(CollectionTypeFilter.INTERMEDIATE_COLLECTION, ObjectTypeFilter.COLLECTION),
initFilter(CollectionTypeFilter.LOG_COLLECTION, ObjectTypeFilter.COLLECTION),
),
);
initFilter(ObjectTypeFilter.COLLECTION),
initFilter(CollectionTypeFilter.GENERAL_COLLECTION, ObjectTypeFilter.COLLECTION),
initFilter(CollectionTypeFilter.OUTPUT_COLLECTION, ObjectTypeFilter.COLLECTION),
+ initFilter(CollectionTypeFilter.INTERMEDIATE_COLLECTION, ObjectTypeFilter.COLLECTION),
initFilter(CollectionTypeFilter.LOG_COLLECTION, ObjectTypeFilter.COLLECTION),
);
return CollectionType.OUTPUT;
case CollectionTypeFilter.LOG_COLLECTION:
return CollectionType.LOG;
+ case CollectionTypeFilter.INTERMEDIATE_COLLECTION:
+ return CollectionType.INTERMEDIATE;
+ default:
+ return CollectionType.GENERAL;
}
};
.map(objectTypeToResourceKind);
};
-export const buildProcessStatusFilters = ( fb: FilterBuilder, activeStatusFilter: string, resourcePrefix?: string ): FilterBuilder => {
+export const buildProcessStatusFilters = (fb: FilterBuilder, activeStatusFilter: string, resourcePrefix?: string): FilterBuilder => {
switch (activeStatusFilter) {
case ProcessStatusFilter.ONHOLD: {
fb.addDistinct('state', ContainerRequestState.FINAL, resourcePrefix);
},
schedulingParameters: { max_run_time: undefined },
state: "Committed",
+ useExisting: false
});
// and
properties: {
workflowUuid: selectedWorkflow.uuid,
workflowName: selectedWorkflow.name
- }
+ },
+ useExisting: false
};
const newProcess = await services.containerRequestService.create(newProcessData);
dispatch(navigateTo(newProcess.uuid));
import { deleteResources, updateResources } from 'store/resources/resources-actions';
import { Participant } from "views-components/sharing-dialog/participant-select";
import { initialize, reset } from "redux-form";
-import { getUserDisplayName } from "models/user";
+import { getUserDisplayName, UserResource } from "models/user";
export const virtualMachinesActions = unionize({
SET_REQUESTED_DATE: ofType<string>(),
export const VIRTUAL_MACHINE_ADD_LOGIN_VM_FIELD = 'vmUuid';
export const VIRTUAL_MACHINE_ADD_LOGIN_USER_FIELD = 'user';
export const VIRTUAL_MACHINE_ADD_LOGIN_GROUPS_FIELD = 'groups';
+export const VIRTUAL_MACHINE_ADD_LOGIN_EXCLUDE = 'excludedPerticipants';
export const openUserVirtualMachines = () =>
async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
export const openAddVirtualMachineLoginDialog = (vmUuid: string) =>
async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
- dispatch(initialize(VIRTUAL_MACHINE_ADD_LOGIN_FORM, {[VIRTUAL_MACHINE_ADD_LOGIN_VM_FIELD]: vmUuid, [VIRTUAL_MACHINE_ADD_LOGIN_GROUPS_FIELD]: []}));
- dispatch(dialogActions.OPEN_DIALOG( {id: VIRTUAL_MACHINE_ADD_LOGIN_DIALOG, data: {}} ));
+ // Get login permissions of vm
+ const virtualMachines = await services.virtualMachineService.list();
+ dispatch(updateResources(virtualMachines.items));
+ const logins = await services.permissionService.list({
+ filters: new FilterBuilder()
+ .addIn('head_uuid', virtualMachines.items.map(item => item.uuid))
+ .addEqual('name', PermissionLevel.CAN_LOGIN)
+ .getFilters()
+ });
+ dispatch(updateResources(logins.items));
+
+ dispatch(initialize(VIRTUAL_MACHINE_ADD_LOGIN_FORM, {
+ [VIRTUAL_MACHINE_ADD_LOGIN_VM_FIELD]: vmUuid,
+ [VIRTUAL_MACHINE_ADD_LOGIN_GROUPS_FIELD]: [],
+ }));
+ dispatch(dialogActions.OPEN_DIALOG( {id: VIRTUAL_MACHINE_ADD_LOGIN_DIALOG, data: {excludedParticipants: logins.items.map(it => it.tailUuid)}} ));
}
export const openEditVirtualMachineLoginDialog = (permissionUuid: string) =>
const user = await services.userService.get(login.tailUuid);
dispatch(initialize(VIRTUAL_MACHINE_ADD_LOGIN_FORM, {
[VIRTUAL_MACHINE_UPDATE_LOGIN_UUID_FIELD]: permissionUuid,
- [VIRTUAL_MACHINE_ADD_LOGIN_USER_FIELD]: {name: getUserDisplayName(user, true), uuid: login.tailUuid},
+ [VIRTUAL_MACHINE_ADD_LOGIN_USER_FIELD]: {name: getUserDisplayName(user, true, true), uuid: login.tailUuid},
[VIRTUAL_MACHINE_ADD_LOGIN_GROUPS_FIELD]: login.properties.groups,
}));
dispatch(dialogActions.OPEN_DIALOG( {id: VIRTUAL_MACHINE_ADD_LOGIN_DIALOG, data: {updating: true}} ));
export const addUpdateVirtualMachineLogin = ({uuid, vmUuid, user, groups}: AddLoginFormData) =>
async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
+ let userResource: UserResource | undefined = undefined;
try {
// Get user
- const userResource = await services.userService.get(user.uuid);
-
+ userResource = await services.userService.get(user.uuid, false);
+ } catch (e) {
+ dispatch(snackbarActions.OPEN_SNACKBAR({ message: "Failed to get user details.", hideDuration: 2000, kind: SnackbarKind.ERROR }));
+ return;
+ }
+ try {
if (uuid) {
const permission = await services.permissionService.update(uuid, {
tailUuid: userResource.uuid,
interface ParticipantSelectProps {
items: Participant[];
+ excludedParticipants?: string[];
label?: string;
autofocus?: boolean;
onlyPeople?: boolean;
+ onlyActive?: boolean;
+ disabled?: boolean;
onBlur?: (event: React.FocusEvent<HTMLInputElement>) => void;
onFocus?: (event: React.FocusEvent<HTMLInputElement>) => void;
const getDisplayName = (item: GroupResource | UserResource) => {
switch (item.kind) {
case ResourceKind.USER:
- return getUserDisplayName(item, true);
+ return getUserDisplayName(item, true, true);
case ResourceKind.GROUP:
- return item.name;
+ return item.name + `(${`(${(item as Resource).uuid})`})`;
default:
return (item as Resource).uuid;
}
onChange={this.handleChange}
onCreate={this.handleCreate}
onSelect={this.handleSelect}
- onDelete={this.handleDelete}
+ onDelete={this.props.onDelete && !this.props.disabled ? this.handleDelete : undefined}
onFocus={this.props.onFocus}
onBlur={this.props.onBlur}
renderChipValue={this.renderChipValue}
- renderSuggestion={this.renderSuggestion} />
+ renderSuggestion={this.renderSuggestion}
+ disabled={this.props.disabled}/>
);
}
const filterUsers = new FilterBuilder()
.addILike('any', value)
+ .addEqual('is_active', this.props.onlyActive || undefined)
+ .addNotIn('uuid', this.props.excludedParticipants)
.getFilters();
const userItems: ListResults<any> = await userService.list({ filters: filterUsers, limit, count: "none" });
const filterGroups = new FilterBuilder()
.addNotIn('group_class', [GroupClass.PROJECT, GroupClass.FILTER])
+ .addNotIn('uuid', this.props.excludedParticipants)
.addILike('name', value)
.getFilters();
import React from 'react';
import { compose } from "redux";
-import { reduxForm, InjectedFormProps, WrappedFieldProps, Field } from 'redux-form';
+import { reduxForm, InjectedFormProps, Field, GenericField } from 'redux-form';
import { withDialog, WithDialogProps } from "store/dialog/with-dialog";
import { FormDialog } from 'components/form-dialog/form-dialog';
import { VIRTUAL_MACHINE_ADD_LOGIN_DIALOG, VIRTUAL_MACHINE_ADD_LOGIN_FORM, addUpdateVirtualMachineLogin, AddLoginFormData, VIRTUAL_MACHINE_ADD_LOGIN_USER_FIELD, VIRTUAL_MACHINE_ADD_LOGIN_GROUPS_FIELD } from 'store/virtual-machines/virtual-machines-actions';
type CreateGroupDialogComponentProps = WithDialogProps<{updating: boolean}> & InjectedFormProps<AddLoginFormData>;
-const AddLoginFormFields = () =>
- <>
- <UserField />
+const AddLoginFormFields = (props) => {
+ return <>
+ <ParticipantField
+ name={VIRTUAL_MACHINE_ADD_LOGIN_USER_FIELD}
+ component={props.data.updating ? ReadOnlyUserSelect : UserSelect}
+ excludedParticipants={props.data.excludedParticipants}
+ />
<GroupArrayInput
name={VIRTUAL_MACHINE_ADD_LOGIN_GROUPS_FIELD}
input={{id:"Add groups to VM login (eg: docker, sudo)", disabled:false}}
required={false}
/>
</>;
+}
+
+interface UserFieldProps {
+ excludedParticipants: string[];
+}
-const UserField = () =>
- <Field
- name={VIRTUAL_MACHINE_ADD_LOGIN_USER_FIELD}
- component={UserSelect}
- />;
+const ParticipantField = Field as new () => GenericField<UserFieldProps>;
-const UserSelect = ({ input, meta }: WrappedFieldProps) =>
+const UserSelect = (props) =>
<ParticipantSelect
onlyPeople
+ onlyActive
label='Search for user to grant login permission'
- items={input.value ? [input.value] : []}
- onSelect={input.onChange}
- onDelete={() => (input.onChange(''))} />;
+ items={props.input.value ? [props.input.value] : []}
+ excludedParticipants={props.excludedParticipants}
+ onSelect={props.input.onChange}
+ onDelete={() => (props.input.onChange(''))} />;
+
+const ReadOnlyUserSelect = (props) =>
+ <ParticipantSelect
+ onlyPeople
+ onlyActive
+ label='User'
+ items={props.input.value ? [props.input.value] : []}
+ disabled={true} />;