Vishwas1 commited on
Commit
3cc6bdd
Β·
verified Β·
1 Parent(s): bc94f5e

πŸš€ ENHANCED VERSION: Live object selection, schema reading, Faker test data generation, field selection

Browse files
Files changed (1) hide show
  1. app.py +273 -57
app.py CHANGED
@@ -3,21 +3,172 @@ import pandas as pd
3
  from simple_salesforce import Salesforce
4
  from datetime import datetime
5
  import logging
 
 
 
6
 
7
  # Set up logging
8
  logging.basicConfig(level=logging.INFO)
9
  logger = logging.getLogger(__name__)
10
 
11
- # Global connection
12
  sf_connection = None
 
 
 
13
 
14
- def salesforce_data_loader(username, password, security_token, sandbox, operation, csv_file):
15
- """Main function that handles all Salesforce operations"""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
16
  global sf_connection
17
 
18
  # Step 1: Connect to Salesforce
19
  if not username or not password or not security_token:
20
- return "❌ Please provide username, password, and security token"
21
 
22
  try:
23
  domain = 'test' if sandbox else None
@@ -30,19 +181,53 @@ def salesforce_data_loader(username, password, security_token, sandbox, operatio
30
 
31
  connection_msg = f"βœ… Connected to Salesforce as {username}\n"
32
 
33
- # Step 2: Handle file upload if provided
34
- if csv_file is not None and operation != "connect_only":
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
35
  try:
36
- # Read the file
37
  if csv_file.name.endswith('.csv'):
38
  df = pd.read_csv(csv_file.name)
39
  elif csv_file.name.endswith(('.xlsx', '.xls')):
40
  df = pd.read_excel(csv_file.name)
41
  else:
42
- return connection_msg + "❌ Please upload a CSV or Excel file"
43
 
44
  if df.empty:
45
- return connection_msg + "❌ The uploaded file is empty"
46
 
47
  # Clean data
48
  records = df.to_dict('records')
@@ -51,43 +236,46 @@ def salesforce_data_loader(username, password, security_token, sandbox, operatio
51
  cleaned_record = {k: v for k, v in record.items() if pd.notna(v)}
52
  cleaned_records.append(cleaned_record)
53
 
54
- # Determine object based on columns (simple heuristic)
55
- columns = df.columns.str.lower()
56
- if any(col in columns for col in ['firstname', 'lastname', 'email']):
57
- object_name = "Contact"
58
- elif any(col in columns for col in ['company', 'name']):
59
- object_name = "Account"
60
- else:
61
- object_name = "Lead"
62
-
63
- # Perform operation using bulk API correctly
64
- if operation == "insert":
65
- result = sf_connection.bulk.__getattr__(object_name).insert(cleaned_records)
66
- elif operation == "update":
67
- result = sf_connection.bulk.__getattr__(object_name).update(cleaned_records)
68
- else:
69
- return connection_msg + "❌ Invalid operation. Use 'insert' or 'update'"
70
 
71
  # Process results
72
  success_count = sum(1 for r in result if r.get('success'))
73
  error_count = len(result) - success_count
74
 
75
- upload_msg = f"\nπŸ“€ Upload Results:\n"
76
- upload_msg += f"Object: {object_name}\n"
77
- upload_msg += f"Total records: {len(records)}\n"
78
- upload_msg += f"βœ… Successful: {success_count}\n"
79
- upload_msg += f"❌ Failed: {error_count}\n"
 
 
 
 
80
 
81
- return connection_msg + upload_msg
82
 
83
  except Exception as e:
84
- return connection_msg + f"❌ Upload error: {str(e)}"
85
 
86
- # Step 3: Handle export operation
87
- elif operation == "export":
 
 
88
  try:
89
- # Export some Account records as example
90
- query = "SELECT Id, Name, Type, Phone, Website FROM Account LIMIT 100"
 
 
 
 
 
 
 
 
 
 
 
91
  result = sf_connection.query_all(query)
92
  records = result['records']
93
 
@@ -96,35 +284,40 @@ def salesforce_data_loader(username, password, security_token, sandbox, operatio
96
  if 'attributes' in df.columns:
97
  df = df.drop('attributes', axis=1)
98
 
 
 
 
 
99
  export_msg = f"\nπŸ“₯ Export Results:\n"
 
100
  export_msg += f"Records exported: {len(records)}\n"
101
  export_msg += f"Fields: {', '.join(df.columns)}\n"
102
- export_msg += f"Sample data:\n{df.head().to_string()}"
103
 
104
- return connection_msg + export_msg
105
  else:
106
- return connection_msg + "\n❌ No records found to export"
107
 
108
  except Exception as e:
109
- return connection_msg + f"\n❌ Export error: {str(e)}"
110
 
111
  else:
112
- return connection_msg + "\nπŸ’‘ Connection successful! Upload a file to insert/update data, or select 'export' to download data."
113
 
114
  except Exception as e:
115
  error_msg = str(e)
116
  if "INVALID_LOGIN" in error_msg:
117
- return "❌ Invalid credentials. Please check your username, password, and security token."
118
  elif "API_DISABLED_FOR_ORG" in error_msg:
119
- return "❌ API access is disabled. Contact your Salesforce admin."
120
  elif "LOGIN_MUST_USE_SECURITY_TOKEN" in error_msg:
121
- return "❌ Security token required. Append it to your password."
122
  else:
123
- return f"❌ Connection failed: {error_msg}"
124
 
125
- # Create the interface
126
  demo = gr.Interface(
127
- fn=salesforce_data_loader,
128
  inputs=[
129
  gr.Textbox(label="Username", placeholder="[email protected]"),
130
  gr.Textbox(label="Password", type="password"),
@@ -132,24 +325,47 @@ demo = gr.Interface(
132
  gr.Checkbox(label="Sandbox Environment"),
133
  gr.Dropdown(
134
  label="Operation",
135
- choices=["connect_only", "insert", "update", "export"],
 
 
 
 
 
 
136
  value="connect_only"
137
  ),
138
- gr.File(label="CSV/Excel File (optional)", file_types=[".csv", ".xlsx", ".xls"])
 
 
 
 
 
 
 
 
 
139
  ],
140
- outputs=gr.Textbox(label="Results", lines=10),
141
- title="πŸš€ Salesforce Data Loader",
142
  description="""
143
- **Simple Salesforce Data Management Tool**
144
 
145
- 1. **Connect**: Enter your credentials and select 'connect_only'
146
- 2. **Upload**: Select 'insert' or 'update' and upload a CSV/Excel file
147
- 3. **Export**: Select 'export' to download Account data
 
 
 
148
 
149
- **Note**: For uploads, the tool auto-detects object type based on column names.
 
 
 
 
 
 
150
  """,
151
  examples=[
152
- ["[email protected]", "password123", "token123", False, "connect_only", None],
153
  ]
154
  )
155
 
 
3
  from simple_salesforce import Salesforce
4
  from datetime import datetime
5
  import logging
6
+ import json
7
+ from faker import Faker
8
+ import random
9
 
10
  # Set up logging
11
  logging.basicConfig(level=logging.INFO)
12
  logger = logging.getLogger(__name__)
13
 
14
+ # Global connection and state
15
  sf_connection = None
16
+ available_objects = []
17
+ object_schemas = {}
18
+ fake = Faker()
19
 
20
+ def get_salesforce_objects():
21
+ """Get list of available Salesforce objects"""
22
+ global sf_connection, available_objects
23
+
24
+ if not sf_connection:
25
+ return []
26
+
27
+ try:
28
+ # Get commonly used objects and test their accessibility
29
+ common_objects = [
30
+ 'Account', 'Contact', 'Lead', 'Opportunity', 'Case',
31
+ 'Campaign', 'User', 'Product2', 'Task', 'Event'
32
+ ]
33
+ available_objects = []
34
+
35
+ for obj_name in common_objects:
36
+ try:
37
+ obj = getattr(sf_connection, obj_name)
38
+ obj.describe()
39
+ available_objects.append(obj_name)
40
+ except:
41
+ continue
42
+
43
+ return available_objects
44
+ except Exception as e:
45
+ logger.error(f"Error getting objects: {str(e)}")
46
+ return ['Account', 'Contact', 'Lead']
47
+
48
+ def get_object_schema(object_name):
49
+ """Get schema for a specific Salesforce object"""
50
+ global sf_connection, object_schemas
51
+
52
+ if not sf_connection or not object_name:
53
+ return {}
54
+
55
+ try:
56
+ if object_name not in object_schemas:
57
+ obj = getattr(sf_connection, object_name)
58
+ metadata = obj.describe()
59
+
60
+ schema = {
61
+ 'name': object_name,
62
+ 'label': metadata.get('label', object_name),
63
+ 'fields': []
64
+ }
65
+
66
+ for field in metadata['fields']:
67
+ if field['createable'] or field['updateable']:
68
+ field_info = {
69
+ 'name': field['name'],
70
+ 'label': field['label'],
71
+ 'type': field['type'],
72
+ 'required': not field['nillable'] and not field['defaultedOnCreate'],
73
+ 'length': field.get('length', 0),
74
+ 'picklistValues': [pv['value'] for pv in field.get('picklistValues', [])]
75
+ }
76
+ schema['fields'].append(field_info)
77
+
78
+ object_schemas[object_name] = schema
79
+
80
+ return object_schemas[object_name]
81
+ except Exception as e:
82
+ logger.error(f"Error getting schema for {object_name}: {str(e)}")
83
+ return {}
84
+
85
+ def generate_test_data(object_name, fields, num_records=100):
86
+ """Generate test data using Faker for specified object and fields"""
87
+ try:
88
+ schema = get_object_schema(object_name)
89
+ if not schema:
90
+ return None, "❌ Could not get object schema"
91
+
92
+ records = []
93
+
94
+ for _ in range(num_records):
95
+ record = {}
96
+
97
+ for field_name in fields:
98
+ field_info = next((f for f in schema['fields'] if f['name'] == field_name), None)
99
+ if not field_info:
100
+ continue
101
+
102
+ field_type = field_info['type']
103
+
104
+ # Generate data based on field type and name
105
+ if field_name.lower() in ['firstname', 'first_name']:
106
+ record[field_name] = fake.first_name()
107
+ elif field_name.lower() in ['lastname', 'last_name']:
108
+ record[field_name] = fake.last_name()
109
+ elif field_name.lower() in ['name'] and object_name == 'Account':
110
+ record[field_name] = fake.company()
111
+ elif field_name.lower() in ['email']:
112
+ record[field_name] = fake.email()
113
+ elif field_name.lower() in ['phone']:
114
+ record[field_name] = fake.phone_number()
115
+ elif field_name.lower() in ['website']:
116
+ record[field_name] = fake.url()
117
+ elif field_name.lower() in ['street', 'mailingstreet', 'billingstreet']:
118
+ record[field_name] = fake.street_address()
119
+ elif field_name.lower() in ['city', 'mailingcity', 'billingcity']:
120
+ record[field_name] = fake.city()
121
+ elif field_name.lower() in ['state', 'mailingstate', 'billingstate']:
122
+ record[field_name] = fake.state_abbr()
123
+ elif field_name.lower() in ['postalcode', 'mailingpostalcode', 'billingpostalcode']:
124
+ record[field_name] = fake.zipcode()
125
+ elif field_name.lower() in ['country', 'mailingcountry', 'billingcountry']:
126
+ record[field_name] = 'US'
127
+ elif field_type == 'picklist' and field_info['picklistValues']:
128
+ record[field_name] = random.choice(field_info['picklistValues'])
129
+ elif field_type == 'boolean':
130
+ record[field_name] = random.choice([True, False])
131
+ elif field_type in ['int', 'double', 'currency']:
132
+ if 'annual' in field_name.lower() or 'revenue' in field_name.lower():
133
+ record[field_name] = random.randint(100000, 10000000)
134
+ else:
135
+ record[field_name] = random.randint(1, 1000)
136
+ elif field_type == 'date':
137
+ record[field_name] = fake.date_between(start_date='-1y', end_date='today').isoformat()
138
+ elif field_type == 'datetime':
139
+ record[field_name] = fake.date_time_between(start_date='-1y', end_date='now').isoformat()
140
+ elif field_type in ['string', 'textarea']:
141
+ if field_info['length'] > 100:
142
+ record[field_name] = fake.text(max_nb_chars=min(field_info['length'], 200))
143
+ else:
144
+ record[field_name] = fake.sentence(nb_words=3)[:field_info['length']]
145
+ else:
146
+ # Default string value
147
+ record[field_name] = f"Test {field_name}"
148
+
149
+ records.append(record)
150
+
151
+ # Create DataFrame
152
+ df = pd.DataFrame(records)
153
+
154
+ # Save to CSV
155
+ filename = f"test_data_{object_name}_{datetime.now().strftime('%Y%m%d_%H%M%S')}.csv"
156
+ df.to_csv(filename, index=False)
157
+
158
+ return filename, f"βœ… Generated {num_records} test records for {object_name}\nFields: {', '.join(fields)}\nFile: {filename}"
159
+
160
+ except Exception as e:
161
+ logger.error(f"Error generating test data: {str(e)}")
162
+ return None, f"❌ Error generating test data: {str(e)}"
163
+
164
+ def enhanced_salesforce_operation(username, password, security_token, sandbox, operation,
165
+ object_name, selected_fields, csv_file, num_records):
166
+ """Enhanced Salesforce operations with full functionality"""
167
  global sf_connection
168
 
169
  # Step 1: Connect to Salesforce
170
  if not username or not password or not security_token:
171
+ return "❌ Please provide username, password, and security token", None, "[]", "[]"
172
 
173
  try:
174
  domain = 'test' if sandbox else None
 
181
 
182
  connection_msg = f"βœ… Connected to Salesforce as {username}\n"
183
 
184
+ # Get available objects
185
+ objects = get_salesforce_objects()
186
+ objects_json = json.dumps(objects)
187
+
188
+ # Step 2: Handle different operations
189
+ if operation == "connect_only":
190
+ return connection_msg + f"Available objects: {', '.join(objects)}", None, objects_json, "[]"
191
+
192
+ elif operation == "get_schema":
193
+ if not object_name:
194
+ return connection_msg + "❌ Please select an object", None, objects_json, "[]"
195
+
196
+ schema = get_object_schema(object_name)
197
+ fields = [f"{f['name']} ({f['type']})" for f in schema.get('fields', [])]
198
+ fields_json = json.dumps([f['name'] for f in schema.get('fields', [])])
199
+
200
+ return (connection_msg + f"πŸ“‹ Schema for {object_name}:\n" +
201
+ f"Fields: {len(fields)}\n" + "\n".join(fields[:20]) +
202
+ (f"\n... and {len(fields)-20} more fields" if len(fields) > 20 else "")), None, objects_json, fields_json
203
+
204
+ elif operation == "generate_data":
205
+ if not object_name or not selected_fields:
206
+ return connection_msg + "❌ Please select object and fields", None, objects_json, "[]"
207
+
208
+ fields_list = selected_fields.split(',') if isinstance(selected_fields, str) else selected_fields
209
+ filename, result = generate_test_data(object_name, fields_list, num_records)
210
+
211
+ return connection_msg + result, filename, objects_json, "[]"
212
+
213
+ elif operation == "import_data":
214
+ if not csv_file:
215
+ return connection_msg + "❌ Please upload a CSV file", None, objects_json, "[]"
216
+
217
+ if not object_name:
218
+ return connection_msg + "❌ Please select target object", None, objects_json, "[]"
219
+
220
+ # Read and process file
221
  try:
 
222
  if csv_file.name.endswith('.csv'):
223
  df = pd.read_csv(csv_file.name)
224
  elif csv_file.name.endswith(('.xlsx', '.xls')):
225
  df = pd.read_excel(csv_file.name)
226
  else:
227
+ return connection_msg + "❌ Please upload a CSV or Excel file", None, objects_json, "[]"
228
 
229
  if df.empty:
230
+ return connection_msg + "❌ The uploaded file is empty", None, objects_json, "[]"
231
 
232
  # Clean data
233
  records = df.to_dict('records')
 
236
  cleaned_record = {k: v for k, v in record.items() if pd.notna(v)}
237
  cleaned_records.append(cleaned_record)
238
 
239
+ # Import using bulk API
240
+ result = sf_connection.bulk.__getattr__(object_name).insert(cleaned_records)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
241
 
242
  # Process results
243
  success_count = sum(1 for r in result if r.get('success'))
244
  error_count = len(result) - success_count
245
 
246
+ import_msg = f"\nπŸ“€ Import Results:\n"
247
+ import_msg += f"Object: {object_name}\n"
248
+ import_msg += f"Total records: {len(records)}\n"
249
+ import_msg += f"βœ… Successful: {success_count}\n"
250
+ import_msg += f"❌ Failed: {error_count}\n"
251
+
252
+ if error_count > 0:
253
+ errors = [r.get('errors', []) for r in result if not r.get('success')]
254
+ import_msg += f"\nFirst few errors: {str(errors[:3])}"
255
 
256
+ return connection_msg + import_msg, None, objects_json, "[]"
257
 
258
  except Exception as e:
259
+ return connection_msg + f"❌ Import error: {str(e)}", None, objects_json, "[]"
260
 
261
+ elif operation == "export_data":
262
+ if not object_name:
263
+ return connection_msg + "❌ Please select an object", None, objects_json, "[]"
264
+
265
  try:
266
+ schema = get_object_schema(object_name)
267
+
268
+ # Use selected fields or default fields
269
+ if selected_fields:
270
+ fields_list = selected_fields.split(',') if isinstance(selected_fields, str) else selected_fields
271
+ fields_list = [f.strip() for f in fields_list]
272
+ else:
273
+ # Use first 10 fields as default
274
+ fields_list = [f['name'] for f in schema.get('fields', [])[:10]]
275
+
276
+ fields_str = ', '.join(fields_list)
277
+ query = f"SELECT {fields_str} FROM {object_name} LIMIT 100"
278
+
279
  result = sf_connection.query_all(query)
280
  records = result['records']
281
 
 
284
  if 'attributes' in df.columns:
285
  df = df.drop('attributes', axis=1)
286
 
287
+ # Save export file
288
+ export_file = f"export_{object_name}_{datetime.now().strftime('%Y%m%d_%H%M%S')}.csv"
289
+ df.to_csv(export_file, index=False)
290
+
291
  export_msg = f"\nπŸ“₯ Export Results:\n"
292
+ export_msg += f"Object: {object_name}\n"
293
  export_msg += f"Records exported: {len(records)}\n"
294
  export_msg += f"Fields: {', '.join(df.columns)}\n"
295
+ export_msg += f"Sample data:\n{df.head(3).to_string()}"
296
 
297
+ return connection_msg + export_msg, export_file, objects_json, "[]"
298
  else:
299
+ return connection_msg + f"\n❌ No {object_name} records found", None, objects_json, "[]"
300
 
301
  except Exception as e:
302
+ return connection_msg + f"\n❌ Export error: {str(e)}", None, objects_json, "[]"
303
 
304
  else:
305
+ return connection_msg + "❌ Invalid operation", None, objects_json, "[]"
306
 
307
  except Exception as e:
308
  error_msg = str(e)
309
  if "INVALID_LOGIN" in error_msg:
310
+ return "❌ Invalid credentials. Please check your username, password, and security token.", None, "[]", "[]"
311
  elif "API_DISABLED_FOR_ORG" in error_msg:
312
+ return "❌ API access is disabled. Contact your Salesforce admin.", None, "[]", "[]"
313
  elif "LOGIN_MUST_USE_SECURITY_TOKEN" in error_msg:
314
+ return "❌ Security token required. Append it to your password.", None, "[]", "[]"
315
  else:
316
+ return f"❌ Connection failed: {error_msg}", None, "[]", "[]"
317
 
318
+ # Create the enhanced interface
319
  demo = gr.Interface(
320
+ fn=enhanced_salesforce_operation,
321
  inputs=[
322
  gr.Textbox(label="Username", placeholder="[email protected]"),
323
  gr.Textbox(label="Password", type="password"),
 
325
  gr.Checkbox(label="Sandbox Environment"),
326
  gr.Dropdown(
327
  label="Operation",
328
+ choices=[
329
+ "connect_only",
330
+ "get_schema",
331
+ "generate_data",
332
+ "import_data",
333
+ "export_data"
334
+ ],
335
  value="connect_only"
336
  ),
337
+ gr.Dropdown(label="Salesforce Object", choices=[], allow_custom_value=True),
338
+ gr.Textbox(label="Selected Fields (comma-separated)", placeholder="Name,Email,Phone"),
339
+ gr.File(label="CSV/Excel File (for import)", file_types=[".csv", ".xlsx", ".xls"]),
340
+ gr.Slider(label="Number of Test Records", minimum=10, maximum=1000, value=100, step=10)
341
+ ],
342
+ outputs=[
343
+ gr.Textbox(label="Results", lines=15),
344
+ gr.File(label="Download File"),
345
+ gr.Textbox(label="Available Objects (JSON)", visible=False),
346
+ gr.Textbox(label="Available Fields (JSON)", visible=False)
347
  ],
348
+ title="πŸš€ Enhanced Salesforce Data Loader",
 
349
  description="""
350
+ **Advanced Salesforce Data Management Tool**
351
 
352
+ **Workflow:**
353
+ 1. **Connect**: Enter credentials, select 'connect_only' to see available objects
354
+ 2. **Get Schema**: Select object, choose 'get_schema' to see fields
355
+ 3. **Generate Data**: Select fields, choose 'generate_data' to create test data with Faker
356
+ 4. **Import**: Upload CSV/Excel, select target object, choose 'import_data'
357
+ 5. **Export**: Select object and fields, choose 'export_data'
358
 
359
+ **Features:**
360
+ - βœ… Live object detection from your Salesforce org
361
+ - βœ… Dynamic schema reading with field types
362
+ - βœ… Intelligent test data generation using Faker
363
+ - βœ… Field mapping and validation
364
+ - βœ… Bulk operations for performance
365
+ - βœ… Relationship data support (Account + Contact)
366
  """,
367
  examples=[
368
+ ["[email protected]", "password123", "token123", False, "connect_only", "", "", None, 100],
369
  ]
370
  )
371