fix(UX): Upload Attendance

Enqueue import if rows more than 200. Show import progress in dashboard.
diff --git a/erpnext/hr/doctype/upload_attendance/upload_attendance.js b/erpnext/hr/doctype/upload_attendance/upload_attendance.js
index 277c060..e9c2031 100644
--- a/erpnext/hr/doctype/upload_attendance/upload_attendance.js
+++ b/erpnext/hr/doctype/upload_attendance/upload_attendance.js
@@ -14,6 +14,7 @@
 	refresh: function() {
 		this.frm.disable_save();
 		this.show_upload();
+		this.setup_import_progress();
 	},
 
 	get_template:function() {
@@ -33,46 +34,37 @@
 		var $wrapper = $(cur_frm.fields_dict.upload_html.wrapper).empty();
 		new frappe.ui.FileUploader({
 			wrapper: $wrapper,
-			method: 'erpnext.hr.doctype.upload_attendance.upload_attendance.upload',
-			on_success(file_doc, r) {
-				var $log_wrapper = $(cur_frm.fields_dict.import_log.wrapper).empty();
-
-				if(!r.messages) r.messages = [];
-				// replace links if error has occured
-				if(r.exc || r.error) {
-					r.messages = $.map(r.message.messages, function(v) {
-						var msg = v.replace("Inserted", "Valid")
-							.replace("Updated", "Valid").split("<");
-						if (msg.length > 1) {
-							v = msg[0] + (msg[1].split(">").slice(-1)[0]);
-						} else {
-							v = msg[0];
-						}
-						return v;
-					});
-
-					r.messages = ["<h4 style='color:red'>"+__("Import Failed!")+"</h4>"]
-						.concat(r.messages);
-				} else {
-					r.messages = ["<h4 style='color:green'>"+__("Import Successful!")+"</h4>"]
-						.concat(r.message.messages);
-				}
-
-				$.each(r.messages, function(i, v) {
-					var $p = $('<p>').html(v).appendTo($log_wrapper);
-					if(v.substr(0,5)=='Error') {
-						$p.css('color', 'red');
-					} else if(v.substr(0,8)=='Inserted') {
-						$p.css('color', 'green');
-					} else if(v.substr(0,7)=='Updated') {
-						$p.css('color', 'green');
-					} else if(v.substr(0,5)=='Valid') {
-						$p.css('color', '#777');
-					}
-				});
-			}
+			method: 'erpnext.hr.doctype.upload_attendance.upload_attendance.upload'
 		});
 	},
+
+	setup_import_progress() {
+		var $log_wrapper = $(this.frm.fields_dict.import_log.wrapper).empty();
+
+		frappe.realtime.on('import_attendance', (data) => {
+			console.log(data)
+			if (data.progress) {
+				this.frm.dashboard.show_progress('Import Attendance', data.progress / data.total * 100,
+					__('Importing {0} of {1}', [data.progress, data.total]));
+				if (data.progress === data.total) {
+					this.frm.dashboard.hide_progress('Import Attendance');
+				}
+			} else if (data.error) {
+				this.frm.dashboard.hide();
+				let messages = [`<th>${__('Error in some rows')}</th>`].concat(data.messages
+					.filter(message => message.includes('Error'))
+					.map(message => `<tr><td>${message}</td></tr>`))
+					.join('');
+				$log_wrapper.append('<table class="table table-bordered">' + messages);
+			} else if (data.messages) {
+				this.frm.dashboard.hide();
+				let messages = [`<th>${__('Import Successful')}</th>`].concat(data.messages
+					.map(message => `<tr><td>${message}</td></tr>`))
+					.join('');
+				$log_wrapper.append('<table class="table table-bordered">' + messages);
+			}
+		});
+	}
 })
 
 cur_frm.cscript = new erpnext.hr.AttendanceControlPanel({frm: cur_frm});
diff --git a/erpnext/hr/doctype/upload_attendance/upload_attendance.py b/erpnext/hr/doctype/upload_attendance/upload_attendance.py
index 6a4ea6a..f452155 100644
--- a/erpnext/hr/doctype/upload_attendance/upload_attendance.py
+++ b/erpnext/hr/doctype/upload_attendance/upload_attendance.py
@@ -117,22 +117,25 @@
 		raise frappe.PermissionError
 
 	from frappe.utils.csvutils import read_csv_content
+	rows = read_csv_content(frappe.local.uploaded_file)
+	if not rows:
+		frappe.throw(_("Please select a csv file"))
+	frappe.enqueue(import_attendances, rows=rows, now=True if len(rows) < 200 else False)
+
+def import_attendances(rows):
 	from frappe.modules import scrub
 
-	rows = read_csv_content(frappe.local.uploaded_file)
 	rows = list(filter(lambda x: x and any(x), rows))
-	if not rows:
-		msg = [_("Please select a csv file")]
-		return {"messages": msg, "error": msg}
 	columns = [scrub(f) for f in rows[4]]
 	columns[0] = "name"
 	columns[3] = "attendance_date"
+	rows = rows[5:]
 	ret = []
 	error = False
 
 	from frappe.utils.csvutils import check_record, import_doc
 
-	for i, row in enumerate(rows[5:]):
+	for i, row in enumerate(rows):
 		if not row: continue
 		row_idx = i + 5
 		d = frappe._dict(zip(columns, row))
@@ -144,6 +147,10 @@
 		try:
 			check_record(d)
 			ret.append(import_doc(d, "Attendance", 1, row_idx, submit=True))
+			frappe.publish_realtime('import_attendance', dict(
+				progress=i,
+				total=len(rows)
+			))
 		except AttributeError:
 			pass
 		except Exception as e:
@@ -156,4 +163,8 @@
 		frappe.db.rollback()
 	else:
 		frappe.db.commit()
-	return {"messages": ret, "error": error}
+
+	frappe.publish_realtime('import_attendance', dict(
+		messages=ret,
+		error=error
+	))