refactor(job): Implement polymorphic behavior on creation for jobs #134

Merged
NaiJi merged 3 commits from server-settings into master 2022-10-08 19:22:46 +03:00
5 changed files with 57 additions and 77 deletions

View File

@ -24,14 +24,13 @@ class JobsCubit extends Cubit<JobsState> {
final ServicesCubit servicesCubit;
void addJob(final ClientJob job) {
final List<ClientJob> newJobsList = [];
if (state is JobsStateWithJobs) {
final JobsStateWithJobs jobsState = state as JobsStateWithJobs;
newJobsList.addAll(jobsState.clientJobList);
final jobs = currentJobList;
if (job.canAddTo(jobs)) {
_updateJobsState([
...jobs,
...[job],
]);
}
newJobsList.add(job);
getIt<NavigationService>().showSnackBar('jobs.job_added'.tr());
emit(JobsStateWithJobs(newJobsList));
}
void removeJob(final String id) {
@ -39,37 +38,18 @@ class JobsCubit extends Cubit<JobsState> {
emit(newState);
}
void createOrRemoveServiceToggleJob(final ToggleJob job) {
final List<ClientJob> newJobsList = <ClientJob>[];
List<ClientJob> get currentJobList {
final List<ClientJob> jobs = <ClientJob>[];
if (state is JobsStateWithJobs) {
newJobsList.addAll((state as JobsStateWithJobs).clientJobList);
}
final bool needToRemoveJob = newJobsList
.any((final el) => el is ServiceToggleJob && el.id == job.id);
if (needToRemoveJob) {
final ClientJob removingJob = newJobsList.firstWhere(
(final el) => el is ServiceToggleJob && el.id == job.id,
);
removeJob(removingJob.id);
} else {
newJobsList.add(job);
getIt<NavigationService>().showSnackBar('jobs.job_added'.tr());
emit(JobsStateWithJobs(newJobsList));
jobs.addAll((state as JobsStateWithJobs).clientJobList);
}
return jobs;
}
void createShhJobIfNotExist(final CreateSSHKeyJob job) {
final List<ClientJob> newJobsList = <ClientJob>[];
if (state is JobsStateWithJobs) {
newJobsList.addAll((state as JobsStateWithJobs).clientJobList);
}
final bool isExistInJobList =
newJobsList.any((final el) => el is CreateSSHKeyJob);
if (!isExistInJobList) {
newJobsList.add(job);
getIt<NavigationService>().showSnackBar('jobs.job_added'.tr());
emit(JobsStateWithJobs(newJobsList));
}
void _updateJobsState(final List<ClientJob> newJobs) {
getIt<NavigationService>().showSnackBar('jobs.job_added'.tr());
emit(JobsStateWithJobs(newJobs));
}
Future<void> rebootServer() async {

View File

@ -17,6 +17,7 @@ abstract class ClientJob extends Equatable {
final String title;
final String id;
bool canAddTo(final List<ClientJob> jobs) => true;
void execute(final JobsCubit cubit);
@override
@ -25,10 +26,14 @@ abstract class ClientJob extends Equatable {
class RebuildServerJob extends ClientJob {
RebuildServerJob({
required final super.title,
final super.id,
required super.title,
super.id,
});
@override
bool canAddTo(final List<ClientJob> jobs) =>
!jobs.any((final job) => job is RebuildServerJob);
inex marked this conversation as resolved Outdated

These validations look awful. We have equatable in our project for that.

https://pub.dev/packages/equatable

This library is used in all of our cubits state to compare if they are the same. Should we use it here too?

These validations look awful. We have `equatable` in our project for that. https://pub.dev/packages/equatable This library is used in all of our cubits state to compare if they are the same. Should we use it here too?

We can't use equatable for it, because it bears different semantics. Even its documentation page says

Equatable overrides == and hashCode

What canAddTo does is not simple checking for equality, if you read these checks again you will see they have different meaning, none of them is checking for absolute equality of job instances, at least because jobs have unique ids.

  • RebuildServerJob checks if there is already any RebuildServerJob instance in the list
  • DeleteUserJob checks if there is already a DeleteUserJob instance with the same login in the list
  • ServiceToggleJob checks if there is already a ServiceToggleJob instance with the same service id in the list
  • DeleteSSHKeyJob checks if there is already a DeleteSSHKeyJob instance with the same both user login and public key at the same time.

The issue is the looks of the solution and I actually don't know how to avoid this. I mean, previously these checks were in JobsCubit, now they are in the jobs themselves, the same approach.

We can't use equatable for it, because it bears different semantics. Even its documentation page says ``` Equatable overrides == and hashCode ``` What `canAddTo` does is not simple checking for equality, if you read these checks again you will see they have different meaning, none of them is checking for absolute equality of job instances, at least because jobs have unique ids. - RebuildServerJob checks if there is already **any** RebuildServerJob instance in the list - DeleteUserJob checks if there is already a DeleteUserJob instance with the same login in the list - ServiceToggleJob checks if there is already a ServiceToggleJob instance with the same service id in the list - DeleteSSHKeyJob checks if there is already a DeleteSSHKeyJob instance with the same both user login and public key at the same time. The issue is the looks of the solution and I actually don't know how to avoid this. I mean, previously these checks were in JobsCubit, now they are in the jobs themselves, the same approach.

Equatable covers exactly same logic you see using in these checks. Just drop to props fields you want to check, and that's it.

Equatable covers exactly same logic you see using in these checks. Just drop to `props` fields you want to check, and that's it.

Ah, well, I see that during check you do not construct a Job object, and you nave nothing to compare yet.

Nope, just pass this

~~Ah, well, I see that during check you do not construct a Job object, and you nave nothing to compare yet.~~ Nope, just pass `this`
@override
void execute(final JobsCubit cubit) async {
await cubit.upgradeServer();
@ -74,6 +79,11 @@ class DeleteUserJob extends ClientJob {
final User user;
@override
bool canAddTo(final List<ClientJob> jobs) => !jobs.any(
(final job) => job is DeleteUserJob && job.user.login == user.login,
);
@override
void execute(final JobsCubit cubit) async {
await cubit.usersCubit.deleteUser(user);
@ -83,33 +93,30 @@ class DeleteUserJob extends ClientJob {
List<Object> get props => [id, title, user];
}
abstract class ToggleJob extends ClientJob {
ToggleJob({
required final this.service,
required final super.title,
});
final Service service;
@override
List<Object> get props => [...super.props, service];
}
class ServiceToggleJob extends ToggleJob {
class ServiceToggleJob extends ClientJob {
ServiceToggleJob({
required super.service,
required this.service,
required this.needToTurnOn,
}) : super(
title:
'${needToTurnOn ? "jobs.service_turn_on".tr() : "jobs.service_turn_off".tr()} ${service.displayName}',
);
final bool needToTurnOn;
final Service service;
@override
bool canAddTo(final List<ClientJob> jobs) => !jobs.any(
(final job) => job is ServiceToggleJob && job.service.id == service.id,
);
@override
void execute(final JobsCubit cubit) async {
await cubit.api.switchService(service.id, needToTurnOn);
}
final bool needToTurnOn;
@override
List<Object> get props => [...super.props, service];
}
class CreateSSHKeyJob extends ClientJob {
@ -139,6 +146,14 @@ class DeleteSSHKeyJob extends ClientJob {
final User user;
final String publicKey;
@override
bool canAddTo(final List<ClientJob> jobs) => !jobs.any(
(final job) =>
job is DeleteSSHKeyJob &&
job.publicKey == publicKey &&
job.user.login == user.login,
);
@override
void execute(final JobsCubit cubit) async {
await cubit.usersCubit.deleteSshKey(user, publicKey);

View File

@ -10,7 +10,6 @@ class _ServerSettings extends StatefulWidget {
class _ServerSettingsState extends State<_ServerSettings> {
bool? allowAutoUpgrade;
bool? rebootAfterUpgrade;
bool? didSomethingChange;
@override
Widget build(final BuildContext context) {
@ -25,18 +24,14 @@ class _ServerSettingsState extends State<_ServerSettings> {
rebootAfterUpgrade = serverDetailsState.autoUpgradeSettings.allowReboot;
}
didSomethingChange ??= false;
return Column(
children: [
SwitchListTile(
value: allowAutoUpgrade ?? false,
onChanged: (final switched) {
if (didSomethingChange == false) {
context.read<JobsCubit>().addJob(
RebuildServerJob(title: 'jobs.upgrade_server'.tr()),
);
}
context.read<JobsCubit>().addJob(
RebuildServerJob(title: 'jobs.upgrade_server'.tr()),
);
context
.read<ServerDetailsCubit>()
.repository
@ -48,7 +43,6 @@ class _ServerSettingsState extends State<_ServerSettings> {
);
setState(() {
allowAutoUpgrade = switched;
didSomethingChange = true;
});
},
title: Text('server.allow_autoupgrade'.tr()),
@ -60,11 +54,9 @@ class _ServerSettingsState extends State<_ServerSettings> {
SwitchListTile(
value: rebootAfterUpgrade ?? false,
onChanged: (final switched) {
if (didSomethingChange == false) {
context.read<JobsCubit>().addJob(
RebuildServerJob(title: 'jobs.upgrade_server'.tr()),
);
}
context.read<JobsCubit>().addJob(
RebuildServerJob(title: 'jobs.upgrade_server'.tr()),
);
context
.read<ServerDetailsCubit>()
.repository
@ -76,7 +68,6 @@ class _ServerSettingsState extends State<_ServerSettings> {
);
setState(() {
rebootAfterUpgrade = switched;
didSomethingChange = true;
});
},
title: Text('server.reboot_after_upgrade'.tr()),
@ -91,14 +82,9 @@ class _ServerSettingsState extends State<_ServerSettings> {
serverDetailsState.serverTimezone.toString(),
),
onTap: () {
if (didSomethingChange == false) {
context.read<JobsCubit>().addJob(
RebuildServerJob(title: 'jobs.upgrade_server'.tr()),
);
}
setState(() {
didSomethingChange = true;
});
context.read<JobsCubit>().addJob(
RebuildServerJob(title: 'jobs.upgrade_server'.tr()),
);
Navigator.of(context).push(
materialRoute(
const SelectTimezone(),

View File

@ -89,7 +89,7 @@ class _ServicePageState extends State<ServicePage> {
ListTile(
iconColor: Theme.of(context).colorScheme.onBackground,
onTap: () => {
context.read<JobsCubit>().createOrRemoveServiceToggleJob(
context.read<JobsCubit>().addJob(
ServiceToggleJob(
service: service,
needToTurnOn: serviceDisabled,

View File

@ -157,8 +157,7 @@ class _Card extends StatelessWidget {
return BrandSwitch(
value: isActive,
onChanged: (final value) =>
jobsCubit.createOrRemoveServiceToggleJob(
onChanged: (final value) => jobsCubit.addJob(
ServiceToggleJob(
service: service,
needToTurnOn: value,