michael-s-molina commented on code in PR #35077:
URL: https://github.com/apache/superset/pull/35077#discussion_r2336502310
##########
superset-frontend/src/core/sqlLab.ts:
##########
@@ -66,34 +213,168 @@ const predicate = (actionType: string):
AnyListenerPredicate<RootState> => {
};
export const onDidQueryRun: typeof sqlLabType.onDidQueryRun = (
- listener: (editor: sqlLabType.Editor) => void,
+ listener: (editor: sqlLabType.QueryRequestContext) => void,
+ thisArgs?: any,
+): Disposable =>
+ createActionListener(
+ predicate(START_QUERY),
+ listener,
+ (action: ReturnType<typeof startQuery>) => {
+ const { query } = action;
+ const {
+ id,
+ dbId,
+ catalog,
+ schema,
+ sql,
+ startDttm,
+ ctas_method: ctasMethod,
+ runAsync,
+ tempTable,
+ templateParams,
+ } = query;
+ const editor = new Editor(sql, dbId, catalog, schema);
+ const panels: Panel[] = []; // TODO: Populate panels
+ const tab = new Tab(query.sqlEditorId, query.tab, editor, panels);
+ return new QueryRequestContext(id, editor, tab, runAsync, startDttm, {
+ ctasMethod,
+ tempTable,
+ templateParams,
+ });
+ },
+ thisArgs,
+ );
+
+export const onDidQuerySuccess: typeof sqlLabType.onDidQuerySuccess = (
+ listener: (query: sqlLabType.QueryResultContext) => void,
thisArgs?: any,
): Disposable =>
createActionListener(
predicate(QUERY_SUCCESS),
listener,
(action: ReturnType<typeof querySuccess>) => {
+ const { query, results } = action;
+ const {
+ id,
+ dbId,
+ catalog,
+ schema,
+ sql,
+ startDttm,
+ ctas_method: ctasMethod,
+ runAsync,
+ templateParams,
+ } = query;
+ const {
+ query_id: queryId,
+ columns,
+ data,
+ query: { endDttm, executedSql, tempTable, limit, limitingFactor },
+ } = results;
+ const editor = new Editor(sql, dbId, catalog, schema);
+ const panels: Panel[] = []; // TODO: Populate panels
+ const tab = new Tab(query.sqlEditorId, query.tab, editor, panels);
+ return new QueryResultContext(
+ id,
+ queryId,
+ executedSql ?? sql,
+ columns,
+ data,
+ editor,
+ tab,
+ runAsync,
+ startDttm,
+ endDttm,
+ {
+ ctasMethod,
+ tempTable,
+ templateParams,
+ limit,
+ limitingFactor,
+ },
+ );
+ },
+ thisArgs,
+ );
+
+export const onDidQueryStop: typeof sqlLabType.onDidQueryStop = (
+ listener: (query: sqlLabType.QueryRequestContext) => void,
+ thisArgs?: any,
+): Disposable =>
+ createActionListener(
+ predicate(STOP_QUERY),
+ listener,
+ (action: ReturnType<typeof stopQuery>) => {
const { query } = action;
- const { dbId, catalog, schema, sql } = query;
- return new Editor(sql, dbId, catalog, schema);
+ const {
+ id,
+ dbId,
+ catalog,
+ schema,
+ sql,
+ startDttm,
+ ctas_method: ctasMethod,
+ runAsync,
+ tempTable,
+ templateParams,
+ } = query;
+ const editor = new Editor(sql, dbId, catalog, schema);
+ const panels: Panel[] = []; // TODO: Populate panels
+ const tab = new Tab(query.sqlEditorId, query.tab, editor, panels);
+ return new QueryRequestContext(id, editor, tab, runAsync, startDttm, {
+ ctasMethod,
+ tempTable,
+ templateParams,
+ });
},
thisArgs,
);
export const onDidQueryFail: typeof sqlLabType.onDidQueryFail = (
- listener: (e: string) => void,
+ listener: (query: sqlLabType.QueryErrorResultContext) => void,
thisArgs?: any,
): Disposable =>
createActionListener(
- predicate(QUERY_FAILED),
+ action => action.type === QUERY_FAILED,
Review Comment:
Why not use `predicate` which also checks for the active editor?
##########
superset-frontend/packages/superset-core/src/api/core.ts:
##########
@@ -76,6 +76,35 @@ export declare interface Database {
schemas: Schema[];
}
+// Keep in sync with superset/errors.py
+export type ErrorLevel = 'info' | 'warning' | 'error';
+
+/**
+ * Superset error object structure.
+ * Contains details about an error that occurred within Superset.
+ */
+export type SupersetError<ExtraType = Record<string, any> | null> = {
+ /**
+ * Error types, see enum of SupersetErrorType in superset/errors.py
+ */
+ error_type: string;
+
+ /**
+ * Extra properties based on the error types
+ */
+ extra: ExtraType;
Review Comment:
```suggestion
extra?: Record<string, any>;
```
##########
superset-frontend/packages/superset-core/src/api/core.ts:
##########
@@ -76,6 +76,35 @@ export declare interface Database {
schemas: Schema[];
}
+// Keep in sync with superset/errors.py
+export type ErrorLevel = 'info' | 'warning' | 'error';
Review Comment:
@villebro An example of why a fully typed REST API client for Superset is
necessary. Ideally, we would have a common definition like Thrift and export
both TS and Python classes.
##########
superset-frontend/packages/superset-core/src/api/sqlLab.ts:
##########
@@ -111,6 +111,171 @@ export interface Tab {
panels: Panel[];
}
+/**
+ * Generic data types, see enum of the same name in superset/utils/core.py.
+ */
+enum GenericDataType {
+ Numeric = 0,
+ String = 1,
+ Temporal = 2,
+ Boolean = 3,
+}
+
+/**
+ * Column metadata returned in query results.
+ */
+export type QueryColumn = {
+ /**
+ * Label of the column
+ */
+ name?: string;
+
+ /**
+ * Column name defined
+ */
+ column_name: string;
+
+ /**
+ * Type of the column value
+ */
+ type: string | null;
+
+ /**
+ * Generic data type format
+ */
+ type_generic: GenericDataType;
+
+ /**
+ * True if the column is date format
+ */
+ is_dttm: boolean;
+};
+
+export enum CTASMethod {
+ Table = 'TABLE',
+ View = 'VIEW',
+}
+
+export interface CTAS {
+ /**
+ * Create method for CTAS creation request
+ */
+ method: CTASMethod;
+
+ /**
+ * Temporary table name for creation using a CTAS query
+ */
+ tempTable: string | null;
+}
+
+export interface QueryRequestContext {
+ /**
+ * Unique query ID on client side
+ */
+ id: string;
+
+ /**
+ * Contains CTAS if the query requests table creation
+ */
+ ctas: CTAS | null;
+
+ /**
+ * The SQL editor instance associated with the query
+ */
+ editor: Editor;
+
+ /**
+ * Requested row limit for the query
+ */
+ queryLimit: number | null;
+
+ /**
+ * True if the query execution result will be/was delivered asynchronously
+ */
+ runAsync?: boolean;
+
+ /**
+ * Start datetime for the query in a numerical timestamp
+ */
+ startDttm: number;
+
+ /**
+ * The tab instance associated with the request query
+ */
+ tab: Tab;
+
+ /**
+ * A key-value JSON formatted string associated with Jinja template variables
+ */
+ templateParams: string;
+}
+
+export interface QueryErrorResultContext extends QueryRequestContext {
+ /**
+ * Finished datetime for the query in a numerical timestamp
+ */
+ endDttm: number;
+
+ /**
+ * Error message returned from DB engine
+ */
+ errorMessage: string;
+
+ /**
+ * Error details in a SupersetError structure
+ */
+ errors?: SupersetError[];
+
+ /**
+ * Executed sql after parsing the jinja templates
+ */
+ executedSql: string | null;
+}
+
+export interface QueryResultContext extends QueryRequestContext {
+ /**
+ * Finished datetime for the query in a numerical timestamp
+ */
+ endDttm: number;
+
+ /**
+ * Executed sql after parsing the jinja templates
+ */
+ executedSql: string;
+
+ /**
+ * Actual number of rows returned by the query
+ */
+ limit: number;
Review Comment:
This name needs to be more distinguishable given that there's a `queryLimit`
property inherited from `QueryContext`. How about changing both properties to
`requestedLimit` and `appliedLimit`? We would also need to rename
`limitingFactor`.
##########
superset-frontend/packages/superset-core/src/api/sqlLab.ts:
##########
@@ -111,6 +111,171 @@ export interface Tab {
panels: Panel[];
}
+/**
+ * Generic data types, see enum of the same name in superset/utils/core.py.
+ */
+enum GenericDataType {
+ Numeric = 0,
+ String = 1,
+ Temporal = 2,
+ Boolean = 3,
+}
+
+/**
+ * Column metadata returned in query results.
+ */
+export type QueryColumn = {
+ /**
+ * Label of the column
+ */
+ name?: string;
+
+ /**
+ * Column name defined
+ */
+ column_name: string;
+
+ /**
+ * Type of the column value
+ */
+ type: string | null;
+
+ /**
+ * Generic data type format
+ */
+ type_generic: GenericDataType;
+
+ /**
+ * True if the column is date format
+ */
+ is_dttm: boolean;
+};
+
+export enum CTASMethod {
+ Table = 'TABLE',
+ View = 'VIEW',
+}
+
+export interface CTAS {
+ /**
+ * Create method for CTAS creation request
+ */
+ method: CTASMethod;
+
+ /**
+ * Temporary table name for creation using a CTAS query
+ */
+ tempTable: string | null;
+}
+
+export interface QueryRequestContext {
+ /**
+ * Unique query ID on client side
+ */
+ id: string;
+
+ /**
+ * Contains CTAS if the query requests table creation
+ */
+ ctas: CTAS | null;
+
+ /**
+ * The SQL editor instance associated with the query
+ */
+ editor: Editor;
+
+ /**
+ * Requested row limit for the query
+ */
+ queryLimit: number | null;
+
+ /**
+ * True if the query execution result will be/was delivered asynchronously
+ */
+ runAsync?: boolean;
+
+ /**
+ * Start datetime for the query in a numerical timestamp
+ */
+ startDttm: number;
+
+ /**
+ * The tab instance associated with the request query
+ */
+ tab: Tab;
+
+ /**
+ * A key-value JSON formatted string associated with Jinja template variables
+ */
+ templateParams: string;
+}
+
+export interface QueryErrorResultContext extends QueryRequestContext {
+ /**
+ * Finished datetime for the query in a numerical timestamp
+ */
+ endDttm: number;
+
+ /**
+ * Error message returned from DB engine
+ */
+ errorMessage: string;
+
+ /**
+ * Error details in a SupersetError structure
+ */
+ errors?: SupersetError[];
+
+ /**
+ * Executed sql after parsing the jinja templates
+ */
+ executedSql: string | null;
+}
+
+export interface QueryResultContext extends QueryRequestContext {
+ /**
+ * Finished datetime for the query in a numerical timestamp
+ */
+ endDttm: number;
+
+ /**
+ * Executed sql after parsing the jinja templates
Review Comment:
```suggestion
* Executed SQL after parsing Jinja templates and resolving virtual
datasets.
```
##########
superset-frontend/packages/superset-core/src/api/core.ts:
##########
@@ -76,6 +76,35 @@ export declare interface Database {
schemas: Schema[];
}
+// Keep in sync with superset/errors.py
+export type ErrorLevel = 'info' | 'warning' | 'error';
+
+/**
+ * Superset error object structure.
+ * Contains details about an error that occurred within Superset.
+ */
+export type SupersetError<ExtraType = Record<string, any> | null> = {
Review Comment:
```suggestion
export type SupersetError = {
```
##########
superset-frontend/packages/superset-core/src/api/sqlLab.ts:
##########
@@ -111,6 +111,171 @@ export interface Tab {
panels: Panel[];
}
+/**
+ * Generic data types, see enum of the same name in superset/utils/core.py.
+ */
+enum GenericDataType {
+ Numeric = 0,
+ String = 1,
+ Temporal = 2,
+ Boolean = 3,
+}
+
+/**
+ * Column metadata returned in query results.
+ */
+export type QueryColumn = {
+ /**
+ * Label of the column
+ */
+ name?: string;
+
+ /**
+ * Column name defined
+ */
+ column_name: string;
+
+ /**
+ * Type of the column value
+ */
+ type: string | null;
+
+ /**
+ * Generic data type format
+ */
+ type_generic: GenericDataType;
+
+ /**
+ * True if the column is date format
+ */
+ is_dttm: boolean;
+};
+
+export enum CTASMethod {
+ Table = 'TABLE',
+ View = 'VIEW',
+}
+
+export interface CTAS {
+ /**
+ * Create method for CTAS creation request
+ */
+ method: CTASMethod;
+
+ /**
+ * Temporary table name for creation using a CTAS query
+ */
+ tempTable: string | null;
+}
+
+export interface QueryRequestContext {
+ /**
+ * Unique query ID on client side
+ */
+ id: string;
+
+ /**
+ * Contains CTAS if the query requests table creation
+ */
+ ctas: CTAS | null;
+
+ /**
+ * The SQL editor instance associated with the query
+ */
+ editor: Editor;
Review Comment:
The `editor` is already available in `tab`.
##########
superset-frontend/packages/superset-core/src/api/sqlLab.ts:
##########
@@ -111,6 +111,171 @@ export interface Tab {
panels: Panel[];
}
+/**
+ * Generic data types, see enum of the same name in superset/utils/core.py.
+ */
+enum GenericDataType {
+ Numeric = 0,
+ String = 1,
+ Temporal = 2,
+ Boolean = 3,
+}
+
+/**
+ * Column metadata returned in query results.
+ */
+export type QueryColumn = {
+ /**
+ * Label of the column
+ */
+ name?: string;
+
+ /**
+ * Column name defined
+ */
+ column_name: string;
+
+ /**
+ * Type of the column value
+ */
+ type: string | null;
+
+ /**
+ * Generic data type format
+ */
+ type_generic: GenericDataType;
+
+ /**
+ * True if the column is date format
+ */
+ is_dttm: boolean;
+};
+
+export enum CTASMethod {
+ Table = 'TABLE',
+ View = 'VIEW',
+}
+
+export interface CTAS {
+ /**
+ * Create method for CTAS creation request
+ */
+ method: CTASMethod;
+
+ /**
+ * Temporary table name for creation using a CTAS query
+ */
+ tempTable: string | null;
+}
+
+export interface QueryRequestContext {
+ /**
+ * Unique query ID on client side
+ */
+ id: string;
+
+ /**
+ * Contains CTAS if the query requests table creation
+ */
+ ctas: CTAS | null;
+
+ /**
+ * The SQL editor instance associated with the query
+ */
+ editor: Editor;
+
+ /**
+ * Requested row limit for the query
+ */
+ queryLimit: number | null;
+
+ /**
+ * True if the query execution result will be/was delivered asynchronously
+ */
+ runAsync?: boolean;
+
+ /**
+ * Start datetime for the query in a numerical timestamp
+ */
+ startDttm: number;
+
+ /**
+ * The tab instance associated with the request query
+ */
+ tab: Tab;
+
+ /**
+ * A key-value JSON formatted string associated with Jinja template variables
+ */
+ templateParams: string;
Review Comment:
Shouldn't `templateParams` be a `Record<str, any>`? The serialization
problem needs to be handled elsewhere. Extensions and built-in features might
want to interact with `templateParams`.
##########
superset-frontend/packages/superset-core/src/api/sqlLab.ts:
##########
@@ -111,6 +111,171 @@ export interface Tab {
panels: Panel[];
}
+/**
+ * Generic data types, see enum of the same name in superset/utils/core.py.
+ */
+enum GenericDataType {
+ Numeric = 0,
+ String = 1,
+ Temporal = 2,
+ Boolean = 3,
+}
+
+/**
+ * Column metadata returned in query results.
+ */
+export type QueryColumn = {
Review Comment:
Can we use the `Column` definition in `api/core`? It looks like all
properties would be valid.
##########
superset-frontend/packages/superset-core/src/api/sqlLab.ts:
##########
@@ -111,6 +111,171 @@ export interface Tab {
panels: Panel[];
}
+/**
+ * Generic data types, see enum of the same name in superset/utils/core.py.
+ */
+enum GenericDataType {
Review Comment:
`GenericDataType` should be moved to `api/core` as it's not an exclusive
concept of SQL Lab.
I'll open a follow-up PR to replace all references to `GenericDataType` from
`@superset-ui/core`.
##########
superset-frontend/packages/superset-core/src/api/sqlLab.ts:
##########
@@ -111,6 +111,171 @@ export interface Tab {
panels: Panel[];
}
+/**
+ * Generic data types, see enum of the same name in superset/utils/core.py.
+ */
+enum GenericDataType {
+ Numeric = 0,
+ String = 1,
+ Temporal = 2,
+ Boolean = 3,
+}
+
+/**
+ * Column metadata returned in query results.
+ */
+export type QueryColumn = {
+ /**
+ * Label of the column
+ */
+ name?: string;
+
+ /**
+ * Column name defined
+ */
+ column_name: string;
+
+ /**
+ * Type of the column value
+ */
+ type: string | null;
+
+ /**
+ * Generic data type format
+ */
+ type_generic: GenericDataType;
+
+ /**
+ * True if the column is date format
+ */
+ is_dttm: boolean;
+};
+
+export enum CTASMethod {
+ Table = 'TABLE',
+ View = 'VIEW',
+}
+
+export interface CTAS {
+ /**
+ * Create method for CTAS creation request
+ */
+ method: CTASMethod;
+
+ /**
+ * Temporary table name for creation using a CTAS query
+ */
+ tempTable: string | null;
+}
+
+export interface QueryRequestContext {
+ /**
+ * Unique query ID on client side
+ */
+ id: string;
+
+ /**
+ * Contains CTAS if the query requests table creation
+ */
+ ctas: CTAS | null;
+
+ /**
+ * The SQL editor instance associated with the query
+ */
+ editor: Editor;
+
+ /**
+ * Requested row limit for the query
+ */
+ queryLimit: number | null;
+
+ /**
+ * True if the query execution result will be/was delivered asynchronously
+ */
+ runAsync?: boolean;
+
+ /**
+ * Start datetime for the query in a numerical timestamp
+ */
+ startDttm: number;
+
+ /**
+ * The tab instance associated with the request query
+ */
+ tab: Tab;
+
+ /**
+ * A key-value JSON formatted string associated with Jinja template variables
+ */
+ templateParams: string;
+}
+
+export interface QueryErrorResultContext extends QueryRequestContext {
+ /**
+ * Finished datetime for the query in a numerical timestamp
+ */
+ endDttm: number;
+
+ /**
+ * Error message returned from DB engine
+ */
+ errorMessage: string;
+
+ /**
+ * Error details in a SupersetError structure
+ */
+ errors?: SupersetError[];
+
+ /**
+ * Executed sql after parsing the jinja templates
Review Comment:
```suggestion
* Executed SQL after parsing Jinja templates and resolving virtual
datasets.
```
##########
superset-frontend/packages/superset-core/src/api/sqlLab.ts:
##########
@@ -111,6 +111,171 @@ export interface Tab {
panels: Panel[];
}
+/**
+ * Generic data types, see enum of the same name in superset/utils/core.py.
+ */
+enum GenericDataType {
+ Numeric = 0,
+ String = 1,
+ Temporal = 2,
+ Boolean = 3,
+}
+
+/**
+ * Column metadata returned in query results.
+ */
+export type QueryColumn = {
+ /**
+ * Label of the column
+ */
+ name?: string;
+
+ /**
+ * Column name defined
+ */
+ column_name: string;
+
+ /**
+ * Type of the column value
+ */
+ type: string | null;
+
+ /**
+ * Generic data type format
+ */
+ type_generic: GenericDataType;
+
+ /**
+ * True if the column is date format
+ */
+ is_dttm: boolean;
+};
+
+export enum CTASMethod {
+ Table = 'TABLE',
+ View = 'VIEW',
+}
+
+export interface CTAS {
+ /**
+ * Create method for CTAS creation request
+ */
+ method: CTASMethod;
+
+ /**
+ * Temporary table name for creation using a CTAS query
+ */
+ tempTable: string | null;
+}
+
+export interface QueryRequestContext {
+ /**
+ * Unique query ID on client side
+ */
+ id: string;
+
+ /**
+ * Contains CTAS if the query requests table creation
+ */
+ ctas: CTAS | null;
+
+ /**
+ * The SQL editor instance associated with the query
+ */
+ editor: Editor;
+
+ /**
+ * Requested row limit for the query
+ */
+ queryLimit: number | null;
+
+ /**
+ * True if the query execution result will be/was delivered asynchronously
+ */
+ runAsync?: boolean;
+
+ /**
+ * Start datetime for the query in a numerical timestamp
+ */
+ startDttm: number;
+
+ /**
+ * The tab instance associated with the request query
+ */
+ tab: Tab;
+
+ /**
+ * A key-value JSON formatted string associated with Jinja template variables
+ */
+ templateParams: string;
+}
+
+export interface QueryErrorResultContext extends QueryRequestContext {
+ /**
+ * Finished datetime for the query in a numerical timestamp
+ */
+ endDttm: number;
+
+ /**
+ * Error message returned from DB engine
+ */
+ errorMessage: string;
+
+ /**
+ * Error details in a SupersetError structure
+ */
+ errors?: SupersetError[];
+
+ /**
+ * Executed sql after parsing the jinja templates
+ */
+ executedSql: string | null;
+}
+
+export interface QueryResultContext extends QueryRequestContext {
+ /**
+ * Finished datetime for the query in a numerical timestamp
+ */
+ endDttm: number;
+
+ /**
+ * Executed sql after parsing the jinja templates
+ */
+ executedSql: string;
+
+ /**
+ * Actual number of rows returned by the query
+ */
+ limit: number;
+
+ /**
+ * Major factor that is determining the row limit of the query results
+ */
+ limitingFactor: string;
+
+ /**
+ * Remote query id stored in backend
+ */
+ queryId: number;
Review Comment:
`QueryContext` has an `id` property which is a client-side ID. Maybe we
should rename both to `clientId` and `remoteId` to make things clearer.
##########
superset-frontend/packages/superset-core/src/api/sqlLab.ts:
##########
@@ -111,6 +111,171 @@ export interface Tab {
panels: Panel[];
}
+/**
+ * Generic data types, see enum of the same name in superset/utils/core.py.
+ */
+enum GenericDataType {
+ Numeric = 0,
+ String = 1,
+ Temporal = 2,
+ Boolean = 3,
+}
+
+/**
+ * Column metadata returned in query results.
+ */
+export type QueryColumn = {
+ /**
+ * Label of the column
+ */
+ name?: string;
+
+ /**
+ * Column name defined
+ */
+ column_name: string;
+
+ /**
+ * Type of the column value
+ */
+ type: string | null;
+
+ /**
+ * Generic data type format
+ */
+ type_generic: GenericDataType;
+
+ /**
+ * True if the column is date format
+ */
+ is_dttm: boolean;
+};
+
+export enum CTASMethod {
+ Table = 'TABLE',
+ View = 'VIEW',
+}
+
+export interface CTAS {
+ /**
+ * Create method for CTAS creation request
+ */
+ method: CTASMethod;
+
+ /**
+ * Temporary table name for creation using a CTAS query
+ */
+ tempTable: string | null;
+}
+
+export interface QueryRequestContext {
Review Comment:
Given that the properties defined here are used both when requesting a query
but also for query results and errors, I think we should remove the `Request`
word from the name and just call it `QueryContext`.
##########
superset-frontend/packages/superset-core/src/api/sqlLab.ts:
##########
@@ -111,6 +111,171 @@ export interface Tab {
panels: Panel[];
}
+/**
+ * Generic data types, see enum of the same name in superset/utils/core.py.
+ */
+enum GenericDataType {
+ Numeric = 0,
+ String = 1,
+ Temporal = 2,
+ Boolean = 3,
+}
+
+/**
+ * Column metadata returned in query results.
+ */
+export type QueryColumn = {
+ /**
+ * Label of the column
+ */
+ name?: string;
+
+ /**
+ * Column name defined
+ */
+ column_name: string;
Review Comment:
If we had something like Thrift configured to generate both Typescript and
Python classes, we would configure it so that Typescript classes would follow
the camelCase convention and Python classes would follow the snake_case
convention. Something like:
```
// Define in Thrift with consistent naming
struct QueryRequest {
1: i32 query_limit
2: string template_params
}
// Configure generators:
// - TypeScript: --gen js:ts,snake_case_to_camel_case
// - Python: --gen py (keeps snake_case naturally)
```
Given that we're defining Typescript APIs, let's make them all camelCase and
handle the conversion manually for now. We can introduce an automatic
conversion layer later.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]